Update internal libass copy to commit 8db4a5
authorgreg <greg@b3059339-0415-0410-9bf9-f77b7e298cf2>
Fri, 8 Jan 2010 18:35:44 +0000 (18:35 +0000)
committergreg <greg@b3059339-0415-0410-9bf9-f77b7e298cf2>
Fri, 8 Jan 2010 18:35:44 +0000 (18:35 +0000)
git-svn-id: svn://git.mplayerhq.hu/mplayer/trunk@30242 b3059339-0415-0410-9bf9-f77b7e298cf2

24 files changed:
Makefile
libass/ass.c
libass/ass.h
libass/ass_bitmap.c
libass/ass_bitmap.h
libass/ass_cache.c
libass/ass_cache.h
libass/ass_cache_template.h [new file with mode: 0644]
libass/ass_drawing.c [new file with mode: 0644]
libass/ass_drawing.h [new file with mode: 0644]
libass/ass_font.c
libass/ass_font.h
libass/ass_fontconfig.c
libass/ass_fontconfig.h
libass/ass_library.c
libass/ass_library.h
libass/ass_parse.c [new file with mode: 0644]
libass/ass_parse.h [new file with mode: 0644]
libass/ass_render.c
libass/ass_render.h [new file with mode: 0644]
libass/ass_strtod.c [new file with mode: 0644]
libass/ass_types.h
libass/ass_utils.c
libass/ass_utils.h

index 46069a7..00d615b 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -122,10 +122,13 @@ SRCS_COMMON-$(LIBASS)                += libmpcodecs/vf_ass.c \
 SRCS_COMMON-$(LIBASS_INTERNAL)       += libass/ass.c \
                                         libass/ass_bitmap.c \
                                         libass/ass_cache.c \
+                                        libass/ass_drawing.c \
                                         libass/ass_font.c \
                                         libass/ass_fontconfig.c \
                                         libass/ass_library.c \
+                                        libass/ass_parse.c \
                                         libass/ass_render.c \
+                                        libass/ass_strtod.c \
                                         libass/ass_utils.c \
 
 SRCS_COMMON-$(LIBAVCODEC)            += av_opts.c \
index 370063a..6becb39 100644 (file)
@@ -1,5 +1,3 @@
-// -*- c-basic-offset: 8; indent-tabs-mode: t -*-
-// vim:ts=8:sw=8:noet:ai:
 /*
  * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
  *
 #include "ass.h"
 #include "ass_utils.h"
 #include "ass_library.h"
-#include "mputils.h"
 
-typedef enum {PST_UNKNOWN = 0, PST_INFO, PST_STYLES, PST_EVENTS, PST_FONTS} parser_state_t;
-
-struct parser_priv_s {
-       parser_state_t state;
-       char* fontname;
-       char* fontdata;
-       int fontdata_size;
-       int fontdata_used;
+typedef enum {
+    PST_UNKNOWN = 0,
+    PST_INFO,
+    PST_STYLES,
+    PST_EVENTS,
+    PST_FONTS
+} ParserState;
+
+struct parser_priv {
+    ParserState state;
+    char *fontname;
+    char *fontdata;
+    int fontdata_size;
+    int fontdata_used;
 };
 
 #define ASS_STYLES_ALLOC 20
 #define ASS_EVENTS_ALLOC 200
 
-void ass_free_track(ass_track_t* track) {
-       int i;
-
-       if (track->parser_priv) {
-               if (track->parser_priv->fontname)
-                       free(track->parser_priv->fontname);
-               if (track->parser_priv->fontdata)
-                       free(track->parser_priv->fontdata);
-               free(track->parser_priv);
-       }
-       if (track->style_format)
-               free(track->style_format);
-       if (track->event_format)
-               free(track->event_format);
-       if (track->styles) {
-               for (i = 0; i < track->n_styles; ++i)
-                       ass_free_style(track, i);
-               free(track->styles);
-       }
-       if (track->events) {
-               for (i = 0; i < track->n_events; ++i)
-                       ass_free_event(track, i);
-               free(track->events);
-       }
+void ass_free_track(ASS_Track *track)
+{
+    int i;
+
+    if (track->parser_priv) {
+        if (track->parser_priv->fontname)
+            free(track->parser_priv->fontname);
+        if (track->parser_priv->fontdata)
+            free(track->parser_priv->fontdata);
+        free(track->parser_priv);
+    }
+    if (track->style_format)
+        free(track->style_format);
+    if (track->event_format)
+        free(track->event_format);
+    if (track->styles) {
+        for (i = 0; i < track->n_styles; ++i)
+            ass_free_style(track, i);
+        free(track->styles);
+    }
+    if (track->events) {
+        for (i = 0; i < track->n_events; ++i)
+            ass_free_event(track, i);
+        free(track->events);
+    }
+    free(track->name);
+    free(track);
 }
 
 /// \brief Allocate a new style struct
 /// \param track track
 /// \return style id
-int ass_alloc_style(ass_track_t* track) {
-       int sid;
+int ass_alloc_style(ASS_Track *track)
+{
+    int sid;
 
-       assert(track->n_styles <= track->max_styles);
+    assert(track->n_styles <= track->max_styles);
 
-       if (track->n_styles == track->max_styles) {
-               track->max_styles += ASS_STYLES_ALLOC;
-               track->styles = (ass_style_t*)realloc(track->styles, sizeof(ass_style_t)*track->max_styles);
-       }
+    if (track->n_styles == track->max_styles) {
+        track->max_styles += ASS_STYLES_ALLOC;
+        track->styles =
+            (ASS_Style *) realloc(track->styles,
+                                  sizeof(ASS_Style) *
+                                  track->max_styles);
+    }
 
-       sid = track->n_styles++;
-       memset(track->styles + sid, 0, sizeof(ass_style_t));
-       return sid;
+    sid = track->n_styles++;
+    memset(track->styles + sid, 0, sizeof(ASS_Style));
+    return sid;
 }
 
 /// \brief Allocate a new event struct
 /// \param track track
 /// \return event id
-int ass_alloc_event(ass_track_t* track) {
-       int eid;
+int ass_alloc_event(ASS_Track *track)
+{
+    int eid;
 
-       assert(track->n_events <= track->max_events);
+    assert(track->n_events <= track->max_events);
 
-       if (track->n_events == track->max_events) {
-               track->max_events += ASS_EVENTS_ALLOC;
-               track->events = (ass_event_t*)realloc(track->events, sizeof(ass_event_t)*track->max_events);
-       }
+    if (track->n_events == track->max_events) {
+        track->max_events += ASS_EVENTS_ALLOC;
+        track->events =
+            (ASS_Event *) realloc(track->events,
+                                  sizeof(ASS_Event) *
+                                  track->max_events);
+    }
 
-       eid = track->n_events++;
-       memset(track->events + eid, 0, sizeof(ass_event_t));
-       return eid;
+    eid = track->n_events++;
+    memset(track->events + eid, 0, sizeof(ASS_Event));
+    return eid;
 }
 
-void ass_free_event(ass_track_t* track, int eid) {
-       ass_event_t* event = track->events + eid;
-       if (event->Name)
-               free(event->Name);
-       if (event->Effect)
-               free(event->Effect);
-       if (event->Text)
-               free(event->Text);
-       if (event->render_priv)
-               free(event->render_priv);
+void ass_free_event(ASS_Track *track, int eid)
+{
+    ASS_Event *event = track->events + eid;
+    if (event->Name)
+        free(event->Name);
+    if (event->Effect)
+        free(event->Effect);
+    if (event->Text)
+        free(event->Text);
+    if (event->render_priv)
+        free(event->render_priv);
 }
 
-void ass_free_style(ass_track_t* track, int sid) {
-       ass_style_t* style = track->styles + sid;
-       if (style->Name)
-               free(style->Name);
-       if (style->FontName)
-               free(style->FontName);
+void ass_free_style(ASS_Track *track, int sid)
+{
+    ASS_Style *style = track->styles + sid;
+    if (style->Name)
+        free(style->Name);
+    if (style->FontName)
+        free(style->FontName);
 }
 
 // ==============================================================================================
 
-static void skip_spaces(char** str) {
-       char* p = *str;
-       while ((*p==' ') || (*p=='\t'))
-               ++p;
-       *str = p;
+static void skip_spaces(char **str)
+{
+    char *p = *str;
+    while ((*p == ' ') || (*p == '\t'))
+        ++p;
+    *str = p;
 }
 
-static void rskip_spaces(char** str, char* limit) {
-       char* p = *str;
-       while ((p >= limit) && ((*p==' ') || (*p=='\t')))
-               --p;
-       *str = p;
+static void rskip_spaces(char **str, char *limit)
+{
+    char *p = *str;
+    while ((p >= limit) && ((*p == ' ') || (*p == '\t')))
+        --p;
+    *str = p;
 }
 
 /**
@@ -160,47 +178,55 @@ static void rskip_spaces(char** str, char* limit) {
  * Returnes 0 if no styles found => expects at least 1 style.
  * Parsing code always adds "Default" style in the end.
  */
-static int lookup_style(ass_track_t* track, char* name) {
-       int i;
-       if (*name == '*') ++name; // FIXME: what does '*' really mean ?
-       for (i = track->n_styles - 1; i >= 0; --i) {
-               // FIXME: mb strcasecmp ?
-               if (strcmp(track->styles[i].Name, name) == 0)
-                       return i;
-       }
-       i = track->default_style;
-       mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_NoStyleNamedXFoundUsingY, track, name, track->styles[i].Name);
-       return i; // use the first style
+static int lookup_style(ASS_Track *track, char *name)
+{
+    int i;
+    if (*name == '*')
+        ++name;                 // FIXME: what does '*' really mean ?
+    for (i = track->n_styles - 1; i >= 0; --i) {
+        // FIXME: mb strcasecmp ?
+        if (strcmp(track->styles[i].Name, name) == 0)
+            return i;
+    }
+    i = track->default_style;
+    ass_msg(track->library, MSGL_WARN,
+            "[%p]: Warning: no style named '%s' found, using '%s'",
+            track, name, track->styles[i].Name);
+    return i;                   // use the first style
 }
 
-static uint32_t string2color(char* p) {
-       uint32_t tmp;
-       (void)strtocolor(&p, &tmp);
-       return tmp;
+static uint32_t string2color(ASS_Library *library, char *p)
+{
+    uint32_t tmp;
+    (void) strtocolor(library, &p, &tmp, 0);
+    return tmp;
 }
 
-static long long string2timecode(char* p) {
-       unsigned h, m, s, ms;
-       long long tm;
-       int res = sscanf(p, "%1d:%2d:%2d.%2d", &h, &m, &s, &ms);
-       if (res < 4) {
-               mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_BadTimestamp);
-               return 0;
-       }
-       tm = ((h * 60 + m) * 60 + s) * 1000 + ms * 10;
-       return tm;
+static long long string2timecode(ASS_Library *library, char *p)
+{
+    unsigned h, m, s, ms;
+    long long tm;
+    int res = sscanf(p, "%1d:%2d:%2d.%2d", &h, &m, &s, &ms);
+    if (res < 4) {
+        ass_msg(library, MSGL_WARN, "Bad timestamp");
+        return 0;
+    }
+    tm = ((h * 60 + m) * 60 + s) * 1000 + ms * 10;
+    return tm;
 }
 
 /**
  * \brief converts numpad-style align to align.
  */
-static int numpad2align(int val) {
-       int res, v;
-       v = (val - 1) / 3; // 0, 1 or 2 for vertical alignment
-       if (v != 0) v = 3 - v;
-       res = ((val - 1) % 3) + 1; // horizontal alignment
-       res += v*4;
-       return res;
+static int numpad2align(int val)
+{
+    int res, v;
+    v = (val - 1) / 3;          // 0, 1 or 2 for vertical alignment
+    if (v != 0)
+        v = 3 - v;
+    res = ((val - 1) % 3) + 1;  // horizontal alignment
+    res += v * 4;
+    return res;
 }
 
 #define NEXT(str,token) \
@@ -210,51 +236,62 @@ static int numpad2align(int val) {
 #define ANYVAL(name,func) \
        } else if (strcasecmp(tname, #name) == 0) { \
                target->name = func(token); \
-               mp_msg(MSGT_ASS, MSGL_DBG2, "%s = %s\n", #name, token);
+               ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
 
 #define STRVAL(name) \
        } else if (strcasecmp(tname, #name) == 0) { \
                if (target->name != NULL) free(target->name); \
                target->name = strdup(token); \
-               mp_msg(MSGT_ASS, MSGL_DBG2, "%s = %s\n", #name, token);
+               ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
+
+#define COLORVAL(name) \
+       } else if (strcasecmp(tname, #name) == 0) { \
+               target->name = string2color(track->library, token); \
+               ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
 
-#define COLORVAL(name) ANYVAL(name,string2color)
 #define INTVAL(name) ANYVAL(name,atoi)
 #define FPVAL(name) ANYVAL(name,atof)
-#define TIMEVAL(name) ANYVAL(name,string2timecode)
+#define TIMEVAL(name) \
+       } else if (strcasecmp(tname, #name) == 0) { \
+               target->name = string2timecode(track->library, token); \
+               ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
+
 #define STYLEVAL(name) \
        } else if (strcasecmp(tname, #name) == 0) { \
                target->name = lookup_style(track, token); \
-               mp_msg(MSGT_ASS, MSGL_DBG2, "%s = %s\n", #name, token);
+               ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
 
 #define ALIAS(alias,name) \
        if (strcasecmp(tname, #alias) == 0) {tname = #name;}
 
-static char* next_token(char** str) {
-       char* p = *str;
-       char* start;
-       skip_spaces(&p);
-       if (*p == '\0') {
-               *str = p;
-               return 0;
-       }
-       start = p; // start of the token
-       for (; (*p != '\0') && (*p != ','); ++p) {}
-       if (*p == '\0') {
-               *str = p; // eos found, str will point to '\0' at exit
-       } else {
-               *p = '\0';
-               *str = p + 1; // ',' found, str will point to the next char (beginning of the next token)
-       }
-       --p; // end of current token
-       rskip_spaces(&p, start);
-       if (p < start)
-               p = start; // empty token
-       else
-               ++p; // the first space character, or '\0'
-       *p = '\0';
-       return start;
+static char *next_token(char **str)
+{
+    char *p = *str;
+    char *start;
+    skip_spaces(&p);
+    if (*p == '\0') {
+        *str = p;
+        return 0;
+    }
+    start = p;                  // start of the token
+    for (; (*p != '\0') && (*p != ','); ++p) {
+    }
+    if (*p == '\0') {
+        *str = p;               // eos found, str will point to '\0' at exit
+    } else {
+        *p = '\0';
+        *str = p + 1;           // ',' found, str will point to the next char (beginning of the next token)
+    }
+    --p;                        // end of current token
+    rskip_spaces(&p, start);
+    if (p < start)
+        p = start;              // empty token
+    else
+        ++p;                    // the first space character, or '\0'
+    *p = '\0';
+    return start;
 }
+
 /**
  * \brief Parse the tail of Dialogue line
  * \param track track
@@ -262,68 +299,62 @@ static char* next_token(char** str) {
  * \param str string to parse, zero-terminated
  * \param n_ignored number of format options to skip at the beginning
 */
-static int process_event_tail(ass_track_t* track, ass_event_t* event, char* str, int n_ignored)
+static int process_event_tail(ASS_Track *track, ASS_Event *event,
+                              char *str, int n_ignored)
 {
-       char* token;
-       char* tname;
-       char* p = str;
-       int i;
-       ass_event_t* target = event;
-
-       char* format;
-       char* q; // format scanning pointer
-
-       if (!track->event_format) {
-               track->event_format = strdup("Format: Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text");
-               mp_msg(MSGT_ASS, MSGL_V, "Event format is broken, reseting to defaults.\n");
-       }
-
-       q = format = strdup(track->event_format);
-
-       if (track->n_styles == 0) {
-               // add "Default" style to the end
-               // will be used if track does not contain a default style (or even does not contain styles at all)
-               int sid = ass_alloc_style(track);
-               track->styles[sid].Name = strdup("Default");
-               track->styles[sid].FontName = strdup("Arial");
-       }
-
-       for (i = 0; i < n_ignored; ++i) {
-               NEXT(q, tname);
-       }
-
-       while (1) {
-               NEXT(q, tname);
-               if (strcasecmp(tname, "Text") == 0) {
-                       char* last;
-                       event->Text = strdup(p);
-                       if (*event->Text != 0) {
-                               last = event->Text + strlen(event->Text) - 1;
-                               if (last >= event->Text && *last == '\r')
-                                       *last = 0;
-                       }
-                       mp_msg(MSGT_ASS, MSGL_DBG2, "Text = %s\n", event->Text);
-                       event->Duration -= event->Start;
-                       free(format);
-                       return 0; // "Text" is always the last
-               }
-               NEXT(p, token);
-
-               ALIAS(End,Duration) // temporarily store end timecode in event->Duration
-               if (0) { // cool ;)
-                       INTVAL(Layer)
-                       STYLEVAL(Style)
-                       STRVAL(Name)
-                       STRVAL(Effect)
-                       INTVAL(MarginL)
-                       INTVAL(MarginR)
-                       INTVAL(MarginV)
-                       TIMEVAL(Start)
-                       TIMEVAL(Duration)
-               }
-       }
-       free(format);
-       return 1;
+    char *token;
+    char *tname;
+    char *p = str;
+    int i;
+    ASS_Event *target = event;
+
+    char *format = strdup(track->event_format);
+    char *q = format;           // format scanning pointer
+
+    if (track->n_styles == 0) {
+        // add "Default" style to the end
+        // will be used if track does not contain a default style (or even does not contain styles at all)
+        int sid = ass_alloc_style(track);
+        track->styles[sid].Name = strdup("Default");
+        track->styles[sid].FontName = strdup("Arial");
+    }
+
+    for (i = 0; i < n_ignored; ++i) {
+        NEXT(q, tname);
+    }
+
+    while (1) {
+        NEXT(q, tname);
+        if (strcasecmp(tname, "Text") == 0) {
+            char *last;
+            event->Text = strdup(p);
+            if (*event->Text != 0) {
+                last = event->Text + strlen(event->Text) - 1;
+                if (last >= event->Text && *last == '\r')
+                    *last = 0;
+            }
+            ass_msg(track->library, MSGL_DBG2, "Text = %s", event->Text);
+            event->Duration -= event->Start;
+            free(format);
+            return 0;           // "Text" is always the last
+        }
+        NEXT(p, token);
+
+        ALIAS(End, Duration)    // temporarily store end timecode in event->Duration
+        if (0) {            // cool ;)
+            INTVAL(Layer)
+            STYLEVAL(Style)
+            STRVAL(Name)
+            STRVAL(Effect)
+            INTVAL(MarginL)
+            INTVAL(MarginR)
+            INTVAL(MarginV)
+            TIMEVAL(Start)
+            TIMEVAL(Duration)
+        }
+    }
+    free(format);
+    return 1;
 }
 
 /**
@@ -331,73 +362,79 @@ static int process_event_tail(ass_track_t* track, ass_event_t* event, char* str,
  * \param track track to apply overrides to
  * The format for overrides is [StyleName.]Field=Value
  */
-void process_force_style(ass_track_t* track) {
-       char **fs, *eq, *dt, *style, *tname, *token;
-       ass_style_t* target;
-       int sid;
-       char** list = track->library->style_overrides;
-
-       if (!list) return;
-
-       for (fs = list; *fs; ++fs) {
-               eq = strrchr(*fs, '=');
-               if (!eq)
-                       continue;
-               *eq = '\0';
-               token = eq + 1;
-
-               if(!strcasecmp(*fs, "PlayResX"))
-                       track->PlayResX = atoi(token);
-               else if(!strcasecmp(*fs, "PlayResY"))
-                       track->PlayResY = atoi(token);
-               else if(!strcasecmp(*fs, "Timer"))
-                       track->Timer = atof(token);
-               else if(!strcasecmp(*fs, "WrapStyle"))
-                       track->WrapStyle = atoi(token);
-               else if(!strcasecmp(*fs, "ScaledBorderAndShadow"))
-                       track->ScaledBorderAndShadow = parse_bool(token);
-
-               dt = strrchr(*fs, '.');
-               if (dt) {
-                       *dt = '\0';
-                       style = *fs;
-                       tname = dt + 1;
-               } else {
-                       style = NULL;
-                       tname = *fs;
-               }
-               for (sid = 0; sid < track->n_styles; ++sid) {
-                       if (style == NULL || strcasecmp(track->styles[sid].Name, style) == 0) {
-                               target = track->styles + sid;
-                               if (0) {
-                                       STRVAL(FontName)
-                                       COLORVAL(PrimaryColour)
-                                       COLORVAL(SecondaryColour)
-                                       COLORVAL(OutlineColour)
-                                       COLORVAL(BackColour)
-                                       FPVAL(FontSize)
-                                       INTVAL(Bold)
-                                       INTVAL(Italic)
-                                       INTVAL(Underline)
-                                       INTVAL(StrikeOut)
-                                       FPVAL(Spacing)
-                                       INTVAL(Angle)
-                                       INTVAL(BorderStyle)
-                                       INTVAL(Alignment)
-                                       INTVAL(MarginL)
-                                       INTVAL(MarginR)
-                                       INTVAL(MarginV)
-                                       INTVAL(Encoding)
-                                       FPVAL(ScaleX)
-                                       FPVAL(ScaleY)
-                                       FPVAL(Outline)
-                                       FPVAL(Shadow)
-                               }
-                       }
-               }
-               *eq = '=';
-               if (dt) *dt = '.';
-       }
+void ass_process_force_style(ASS_Track *track)
+{
+    char **fs, *eq, *dt, *style, *tname, *token;
+    ASS_Style *target;
+    int sid;
+    char **list = track->library->style_overrides;
+
+    if (!list)
+        return;
+
+    for (fs = list; *fs; ++fs) {
+        eq = strrchr(*fs, '=');
+        if (!eq)
+            continue;
+        *eq = '\0';
+        token = eq + 1;
+
+        if (!strcasecmp(*fs, "PlayResX"))
+            track->PlayResX = atoi(token);
+        else if (!strcasecmp(*fs, "PlayResY"))
+            track->PlayResY = atoi(token);
+        else if (!strcasecmp(*fs, "Timer"))
+            track->Timer = atof(token);
+        else if (!strcasecmp(*fs, "WrapStyle"))
+            track->WrapStyle = atoi(token);
+        else if (!strcasecmp(*fs, "ScaledBorderAndShadow"))
+            track->ScaledBorderAndShadow = parse_bool(token);
+        else if (!strcasecmp(*fs, "Kerning"))
+            track->Kerning = parse_bool(token);
+
+        dt = strrchr(*fs, '.');
+        if (dt) {
+            *dt = '\0';
+            style = *fs;
+            tname = dt + 1;
+        } else {
+            style = NULL;
+            tname = *fs;
+        }
+        for (sid = 0; sid < track->n_styles; ++sid) {
+            if (style == NULL
+                || strcasecmp(track->styles[sid].Name, style) == 0) {
+                target = track->styles + sid;
+                if (0) {
+                    STRVAL(FontName)
+                    COLORVAL(PrimaryColour)
+                    COLORVAL(SecondaryColour)
+                    COLORVAL(OutlineColour)
+                    COLORVAL(BackColour)
+                    FPVAL(FontSize)
+                    INTVAL(Bold)
+                    INTVAL(Italic)
+                    INTVAL(Underline)
+                    INTVAL(StrikeOut)
+                    FPVAL(Spacing)
+                    INTVAL(Angle)
+                    INTVAL(BorderStyle)
+                    INTVAL(Alignment)
+                    INTVAL(MarginL)
+                    INTVAL(MarginR)
+                    INTVAL(MarginV)
+                    INTVAL(Encoding)
+                    FPVAL(ScaleX)
+                    FPVAL(ScaleY)
+                    FPVAL(Outline)
+                    FPVAL(Shadow)
+                }
+            }
+        }
+        *eq = '=';
+        if (dt)
+            *dt = '.';
+    }
 }
 
 /**
@@ -406,257 +443,294 @@ void process_force_style(ass_track_t* track) {
  * \param str string to parse, zero-terminated
  * Allocates a new style struct.
 */
-static int process_style(ass_track_t* track, char *str)
+static int process_style(ASS_Track *track, char *str)
 {
 
-       char* token;
-       char* tname;
-       char* p = str;
-       char* format;
-       char* q; // format scanning pointer
-       int sid;
-       ass_style_t* style;
-       ass_style_t* target;
-
-       if (!track->style_format) {
-               // no style format header
-               // probably an ancient script version
-               if (track->track_type == TRACK_TYPE_SSA)
-                       track->style_format = strdup("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
-                                       "TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline,"
-                                       "Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding");
-               else
-                       track->style_format = strdup("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
-                                       "OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut,"
-                                       "ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow,"
-                                       "Alignment, MarginL, MarginR, MarginV, Encoding");
-       }
-
-       q = format = strdup(track->style_format);
-
-       mp_msg(MSGT_ASS, MSGL_V, "[%p] Style: %s\n", track, str);
-
-       sid = ass_alloc_style(track);
-
-       style = track->styles + sid;
-       target = style;
-// fill style with some default values
-       style->ScaleX = 100.;
-       style->ScaleY = 100.;
-
-       while (1) {
-               NEXT(q, tname);
-               NEXT(p, token);
-
-//             ALIAS(TertiaryColour,OutlineColour) // ignore TertiaryColour; it appears only in SSA, and is overridden by BackColour
-
-               if (0) { // cool ;)
-                       STRVAL(Name)
-                               if ((strcmp(target->Name, "Default")==0) || (strcmp(target->Name, "*Default")==0))
-                                       track->default_style = sid;
-                       STRVAL(FontName)
-                       COLORVAL(PrimaryColour)
-                       COLORVAL(SecondaryColour)
-                       COLORVAL(OutlineColour) // TertiaryColor
-                       COLORVAL(BackColour)
-                               // SSA uses BackColour for both outline and shadow
-                               // this will destroy SSA's TertiaryColour, but i'm not going to use it anyway
-                               if (track->track_type == TRACK_TYPE_SSA)
-                                       target->OutlineColour = target->BackColour;
-                       FPVAL(FontSize)
-                       INTVAL(Bold)
-                       INTVAL(Italic)
-                       INTVAL(Underline)
-                       INTVAL(StrikeOut)
-                       FPVAL(Spacing)
-                       INTVAL(Angle)
-                       INTVAL(BorderStyle)
-                       INTVAL(Alignment)
-                               if (track->track_type == TRACK_TYPE_ASS)
-                                       target->Alignment = numpad2align(target->Alignment);
-                       INTVAL(MarginL)
-                       INTVAL(MarginR)
-                       INTVAL(MarginV)
-                       INTVAL(Encoding)
-                       FPVAL(ScaleX)
-                       FPVAL(ScaleY)
-                       FPVAL(Outline)
-                       FPVAL(Shadow)
-               }
-       }
-       style->ScaleX /= 100.;
-       style->ScaleY /= 100.;
-       style->Bold = !!style->Bold;
-       style->Italic = !!style->Italic;
-       style->Underline = !!style->Underline;
-       if (!style->Name)
-               style->Name = strdup("Default");
-       if (!style->FontName)
-               style->FontName = strdup("Arial");
-       // skip '@' at the start of the font name
-       if (*style->FontName == '@') {
-               p = style->FontName;
-               style->FontName = strdup(p + 1);
-               free(p);
-       }
-       free(format);
-       return 0;
+    char *token;
+    char *tname;
+    char *p = str;
+    char *format;
+    char *q;                    // format scanning pointer
+    int sid;
+    ASS_Style *style;
+    ASS_Style *target;
+
+    if (!track->style_format) {
+        // no style format header
+        // probably an ancient script version
+        if (track->track_type == TRACK_TYPE_SSA)
+            track->style_format =
+                strdup
+                ("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
+                 "TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline,"
+                 "Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding");
+        else
+            track->style_format =
+                strdup
+                ("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
+                 "OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut,"
+                 "ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow,"
+                 "Alignment, MarginL, MarginR, MarginV, Encoding");
+    }
+
+    q = format = strdup(track->style_format);
+
+    ass_msg(track->library, MSGL_V, "[%p] Style: %s", track, str);
+
+    sid = ass_alloc_style(track);
+
+    style = track->styles + sid;
+    target = style;
+
+    // fill style with some default values
+    style->ScaleX = 100.;
+    style->ScaleY = 100.;
+
+    while (1) {
+        NEXT(q, tname);
+        NEXT(p, token);
+
+        if (0) {                // cool ;)
+            STRVAL(Name)
+            if ((strcmp(target->Name, "Default") == 0)
+                || (strcmp(target->Name, "*Default") == 0))
+            track->default_style = sid;
+            STRVAL(FontName)
+            COLORVAL(PrimaryColour)
+            COLORVAL(SecondaryColour)
+            COLORVAL(OutlineColour) // TertiaryColor
+            COLORVAL(BackColour)
+            // SSA uses BackColour for both outline and shadow
+            // this will destroy SSA's TertiaryColour, but i'm not going to use it anyway
+            if (track->track_type == TRACK_TYPE_SSA)
+                target->OutlineColour = target->BackColour;
+            FPVAL(FontSize)
+            INTVAL(Bold)
+            INTVAL(Italic)
+            INTVAL(Underline)
+            INTVAL(StrikeOut)
+            FPVAL(Spacing)
+            INTVAL(Angle)
+            INTVAL(BorderStyle)
+            INTVAL(Alignment)
+            if (track->track_type == TRACK_TYPE_ASS)
+                target->Alignment = numpad2align(target->Alignment);
+            INTVAL(MarginL)
+            INTVAL(MarginR)
+            INTVAL(MarginV)
+            INTVAL(Encoding)
+            FPVAL(ScaleX)
+            FPVAL(ScaleY)
+            FPVAL(Outline)
+            FPVAL(Shadow)
+        }
+    }
+    style->ScaleX /= 100.;
+    style->ScaleY /= 100.;
+    style->Bold = !!style->Bold;
+    style->Italic = !!style->Italic;
+    style->Underline = !!style->Underline;
+    if (!style->Name)
+        style->Name = strdup("Default");
+    if (!style->FontName)
+        style->FontName = strdup("Arial");
+    // skip '@' at the start of the font name
+    if (*style->FontName == '@') {
+        p = style->FontName;
+        style->FontName = strdup(p + 1);
+        free(p);
+    }
+    free(format);
+    return 0;
 
 }
 
-static int process_styles_line(ass_track_t* track, char *str)
+static int process_styles_line(ASS_Track *track, char *str)
 {
-       if (!strncmp(str,"Format:", 7)) {
-               char* p = str + 7;
-               skip_spaces(&p);
-               track->style_format = strdup(p);
-               mp_msg(MSGT_ASS, MSGL_DBG2, "Style format: %s\n", track->style_format);
-       } else if (!strncmp(str,"Style:", 6)) {
-               char* p = str + 6;
-               skip_spaces(&p);
-               process_style(track, p);
-       }
-       return 0;
+    if (!strncmp(str, "Format:", 7)) {
+        char *p = str + 7;
+        skip_spaces(&p);
+        track->style_format = strdup(p);
+        ass_msg(track->library, MSGL_DBG2, "Style format: %s",
+               track->style_format);
+    } else if (!strncmp(str, "Style:", 6)) {
+        char *p = str + 6;
+        skip_spaces(&p);
+        process_style(track, p);
+    }
+    return 0;
 }
 
-static int process_info_line(ass_track_t* track, char *str)
+static int process_info_line(ASS_Track *track, char *str)
 {
-       if (!strncmp(str, "PlayResX:", 9)) {
-               track->PlayResX = atoi(str + 9);
-       } else if (!strncmp(str,"PlayResY:", 9)) {
-               track->PlayResY = atoi(str + 9);
-       } else if (!strncmp(str,"Timer:", 6)) {
-               track->Timer = atof(str + 6);
-       } else if (!strncmp(str,"WrapStyle:", 10)) {
-               track->WrapStyle = atoi(str + 10);
-       } else if (!strncmp(str, "ScaledBorderAndShadow:", 22)) {
-               track->ScaledBorderAndShadow = parse_bool(str + 22);
-       }
-       return 0;
+    if (!strncmp(str, "PlayResX:", 9)) {
+        track->PlayResX = atoi(str + 9);
+    } else if (!strncmp(str, "PlayResY:", 9)) {
+        track->PlayResY = atoi(str + 9);
+    } else if (!strncmp(str, "Timer:", 6)) {
+        track->Timer = atof(str + 6);
+    } else if (!strncmp(str, "WrapStyle:", 10)) {
+        track->WrapStyle = atoi(str + 10);
+    } else if (!strncmp(str, "ScaledBorderAndShadow:", 22)) {
+        track->ScaledBorderAndShadow = parse_bool(str + 22);
+    } else if (!strncmp(str, "Kerning:", 8)) {
+        track->Kerning = parse_bool(str + 8);
+    }
+    return 0;
 }
 
-static int process_events_line(ass_track_t* track, char *str)
+static void event_format_fallback(ASS_Track *track)
 {
-       if (!strncmp(str, "Format:", 7)) {
-               char* p = str + 7;
-               skip_spaces(&p);
-               track->event_format = strdup(p);
-               mp_msg(MSGT_ASS, MSGL_DBG2, "Event format: %s\n", track->event_format);
-       } else if (!strncmp(str, "Dialogue:", 9)) {
-               // This should never be reached for embedded subtitles.
-               // They have slightly different format and are parsed in ass_process_chunk,
-               // called directly from demuxer
-               int eid;
-               ass_event_t* event;
-
-               str += 9;
-               skip_spaces(&str);
-
-               eid = ass_alloc_event(track);
-               event = track->events + eid;
-
-               process_event_tail(track, event, str, 0);
-       } else {
-               mp_msg(MSGT_ASS, MSGL_V, "Not understood: %s  \n", str);
-       }
-       return 0;
+    track->parser_priv->state = PST_EVENTS;
+    if (track->track_type == TRACK_TYPE_SSA)
+        track->event_format = strdup("Format: Marked, Start, End, Style, "
+            "Name, MarginL, MarginR, MarginV, Effect, Text");
+    else
+        track->event_format = strdup("Format: Layer, Start, End, Style, "
+            "Actor, MarginL, MarginR, MarginV, Effect, Text");
+    ass_msg(track->library, MSGL_V,
+            "No event format found, using fallback");
+}
+
+static int process_events_line(ASS_Track *track, char *str)
+{
+    if (!strncmp(str, "Format:", 7)) {
+        char *p = str + 7;
+        skip_spaces(&p);
+        track->event_format = strdup(p);
+        ass_msg(track->library, MSGL_DBG2, "Event format: %s", track->event_format);
+    } else if (!strncmp(str, "Dialogue:", 9)) {
+        // This should never be reached for embedded subtitles.
+        // They have slightly different format and are parsed in ass_process_chunk,
+        // called directly from demuxer
+        int eid;
+        ASS_Event *event;
+
+        str += 9;
+        skip_spaces(&str);
+
+        eid = ass_alloc_event(track);
+        event = track->events + eid;
+
+        // We can't parse events with event_format
+        if (!track->event_format)
+            event_format_fallback(track);
+
+        process_event_tail(track, event, str, 0);
+    } else {
+        ass_msg(track->library, MSGL_V, "Not understood: '%s'", str);
+    }
+    return 0;
 }
 
 // Copied from mkvtoolnix
-static unsigned char* decode_chars(unsigned char c1, unsigned char c2,
-               unsigned char c3, unsigned char c4, unsigned char* dst, int cnt)
+static unsigned char *decode_chars(unsigned char c1, unsigned char c2,
+                                   unsigned char c3, unsigned char c4,
+                                   unsigned char *dst, int cnt)
 {
-       uint32_t value;
-       unsigned char bytes[3];
-       int i;
-
-       value = ((c1 - 33) << 18) + ((c2 - 33) << 12) + ((c3 - 33) << 6) + (c4 - 33);
-       bytes[2] = value & 0xff;
-       bytes[1] = (value & 0xff00) >> 8;
-       bytes[0] = (value & 0xff0000) >> 16;
-
-       for (i = 0; i < cnt; ++i)
-               *dst++ = bytes[i];
-       return dst;
+    uint32_t value;
+    unsigned char bytes[3];
+    int i;
+
+    value =
+        ((c1 - 33) << 18) + ((c2 - 33) << 12) + ((c3 - 33) << 6) + (c4 -
+                                                                    33);
+    bytes[2] = value & 0xff;
+    bytes[1] = (value & 0xff00) >> 8;
+    bytes[0] = (value & 0xff0000) >> 16;
+
+    for (i = 0; i < cnt; ++i)
+        *dst++ = bytes[i];
+    return dst;
 }
 
-static int decode_font(ass_track_t* track)
+static int decode_font(ASS_Track *track)
 {
-       unsigned char* p;
-       unsigned char* q;
-       int i;
-       int size; // original size
-       int dsize; // decoded size
-       unsigned char* buf = 0;
-
-       mp_msg(MSGT_ASS, MSGL_V, "font: %d bytes encoded data \n", track->parser_priv->fontdata_used);
-       size = track->parser_priv->fontdata_used;
-       if (size % 4 == 1) {
-               mp_msg(MSGT_ASS, MSGL_ERR, MSGTR_LIBASS_BadEncodedDataSize);
-               goto error_decode_font;
-       }
-       buf = malloc(size / 4 * 3 + 2);
-       q = buf;
-       for (i = 0, p = (unsigned char*)track->parser_priv->fontdata; i < size / 4; i++, p+=4) {
-               q = decode_chars(p[0], p[1], p[2], p[3], q, 3);
-       }
-       if (size % 4 == 2) {
-               q = decode_chars(p[0], p[1], 0, 0, q, 1);
-       } else if (size % 4 == 3) {
-               q = decode_chars(p[0], p[1], p[2], 0, q, 2);
-       }
-       dsize = q - buf;
-       assert(dsize <= size / 4 * 3 + 2);
-
-       if (track->library->extract_fonts) {
-               ass_add_font(track->library, track->parser_priv->fontname, (char*)buf, dsize);
-               buf = 0;
-       }
-
-error_decode_font:
-       if (buf) free(buf);
-       free(track->parser_priv->fontname);
-       free(track->parser_priv->fontdata);
-       track->parser_priv->fontname = 0;
-       track->parser_priv->fontdata = 0;
-       track->parser_priv->fontdata_size = 0;
-       track->parser_priv->fontdata_used = 0;
-       return 0;
+    unsigned char *p;
+    unsigned char *q;
+    int i;
+    int size;                   // original size
+    int dsize;                  // decoded size
+    unsigned char *buf = 0;
+
+    ass_msg(track->library, MSGL_V, "Font: %d bytes encoded data",
+            track->parser_priv->fontdata_used);
+    size = track->parser_priv->fontdata_used;
+    if (size % 4 == 1) {
+        ass_msg(track->library, MSGL_ERR, "Bad encoded data size");
+        goto error_decode_font;
+    }
+    buf = malloc(size / 4 * 3 + 2);
+    q = buf;
+    for (i = 0, p = (unsigned char *) track->parser_priv->fontdata;
+         i < size / 4; i++, p += 4) {
+        q = decode_chars(p[0], p[1], p[2], p[3], q, 3);
+    }
+    if (size % 4 == 2) {
+        q = decode_chars(p[0], p[1], 0, 0, q, 1);
+    } else if (size % 4 == 3) {
+        q = decode_chars(p[0], p[1], p[2], 0, q, 2);
+    }
+    dsize = q - buf;
+    assert(dsize <= size / 4 * 3 + 2);
+
+    if (track->library->extract_fonts) {
+        ass_add_font(track->library, track->parser_priv->fontname,
+                     (char *) buf, dsize);
+        buf = 0;
+    }
+
+  error_decode_font:
+    if (buf)
+        free(buf);
+    free(track->parser_priv->fontname);
+    free(track->parser_priv->fontdata);
+    track->parser_priv->fontname = 0;
+    track->parser_priv->fontdata = 0;
+    track->parser_priv->fontdata_size = 0;
+    track->parser_priv->fontdata_used = 0;
+    return 0;
 }
 
-static int process_fonts_line(ass_track_t* track, char *str)
+static int process_fonts_line(ASS_Track *track, char *str)
 {
-       int len;
-
-       if (!strncmp(str, "fontname:", 9)) {
-               char* p = str + 9;
-               skip_spaces(&p);
-               if (track->parser_priv->fontname) {
-                       decode_font(track);
-               }
-               track->parser_priv->fontname = strdup(p);
-               mp_msg(MSGT_ASS, MSGL_V, "fontname: %s\n", track->parser_priv->fontname);
-               return 0;
-       }
-
-       if (!track->parser_priv->fontname) {
-               mp_msg(MSGT_ASS, MSGL_V, "Not understood: %s  \n", str);
-               return 0;
-       }
-
-       len = strlen(str);
-       if (len > 80) {
-               mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FontLineTooLong, len, str);
-               return 0;
-       }
-       if (track->parser_priv->fontdata_used + len > track->parser_priv->fontdata_size) {
-               track->parser_priv->fontdata_size += 100 * 1024;
-               track->parser_priv->fontdata = realloc(track->parser_priv->fontdata, track->parser_priv->fontdata_size);
-       }
-       memcpy(track->parser_priv->fontdata + track->parser_priv->fontdata_used, str, len);
-       track->parser_priv->fontdata_used += len;
-
-       return 0;
+    int len;
+
+    if (!strncmp(str, "fontname:", 9)) {
+        char *p = str + 9;
+        skip_spaces(&p);
+        if (track->parser_priv->fontname) {
+            decode_font(track);
+        }
+        track->parser_priv->fontname = strdup(p);
+        ass_msg(track->library, MSGL_V, "Fontname: %s",
+               track->parser_priv->fontname);
+        return 0;
+    }
+
+    if (!track->parser_priv->fontname) {
+        ass_msg(track->library, MSGL_V, "Not understood: '%s'", str);
+        return 0;
+    }
+
+    len = strlen(str);
+    if (len > 80) {
+        ass_msg(track->library, MSGL_WARN, "Font line too long: %d, %s",
+                len, str);
+        return 0;
+    }
+    if (track->parser_priv->fontdata_used + len >
+        track->parser_priv->fontdata_size) {
+        track->parser_priv->fontdata_size += 100 * 1024;
+        track->parser_priv->fontdata =
+            realloc(track->parser_priv->fontdata,
+                    track->parser_priv->fontdata_size);
+    }
+    memcpy(track->parser_priv->fontdata + track->parser_priv->fontdata_used,
+           str, len);
+    track->parser_priv->fontdata_used += len;
+
+    return 0;
 }
 
 /**
@@ -664,67 +738,72 @@ static int process_fonts_line(ass_track_t* track, char *str)
  * \param track track
  * \param str string to parse, zero-terminated
 */
-static int process_line(ass_track_t* track, char *str)
+static int process_line(ASS_Track *track, char *str)
 {
-       if (!strncasecmp(str, "[Script Info]", 13)) {
-               track->parser_priv->state = PST_INFO;
-       } else if (!strncasecmp(str, "[V4 Styles]", 11)) {
-               track->parser_priv->state = PST_STYLES;
-               track->track_type = TRACK_TYPE_SSA;
-       } else if (!strncasecmp(str, "[V4+ Styles]", 12)) {
-               track->parser_priv->state = PST_STYLES;
-               track->track_type = TRACK_TYPE_ASS;
-       } else if (!strncasecmp(str, "[Events]", 8)) {
-               track->parser_priv->state = PST_EVENTS;
-       } else if (!strncasecmp(str, "[Fonts]", 7)) {
-               track->parser_priv->state = PST_FONTS;
-       } else {
-               switch (track->parser_priv->state) {
-               case PST_INFO:
-                       process_info_line(track, str);
-                       break;
-               case PST_STYLES:
-                       process_styles_line(track, str);
-                       break;
-               case PST_EVENTS:
-                       process_events_line(track, str);
-                       break;
-               case PST_FONTS:
-                       process_fonts_line(track, str);
-                       break;
-               default:
-                       break;
-               }
-       }
-
-       // there is no explicit end-of-font marker in ssa/ass
-       if ((track->parser_priv->state != PST_FONTS) && (track->parser_priv->fontname))
-               decode_font(track);
-
-       return 0;
+    if (!strncasecmp(str, "[Script Info]", 13)) {
+        track->parser_priv->state = PST_INFO;
+    } else if (!strncasecmp(str, "[V4 Styles]", 11)) {
+        track->parser_priv->state = PST_STYLES;
+        track->track_type = TRACK_TYPE_SSA;
+    } else if (!strncasecmp(str, "[V4+ Styles]", 12)) {
+        track->parser_priv->state = PST_STYLES;
+        track->track_type = TRACK_TYPE_ASS;
+    } else if (!strncasecmp(str, "[Events]", 8)) {
+        track->parser_priv->state = PST_EVENTS;
+    } else if (!strncasecmp(str, "[Fonts]", 7)) {
+        track->parser_priv->state = PST_FONTS;
+    } else {
+        switch (track->parser_priv->state) {
+        case PST_INFO:
+            process_info_line(track, str);
+            break;
+        case PST_STYLES:
+            process_styles_line(track, str);
+            break;
+        case PST_EVENTS:
+            process_events_line(track, str);
+            break;
+        case PST_FONTS:
+            process_fonts_line(track, str);
+            break;
+        default:
+            break;
+        }
+    }
+
+    // there is no explicit end-of-font marker in ssa/ass
+    if ((track->parser_priv->state != PST_FONTS)
+        && (track->parser_priv->fontname))
+        decode_font(track);
+
+    return 0;
 }
 
-static int process_text(ass_track_t* track, char* str)
+static int process_text(ASS_Track *track, char *str)
 {
-       char* p = str;
-       while(1) {
-               char* q;
-               while (1) {
-                       if ((*p=='\r')||(*p=='\n')) ++p;
-                       else if (p[0]=='\xef' && p[1]=='\xbb' && p[2]=='\xbf') p+=3; // U+FFFE (BOM)
-                       else break;
-               }
-               for (q=p; ((*q!='\0')&&(*q!='\r')&&(*q!='\n')); ++q) {};
-               if (q==p)
-                       break;
-               if (*q != '\0')
-                       *(q++) = '\0';
-               process_line(track, p);
-               if (*q == '\0')
-                       break;
-               p = q;
-       }
-       return 0;
+    char *p = str;
+    while (1) {
+        char *q;
+        while (1) {
+            if ((*p == '\r') || (*p == '\n'))
+                ++p;
+            else if (p[0] == '\xef' && p[1] == '\xbb' && p[2] == '\xbf')
+                p += 3;         // U+FFFE (BOM)
+            else
+                break;
+        }
+        for (q = p; ((*q != '\0') && (*q != '\r') && (*q != '\n')); ++q) {
+        };
+        if (q == p)
+            break;
+        if (*q != '\0')
+            *(q++) = '\0';
+        process_line(track, p);
+        if (*q == '\0')
+            break;
+        p = q;
+    }
+    return 0;
 }
 
 /**
@@ -733,16 +812,16 @@ static int process_text(ass_track_t* track, char* str)
  * \param data string to parse
  * \param size length of data
 */
-void ass_process_data(ass_track_t* track, char* data, int size)
+void ass_process_data(ASS_Track *track, char *data, int size)
 {
-       char* str = malloc(size + 1);
+    char *str = malloc(size + 1);
 
-       memcpy(str, data, size);
-       str[size] = '\0';
+    memcpy(str, data, size);
+    str[size] = '\0';
 
-       mp_msg(MSGT_ASS, MSGL_V, "event: %s\n", str);
-       process_text(track, str);
-       free(str);
+    ass_msg(track->library, MSGL_V, "Event: %s", str);
+    process_text(track, str);
+    free(str);
 }
 
 /**
@@ -752,30 +831,25 @@ void ass_process_data(ass_track_t* track, char* data, int size)
  * \param size length of data
  CodecPrivate section contains [Stream Info] and [V4+ Styles] ([V4 Styles] for SSA) sections
 */
-void ass_process_codec_private(ass_track_t* track, char *data, int size)
+void ass_process_codec_private(ASS_Track *track, char *data, int size)
 {
-       ass_process_data(track, data, size);
-
-       if (!track->event_format) {
-               // probably an mkv produced by ancient mkvtoolnix
-               // such files don't have [Events] and Format: headers
-               track->parser_priv->state = PST_EVENTS;
-               if (track->track_type == TRACK_TYPE_SSA)
-                       track->event_format = strdup("Format: Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text");
-               else
-                       track->event_format = strdup("Format: Layer, Start, End, Style, Actor, MarginL, MarginR, MarginV, Effect, Text");
-       }
-
-       process_force_style(track);
+    ass_process_data(track, data, size);
+
+    // probably an mkv produced by ancient mkvtoolnix
+    // such files don't have [Events] and Format: headers
+    if (!track->event_format)
+        event_format_fallback(track);
+
+    ass_process_force_style(track);
 }
 
-static int check_duplicate_event(ass_track_t* track, int ReadOrder)
+static int check_duplicate_event(ASS_Track *track, int ReadOrder)
 {
-       int i;
-       for (i = 0; i<track->n_events - 1; ++i) // ignoring last event, it is the one we are comparing with
-               if (track->events[i].ReadOrder == ReadOrder)
-                       return 1;
-       return 0;
+    int i;
+    for (i = 0; i < track->n_events - 1; ++i)   // ignoring last event, it is the one we are comparing with
+        if (track->events[i].ReadOrder == ReadOrder)
+            return 1;
+    return 0;
 }
 
 /**
@@ -786,51 +860,53 @@ static int check_duplicate_event(ass_track_t* track, int ReadOrder)
  * \param timecode starting time of the event (milliseconds)
  * \param duration duration of the event (milliseconds)
 */
-void ass_process_chunk(ass_track_t* track, char *data, int size, long long timecode, long long duration)
+void ass_process_chunk(ASS_Track *track, char *data, int size,
+                       long long timecode, long long duration)
 {
-       char* str;
-       int eid;
-       char* p;
-       char* token;
-       ass_event_t* event;
-
-       if (!track->event_format) {
-               mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_EventFormatHeaderMissing);
-               return;
-       }
-
-       str = malloc(size + 1);
-       memcpy(str, data, size);
-       str[size] = '\0';
-       mp_msg(MSGT_ASS, MSGL_V, "event at %" PRId64 ", +%" PRId64 ": %s  \n", (int64_t)timecode, (int64_t)duration, str);
-
-       eid = ass_alloc_event(track);
-       event = track->events + eid;
-
-       p = str;
-
-       do {
-               NEXT(p, token);
-               event->ReadOrder = atoi(token);
-               if (check_duplicate_event(track, event->ReadOrder))
-                       break;
-
-               NEXT(p, token);
-               event->Layer = atoi(token);
-
-               process_event_tail(track, event, p, 3);
-
-               event->Start = timecode;
-               event->Duration = duration;
-
-               free(str);
-               return;
-//             dump_events(tid);
-       } while (0);
-       // some error
-       ass_free_event(track, eid);
-       track->n_events--;
-       free(str);
+    char *str;
+    int eid;
+    char *p;
+    char *token;
+    ASS_Event *event;
+
+    if (!track->event_format) {
+        ass_msg(track->library, MSGL_WARN, "Event format header missing");
+        return;
+    }
+
+    str = malloc(size + 1);
+    memcpy(str, data, size);
+    str[size] = '\0';
+    ass_msg(track->library, MSGL_V, "Event at %" PRId64 ", +%" PRId64 ": %s",
+           (int64_t) timecode, (int64_t) duration, str);
+
+    eid = ass_alloc_event(track);
+    event = track->events + eid;
+
+    p = str;
+
+    do {
+        NEXT(p, token);
+        event->ReadOrder = atoi(token);
+        if (check_duplicate_event(track, event->ReadOrder))
+            break;
+
+        NEXT(p, token);
+        event->Layer = atoi(token);
+
+        process_event_tail(track, event, p, 3);
+
+        event->Start = timecode;
+        event->Duration = duration;
+
+        free(str);
+        return;
+//              dump_events(tid);
+    } while (0);
+    // some error
+    ass_free_event(track, eid);
+    track->n_events--;
+    free(str);
 }
 
 #ifdef CONFIG_ICONV
@@ -840,75 +916,78 @@ void ass_process_chunk(ass_track_t* track, char *data, int size, long long timec
  * \param size buffer size
  * \return a pointer to recoded buffer, caller is responsible for freeing it
 **/
-static char* sub_recode(char* data, size_t size, char* codepage)
+static char *sub_recode(ASS_Library *library, char *data, size_t size,
+                        char *codepage)
 {
-       static iconv_t icdsc = (iconv_t)(-1);
-       char* tocp = "UTF-8";
-       char* outbuf;
-       assert(codepage);
+    iconv_t icdsc;
+    char *tocp = "UTF-8";
+    char *outbuf;
+    assert(codepage);
 
-       {
-               const char* cp_tmp = codepage;
+    {
+        const char *cp_tmp = codepage;
 #ifdef CONFIG_ENCA
-               char enca_lang[3], enca_fallback[100];
-               if (sscanf(codepage, "enca:%2s:%99s", enca_lang, enca_fallback) == 2
-                               || sscanf(codepage, "ENCA:%2s:%99s", enca_lang, enca_fallback) == 2) {
-                       cp_tmp = guess_buffer_cp((unsigned char*)data, size, enca_lang, enca_fallback);
-               }
+        char enca_lang[3], enca_fallback[100];
+        if (sscanf(codepage, "enca:%2s:%99s", enca_lang, enca_fallback) == 2
+            || sscanf(codepage, "ENCA:%2s:%99s", enca_lang,
+                      enca_fallback) == 2) {
+            cp_tmp =
+                ass_guess_buffer_cp(library, (unsigned char *) data, size,
+                                    enca_lang, enca_fallback);
+        }
 #endif
-               if ((icdsc = iconv_open (tocp, cp_tmp)) != (iconv_t)(-1)){
-                       mp_msg(MSGT_ASS,MSGL_V,"LIBSUB: opened iconv descriptor.\n");
-               } else
-                       mp_msg(MSGT_ASS,MSGL_ERR,MSGTR_LIBASS_ErrorOpeningIconvDescriptor);
-       }
-
-       {
-               size_t osize = size;
-               size_t ileft = size;
-               size_t oleft = size - 1;
-               char* ip;
-               char* op;
-               size_t rc;
-               int clear = 0;
-
-               outbuf = malloc(osize);
-               ip = data;
-               op = outbuf;
-
-               while (1) {
-                       if (ileft)
-                               rc = iconv(icdsc, &ip, &ileft, &op, &oleft);
-                       else {// clear the conversion state and leave
-                               clear = 1;
-                               rc = iconv(icdsc, NULL, NULL, &op, &oleft);
-                       }
-                       if (rc == (size_t)(-1)) {
-                               if (errno == E2BIG) {
-                                       size_t offset = op - outbuf;
-                                       outbuf = (char*)realloc(outbuf, osize + size);
-                                       op = outbuf + offset;
-                                       osize += size;
-                                       oleft += size;
-                               } else {
-                                       mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_ErrorRecodingFile);
-                                       return NULL;
-                               }
-                       } else
-                               if (clear)
-                                       break;
-               }
-               outbuf[osize - oleft - 1] = 0;
-       }
-
-       if (icdsc != (iconv_t)(-1)) {
-               (void)iconv_close(icdsc);
-               icdsc = (iconv_t)(-1);
-               mp_msg(MSGT_ASS,MSGL_V,"LIBSUB: closed iconv descriptor.\n");
-       }
-
-       return outbuf;
+        if ((icdsc = iconv_open(tocp, cp_tmp)) != (iconv_t) (-1)) {
+            ass_msg(library, MSGL_V, "Opened iconv descriptor");
+        } else
+            ass_msg(library, MSGL_ERR, "Error opening iconv descriptor");
+    }
+
+    {
+        size_t osize = size;
+        size_t ileft = size;
+        size_t oleft = size - 1;
+        char *ip;
+        char *op;
+        size_t rc;
+        int clear = 0;
+
+        outbuf = malloc(osize);
+        ip = data;
+        op = outbuf;
+
+        while (1) {
+            if (ileft)
+                rc = iconv(icdsc, &ip, &ileft, &op, &oleft);
+            else {              // clear the conversion state and leave
+                clear = 1;
+                rc = iconv(icdsc, NULL, NULL, &op, &oleft);
+            }
+            if (rc == (size_t) (-1)) {
+                if (errno == E2BIG) {
+                    size_t offset = op - outbuf;
+                    outbuf = (char *) realloc(outbuf, osize + size);
+                    op = outbuf + offset;
+                    osize += size;
+                    oleft += size;
+                } else {
+                    ass_msg(library, MSGL_WARN, "Error recoding file");
+                    return NULL;
+                }
+            } else if (clear)
+                break;
+        }
+        outbuf[osize - oleft - 1] = 0;
+    }
+
+    if (icdsc != (iconv_t) (-1)) {
+        (void) iconv_close(icdsc);
+        icdsc = (iconv_t) (-1);
+        ass_msg(library, MSGL_V, "Closed iconv descriptor");
+    }
+
+    return outbuf;
 }
-#endif // ICONV
+#endif                          // ICONV
 
 /**
  * \brief read file contents into newly allocated buffer
@@ -916,86 +995,91 @@ static char* sub_recode(char* data, size_t size, char* codepage)
  * \param bufsize out: file size
  * \return pointer to file contents. Caller is responsible for its deallocation.
  */
-static char* read_file(char* fname, size_t *bufsize)
+static char *read_file(ASS_Library *library, char *fname, size_t *bufsize)
 {
-       int res;
-       long sz;
-       long bytes_read;
-       char* buf;
-
-       FILE* fp = fopen(fname, "rb");
-       if (!fp) {
-               mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FopenFailed, fname);
-               return 0;
-       }
-       res = fseek(fp, 0, SEEK_END);
-       if (res == -1) {
-               mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FseekFailed, fname);
-               fclose(fp);
-               return 0;
-       }
-
-       sz = ftell(fp);
-       rewind(fp);
-
-       if (sz > 10*1024*1024) {
-               mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_RefusingToLoadSubtitlesLargerThan10M, fname);
-               fclose(fp);
-               return 0;
-       }
-
-       mp_msg(MSGT_ASS, MSGL_V, "file size: %ld\n", sz);
-
-       buf = malloc(sz + 1);
-       assert(buf);
-       bytes_read = 0;
-       do {
-               res = fread(buf + bytes_read, 1, sz - bytes_read, fp);
-               if (res <= 0) {
-                       mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_ReadFailed, errno, strerror(errno));
-                       fclose(fp);
-                       free(buf);
-                       return 0;
-               }
-               bytes_read += res;
-       } while (sz - bytes_read > 0);
-       buf[sz] = '\0';
-       fclose(fp);
-
-       if (bufsize)
-               *bufsize = sz;
-       return buf;
+    int res;
+    long sz;
+    long bytes_read;
+    char *buf;
+
+    FILE *fp = fopen(fname, "rb");
+    if (!fp) {
+        ass_msg(library, MSGL_WARN,
+                "ass_read_file(%s): fopen failed", fname);
+        return 0;
+    }
+    res = fseek(fp, 0, SEEK_END);
+    if (res == -1) {
+        ass_msg(library, MSGL_WARN,
+                "ass_read_file(%s): fseek failed", fname);
+        fclose(fp);
+        return 0;
+    }
+
+    sz = ftell(fp);
+    rewind(fp);
+
+    if (sz > 10 * 1024 * 1024) {
+        ass_msg(library, MSGL_INFO,
+               "ass_read_file(%s): Refusing to load subtitles "
+               "larger than 10MiB", fname);
+        fclose(fp);
+        return 0;
+    }
+
+    ass_msg(library, MSGL_V, "File size: %ld", sz);
+
+    buf = malloc(sz + 1);
+    assert(buf);
+    bytes_read = 0;
+    do {
+        res = fread(buf + bytes_read, 1, sz - bytes_read, fp);
+        if (res <= 0) {
+            ass_msg(library, MSGL_INFO, "Read failed, %d: %s", errno,
+                    strerror(errno));
+            fclose(fp);
+            free(buf);
+            return 0;
+        }
+        bytes_read += res;
+    } while (sz - bytes_read > 0);
+    buf[sz] = '\0';
+    fclose(fp);
+
+    if (bufsize)
+        *bufsize = sz;
+    return buf;
 }
 
 /*
  * \param buf pointer to subtitle text in utf-8
  */
-static ass_track_t* parse_memory(ass_library_t* library, char* buf)
+static ASS_Track *parse_memory(ASS_Library *library, char *buf)
 {
-       ass_track_t* track;
-       int i;
+    ASS_Track *track;
+    int i;
 
-       track = ass_new_track(library);
+    track = ass_new_track(library);
 
-       // process header
-       process_text(track, buf);
+    // process header
+    process_text(track, buf);
 
-       // external SSA/ASS subs does not have ReadOrder field
-       for (i = 0; i < track->n_events; ++i)
-               track->events[i].ReadOrder = i;
+    // external SSA/ASS subs does not have ReadOrder field
+    for (i = 0; i < track->n_events; ++i)
+        track->events[i].ReadOrder = i;
 
-       // there is no explicit end-of-font marker in ssa/ass
-       if (track->parser_priv->fontname)
-               decode_font(track);
+    // there is no explicit end-of-font marker in ssa/ass
+    if (track->parser_priv->fontname)
+        decode_font(track);
 
-       if (track->track_type == TRACK_TYPE_UNKNOWN) {
-               ass_free_track(track);
-               return 0;
-       }
+    if (track->track_type == TRACK_TYPE_UNKNOWN) {
+        ass_free_track(track);
+        return 0;
+    }
 
-       process_force_style(track);
+    ass_process_force_style(track);
 
-       return track;
+    return track;
 }
 
 /**
@@ -1006,51 +1090,56 @@ static ass_track_t* parse_memory(ass_library_t* library, char* buf)
  * \param codepage recode buffer contents from given codepage
  * \return newly allocated track
 */
-ass_track_t* ass_read_memory(ass_library_t* library, char* buf, size_t bufsize, char* codepage)
+ASS_Track *ass_read_memory(ASS_Library *library, char *buf,
+                           size_t bufsize, char *codepage)
 {
-       ass_track_t* track;
-       int need_free = 0;
+    ASS_Track *track;
+    int need_free = 0;
 
-       if (!buf)
-               return 0;
+    if (!buf)
+        return 0;
 
 #ifdef CONFIG_ICONV
-       if (codepage)
-               buf = sub_recode(buf, bufsize, codepage);
-       if (!buf)
-               return 0;
-       else
-               need_free = 1;
+    if (codepage) {
+        buf = sub_recode(library, buf, bufsize, codepage);
+        if (!buf)
+            return 0;
+        else
+            need_free = 1;
+    }
 #endif
-       track = parse_memory(library, buf);
-       if (need_free)
-               free(buf);
-       if (!track)
-               return 0;
-
-       mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_AddedSubtitleFileMemory, track->n_styles, track->n_events);
-       return track;
+    track = parse_memory(library, buf);
+    if (need_free)
+        free(buf);
+    if (!track)
+        return 0;
+
+    ass_msg(library, MSGL_INFO, "Added subtitle file: "
+            "<memory> (%d styles, %d events)",
+            track->n_styles, track->n_events);
+    return track;
 }
 
-char* read_file_recode(char* fname, char* codepage, size_t* size)
+static char *read_file_recode(ASS_Library *library, char *fname,
+                              char *codepage, size_t *size)
 {
-       char* buf;
-       size_t bufsize;
+    char *buf;
+    size_t bufsize;
 
-       buf = read_file(fname, &bufsize);
-       if (!buf)
-               return 0;
+    buf = read_file(library, fname, &bufsize);
+    if (!buf)
+        return 0;
 #ifdef CONFIG_ICONV
-       if (codepage) {
-                char* tmpbuf = sub_recode(buf, bufsize, codepage);
-                free(buf);
-                buf = tmpbuf;
-       }
-       if (!buf)
-               return 0;
+    if (codepage) {
+        char *tmpbuf = sub_recode(library, buf, bufsize, codepage);
+        free(buf);
+        buf = tmpbuf;
+    }
+    if (!buf)
+        return 0;
 #endif
-       *size = bufsize;
-       return buf;
+    *size = bufsize;
+    return buf;
 }
 
 /**
@@ -1060,83 +1149,98 @@ char* read_file_recode(char* fname, char* codepage, size_t* size)
  * \param codepage recode buffer contents from given codepage
  * \return newly allocated track
 */
-ass_track_t* ass_read_file(ass_library_t* library, char* fname, char* codepage)
+ASS_Track *ass_read_file(ASS_Library *library, char *fname,
+                         char *codepage)
 {
-       char* buf;
-       ass_track_t* track;
-       size_t bufsize;
+    char *buf;
+    ASS_Track *track;
+    size_t bufsize;
 
-       buf = read_file_recode(fname, codepage, &bufsize);
-       if (!buf)
-               return 0;
-       track = parse_memory(library, buf);
-       free(buf);
-       if (!track)
-               return 0;
+    buf = read_file_recode(library, fname, codepage, &bufsize);
+    if (!buf)
+        return 0;
+    track = parse_memory(library, buf);
+    free(buf);
+    if (!track)
+        return 0;
 
-       track->name = strdup(fname);
+    track->name = strdup(fname);
 
-       mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_AddedSubtitleFileFname, fname, track->n_styles, track->n_events);
+    ass_msg(library, MSGL_INFO,
+            "Added subtitle file: '%s' (%d styles, %d events)",
+            fname, track->n_styles, track->n_events);
 
-//     dump_events(forced_tid);
-       return track;
+    return track;
 }
 
 /**
  * \brief read styles from file into already initialized track
  */
-int ass_read_styles(ass_track_t* track, char* fname, char* codepage)
+int ass_read_styles(ASS_Track *track, char *fname, char *codepage)
 {
-       char* buf;
-       parser_state_t old_state;
-       size_t sz;
+    char *buf;
+    ParserState old_state;
+    size_t sz;
 
-       buf = read_file(fname, &sz);
-       if (!buf)
-               return 1;
+    buf = read_file(track->library, fname, &sz);
+    if (!buf)
+        return 1;
 #ifdef CONFIG_ICONV
-       if (codepage) {
-               char* tmpbuf;
-               tmpbuf = sub_recode(buf, sz, codepage);
-               free(buf);
-               buf = tmpbuf;
-       }
-       if (!buf)
-               return 0;
+    if (codepage) {
+        char *tmpbuf;
+        tmpbuf = sub_recode(track->library, buf, sz, codepage);
+        free(buf);
+        buf = tmpbuf;
+    }
+    if (!buf)
+        return 0;
 #endif
 
-       old_state = track->parser_priv->state;
-       track->parser_priv->state = PST_STYLES;
-       process_text(track, buf);
-       track->parser_priv->state = old_state;
+    old_state = track->parser_priv->state;
+    track->parser_priv->state = PST_STYLES;
+    process_text(track, buf);
+    track->parser_priv->state = old_state;
 
-       return 0;
+    return 0;
 }
 
-long long ass_step_sub(ass_track_t* track, long long now, int movement) {
-       int i;
-
-       if (movement == 0) return 0;
-       if (track->n_events == 0) return 0;
-
-       if (movement < 0)
-               for (i = 0; (i < track->n_events) && ((long long)(track->events[i].Start + track->events[i].Duration) <= now); ++i) {}
-       else
-               for (i = track->n_events - 1; (i >= 0) && ((long long)(track->events[i].Start) > now); --i) {}
-
-       // -1 and n_events are ok
-       assert(i >= -1); assert(i <= track->n_events);
-       i += movement;
-       if (i < 0) i = 0;
-       if (i >= track->n_events) i = track->n_events - 1;
-       return ((long long)track->events[i].Start) - now;
+long long ass_step_sub(ASS_Track *track, long long now, int movement)
+{
+    int i;
+
+    if (movement == 0)
+        return 0;
+    if (track->n_events == 0)
+        return 0;
+
+    if (movement < 0)
+        for (i = 0;
+             (i < track->n_events)
+             &&
+             ((long long) (track->events[i].Start +
+                           track->events[i].Duration) <= now); ++i) {
+    } else
+        for (i = track->n_events - 1;
+             (i >= 0) && ((long long) (track->events[i].Start) > now);
+             --i) {
+        }
+
+    // -1 and n_events are ok
+    assert(i >= -1);
+    assert(i <= track->n_events);
+    i += movement;
+    if (i < 0)
+        i = 0;
+    if (i >= track->n_events)
+        i = track->n_events - 1;
+    return ((long long) track->events[i].Start) - now;
 }
 
-ass_track_t* ass_new_track(ass_library_t* library) {
-       ass_track_t* track = calloc(1, sizeof(ass_track_t));
-       track->library = library;
-       track->ScaledBorderAndShadow = 1;
-       track->parser_priv = calloc(1, sizeof(parser_priv_t));
-       return track;
+ASS_Track *ass_new_track(ASS_Library *library)
+{
+    ASS_Track *track = calloc(1, sizeof(ASS_Track));
+    track->library = library;
+    track->ScaledBorderAndShadow = 1;
+    track->parser_priv = calloc(1, sizeof(ASS_ParserPriv));
+    return track;
 }
-
index 12f16fe..e7674a7 100644 (file)
@@ -1,5 +1,3 @@
-// -*- c-basic-offset: 8; indent-tabs-mode: t -*-
-// vim:ts=8:sw=8:noet:ai:
 /*
  * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
  *
 #define LIBASS_ASS_H
 
 #include <stdio.h>
+#include <stdarg.h>
 #include "ass_types.h"
 
-/// Libass renderer object. Contents are private.
-typedef struct ass_renderer_s ass_renderer_t;
+#define LIBASS_VERSION 0x00908000
 
-/// a linked list of images produced by ass renderer
-typedef struct ass_image_s {
-       int w, h; // bitmap width/height
-       int stride; // bitmap stride
-       unsigned char* bitmap; // 1bpp stride*h alpha buffer
-                              // Actual bitmap size may be as low as
-                              // stride * (h-1) + w
-       uint32_t color; // RGBA
-       int dst_x, dst_y; // bitmap placement inside the video frame
-
-       struct ass_image_s* next; // linked list
-} ass_image_t;
+/*
+ * A linked list of images produced by an ass renderer.
+ *
+ * These images have to be rendered in-order for the correct screen
+ * composition.  The libass renderer clips these bitmaps to the frame size.
+ * w/h can be zero, in this case the bitmap should not be rendered at all.
+ * The last bitmap row is not guaranteed to be padded up to stride size,
+ * e.g. in the worst case a bitmap has the size stride * (h - 1) + w.
+ */
+typedef struct ass_image {
+    int w, h;                   // Bitmap width/height
+    int stride;                 // Bitmap stride
+    unsigned char *bitmap;      // 1bpp stride*h alpha buffer
+                                // Note: the last row may not be padded to
+                                // bitmap stride!
+    uint32_t color;             // Bitmap color and alpha, RGBA
+    int dst_x, dst_y;           // Bitmap placement inside the video frame
+
+    struct ass_image *next;   // Next image, or NULL
+} ASS_Image;
 
-/// Hinting type
-typedef enum {ASS_HINTING_NONE = 0,
-             ASS_HINTING_LIGHT,
-             ASS_HINTING_NORMAL,
-             ASS_HINTING_NATIVE
-} ass_hinting_t;
+/*
+ * Hinting type. (see ass_set_hinting below)
+ *
+ * FreeType's native hinter is still buggy sometimes and it is recommended
+ * to use the light autohinter, ASS_HINTING_LIGHT, instead.  For best
+ * compatibility with problematic fonts, disable hinting.
+ */
+typedef enum {
+    ASS_HINTING_NONE = 0,
+    ASS_HINTING_LIGHT,
+    ASS_HINTING_NORMAL,
+    ASS_HINTING_NATIVE
+} ASS_Hinting;
 
 /**
- * \brief initialize the library
+ * \brief Initialize the library.
  * \return library handle or NULL if failed
  */
-ass_library_t* ass_library_init(void);
+ASS_Library *ass_library_init(void);
 
 /**
- * \brief finalize the library
+ * \brief Finalize the library
  * \param priv library handle
  */
-void ass_library_done(ass_library_t*);
+void ass_library_done(ASS_Library *priv);
 
 /**
- * \brief set private font directory
+ * \brief Set private font directory.
  * It is used for saving embedded fonts and also in font lookup.
+ *
+ * \param priv library handle
+ * \param fonts_dir private directory for font extraction
  */
-void ass_set_fonts_dir(ass_library_t* priv, const char* fonts_dir);
+void ass_set_fonts_dir(ASS_Library *priv, const char *fonts_dir);
 
-void ass_set_extract_fonts(ass_library_t* priv, int extract);
+/**
+ * \brief Whether fonts should be extracted from track data.
+ * \param priv library handle
+ * \param extract whether to extract fonts
+ */
+void ass_set_extract_fonts(ASS_Library *priv, int extract);
 
-void ass_set_style_overrides(ass_library_t* priv, char** list);
+/**
+ * \brief Register style overrides with a library instance.
+ * The overrides should have the form [Style.]Param=Value, e.g.
+ *   SomeStyle.Font=Arial
+ *   ScaledBorderAndShadow=yes
+ *
+ * \param priv library handle
+ * \param list NULL-terminated list of strings
+ */
+void ass_set_style_overrides(ASS_Library *priv, char **list);
 
 /**
- * \brief initialize the renderer
+ * \brief Explicitly process style overrides for a track.
+ * \param track track handle
+ */
+void ass_process_force_style(ASS_Track *track);
+
+/**
+ * \brief Register a callback for debug/info messages.
+ * If a callback is registered, it is called for every message emitted by
+ * libass.  The callback receives a format string and a list of arguments,
+ * to be used for the printf family of functions. Additionally, a log level
+ * from 0 (FATAL errors) to 7 (verbose DEBUG) is passed.  Usually, level 5
+ * should be used by applications.
+ * If no callback is set, all messages level < 5 are printed to stderr,
+ * prefixed with [ass].
+ *
+ * \param priv library handle
+ * \param msg_cb pointer to callback function
+ * \param data additional data, will be passed to callback
+ */
+void ass_set_message_cb(ASS_Library *priv, void (*msg_cb)
+                        (int level, const char *fmt, va_list args, void *data),
+                        void *data);
+
+/**
+ * \brief Initialize the renderer.
  * \param priv library handle
  * \return renderer handle or NULL if failed
  */
-ass_renderer_t* ass_renderer_init(ass_library_t*);
+ASS_Renderer *ass_renderer_init(ASS_Library *);
+
+/**
+ * \brief Finalize the renderer.
+ * \param priv renderer handle
+ */
+void ass_renderer_done(ASS_Renderer *priv);
 
 /**
- * \brief finalize the renderer
+ * \brief Set the frame size in pixels, including margins.
  * \param priv renderer handle
+ * \param w width
+ * \param h height
  */
-void ass_renderer_done(ass_renderer_t* priv);
+void ass_set_frame_size(ASS_Renderer *priv, int w, int h);
 
-void ass_set_frame_size(ass_renderer_t* priv, int w, int h);
-void ass_set_margins(ass_renderer_t* priv, int t, int b, int l, int r);
-void ass_set_use_margins(ass_renderer_t* priv, int use);
-void ass_set_aspect_ratio(ass_renderer_t* priv, double ar);
-void ass_set_font_scale(ass_renderer_t* priv, double font_scale);
-void ass_set_hinting(ass_renderer_t* priv, ass_hinting_t ht);
-void ass_set_line_spacing(ass_renderer_t* priv, double line_spacing);
+/**
+ * \brief Set frame margins.  These values may be negative if pan-and-scan
+ * is used.
+ * \param priv renderer handle
+ * \param t top margin
+ * \param b bottom margin
+ * \param l left margin
+ * \param r right margin
+ */
+void ass_set_margins(ASS_Renderer *priv, int t, int b, int l, int r);
 
 /**
- * \brief set font lookup defaults
+ * \brief Whether margins should be used for placing regular events.
+ * \param priv renderer handle
+ * \param use whether to use the margins
  */
-int  ass_set_fonts(ass_renderer_t* priv, const char* default_font, const char* default_family);
+void ass_set_use_margins(ASS_Renderer *priv, int use);
 
 /**
- * \brief set font lookup defaults, don't use fontconfig even if it is available
+ * \brief Set aspect ratio parameters.
+ * \param priv renderer handle
+ * \param dar display aspect ratio (DAR), prescaled for output PAR
+ * \param sar storage aspect ratio (SAR)
  */
-int  ass_set_fonts_nofc(ass_renderer_t* priv, const char* default_font, const char* default_family);
+void ass_set_aspect_ratio(ASS_Renderer *priv, double dar, double sar);
 
 /**
- * \brief render a frame, producing a list of ass_image_t
- * \param priv library
+ * \brief Set a fixed font scaling factor.
+ * \param priv renderer handle
+ * \param font_scale scaling factor, default is 1.0
+ */
+void ass_set_font_scale(ASS_Renderer *priv, double font_scale);
+
+/**
+ * \brief Set font hinting method.
+ * \param priv renderer handle
+ * \param ht hinting method
+ */
+void ass_set_hinting(ASS_Renderer *priv, ASS_Hinting ht);
+
+/**
+ * \brief Set line spacing. Will not be scaled with frame size.
+ * \param priv renderer handle
+ * \param line_spacing line spacing in pixels
+ */
+void ass_set_line_spacing(ASS_Renderer *priv, double line_spacing);
+
+/**
+ * \brief Set font lookup defaults.
+ * \param default_font path to default font to use. Must be supplied if
+ * fontconfig is disabled or unavailable.
+ * \param default_family fallback font family for fontconfig, or NULL
+ * \param fc whether to use fontconfig
+ * \param config path to fontconfig configuration file, or NULL.  Only relevant
+ * if fontconfig is used.
+ * \param update whether fontconfig cache should be built/updated now.  Only
+ * relevant if fontconfig is used.
+ */
+void ass_set_fonts(ASS_Renderer *priv, const char *default_font,
+                   const char *default_family, int fc, const char *config,
+                   int update);
+
+/**
+ * \brief Update/build font cache.  This needs to be called if it was
+ * disabled when ass_set_fonts was set.
+ *
+ * \param priv renderer handle
+ * \return success
+ */
+int ass_fonts_update(ASS_Renderer *priv);
+
+/**
+ * \brief Set hard cache limits.  Do not set, or set to zero, for reasonable
+ * defaults.
+ *
+ * \param priv renderer handle
+ * \param glyph_max maximum number of cached glyphs
+ * \param bitmap_max_size maximum bitmap cache size (in MB)
+ */
+void ass_set_cache_limits(ASS_Renderer *priv, int glyph_max,
+                          int bitmap_max_size);
+
+/**
+ * \brief Render a frame, producing a list of ASS_Image.
+ * \param priv renderer handle
  * \param track subtitle track
  * \param now video timestamp in milliseconds
+ * \param detect_change will be set to 1 if a change occured compared
+ * to the last invocation
  */
-ass_image_t* ass_render_frame(ass_renderer_t *priv, ass_track_t* track, long long now, int* detect_change);
+ASS_Image *ass_render_frame(ASS_Renderer *priv, ASS_Track *track,
+                            long long now, int *detect_change);
 
 
-// The following functions operate on track objects and do not need an ass_renderer //
+/*
+ * The following functions operate on track objects and do not need
+ * an ass_renderer
+ */
 
 /**
- * \brief allocate a new empty track object
+ * \brief Allocate a new empty track object.
+ * \param library handle
  * \return pointer to empty track
  */
-ass_track_t* ass_new_track(ass_library_t*);
+ASS_Track *ass_new_track(ASS_Library *);
 
 /**
- * \brief deallocate track and all its child objects (styles and events)
+ * \brief Deallocate track and all its child objects (styles and events).
  * \param track track to deallocate
  */
-void ass_free_track(ass_track_t* track);
+void ass_free_track(ASS_Track *track);
 
 /**
- * \brief allocate new style
+ * \brief Allocate new style.
  * \param track track
  * \return newly allocated style id
  */
-int ass_alloc_style(ass_track_t* track);
+int ass_alloc_style(ASS_Track *track);
 
 /**
- * \brief allocate new event
+ * \brief Allocate new event.
  * \param track track
  * \return newly allocated event id
  */
-int ass_alloc_event(ass_track_t* track);
+int ass_alloc_event(ASS_Track *track);
 
 /**
- * \brief delete a style
+ * \brief Delete a style.
  * \param track track
  * \param sid style id
  * Deallocates style data. Does not modify track->n_styles.
  */
-void ass_free_style(ass_track_t* track, int sid);
+void ass_free_style(ASS_Track *track, int sid);
 
 /**
- * \brief delete an event
+ * \brief Delete an event.
  * \param track track
  * \param eid event id
  * Deallocates event data. Does not modify track->n_events.
  */
-void ass_free_event(ass_track_t* track, int eid);
+void ass_free_event(ASS_Track *track, int eid);
 
 /**
  * \brief Parse a chunk of subtitle stream data.
@@ -161,71 +294,81 @@ void ass_free_event(ass_track_t* track, int eid);
  * \param data string to parse
  * \param size length of data
  */
-void ass_process_data(ass_track_t* track, char* data, int size);
+void ass_process_data(ASS_Track *track, char *data, int size);
 
 /**
- * \brief Parse Codec Private section of subtitle stream
+ * \brief Parse Codec Private section of subtitle stream.
  * \param track target track
  * \param data string to parse
  * \param size length of data
  */
-void ass_process_codec_private(ass_track_t* track, char *data, int size);
+void ass_process_codec_private(ASS_Track *track, char *data, int size);
 
 /**
- * \brief Parse a chunk of subtitle stream data. In Matroska, this contains exactly 1 event (or a commentary).
+ * \brief Parse a chunk of subtitle stream data. In Matroska,
+ * this contains exactly 1 event (or a commentary).
  * \param track track
  * \param data string to parse
  * \param size length of data
  * \param timecode starting time of the event (milliseconds)
  * \param duration duration of the event (milliseconds)
-*/
-void ass_process_chunk(ass_track_t* track, char *data, int size, long long timecode, long long duration);
-
-char* read_file_recode(char* fname, char* codepage, size_t* size);
+ */
+void ass_process_chunk(ASS_Track *track, char *data, int size,
+                       long long timecode, long long duration);
 
 /**
  * \brief Read subtitles from file.
+ * \param library library handle
  * \param fname file name
+ * \param codepage encoding (iconv format)
  * \return newly allocated track
 */
-ass_track_t* ass_read_file(ass_library_t* library, char* fname, char* codepage);
+ASS_Track *ass_read_file(ASS_Library *library, char *fname,
+                         char *codepage);
 
 /**
  * \brief Read subtitles from memory.
- * \param library libass library object
+ * \param library library handle
  * \param buf pointer to subtitles text
  * \param bufsize size of buffer
- * \param codepage recode buffer contents from given codepage
+ * \param codepage encoding (iconv format)
  * \return newly allocated track
 */
-ass_track_t* ass_read_memory(ass_library_t* library, char* buf, size_t bufsize, char* codepage);
+ASS_Track *ass_read_memory(ASS_Library *library, char *buf,
+                           size_t bufsize, char *codepage);
 /**
- * \brief read styles from file into already initialized track
+ * \brief Read styles from file into already initialized track.
+ * \param fname file name
+ * \param codepage encoding (iconv format)
  * \return 0 on success
  */
-int ass_read_styles(ass_track_t* track, char* fname, char* codepage);
+int ass_read_styles(ASS_Track *track, char *fname, char *codepage);
 
 /**
  * \brief Add a memory font.
+ * \param library library handle
  * \param name attachment name
  * \param data binary font data
  * \param data_size data size
 */
-void ass_add_font(ass_library_t* library, char* name, char* data, int data_size);
+void ass_add_font(ASS_Library *library, char *name, char *data,
+                  int data_size);
 
 /**
- * \brief Remove all fonts stored in ass_library object
+ * \brief Remove all fonts stored in an ass_library object.
+ * \param library library handle
  */
-void ass_clear_fonts(ass_library_t* library);
+void ass_clear_fonts(ASS_Library *library);
 
 /**
- * \brief Calculates timeshift from now to the start of some other subtitle event, depending on movement parameter
+ * \brief Calculates timeshift from now to the start of some other subtitle
+ * event, depending on movement parameter.
  * \param track subtitle track
- * \param now current time, ms
+ * \param now current time in milliseconds
  * \param movement how many events to skip from the one currently displayed
  * +2 means "the one after the next", -1 means "previous"
- * \return timeshift, ms
+ * \return timeshift in milliseconds
  */
-long long ass_step_sub(ass_track_t* track, long long now, int movement);
+long long ass_step_sub(ASS_Track *track, long long now, int movement);
 
 #endif /* LIBASS_ASS_H */
index 15d583a..c7c039d 100644 (file)
@@ -1,5 +1,3 @@
-// -*- c-basic-offset: 8; indent-tabs-mode: t -*-
-// vim:ts=8:sw=8:noet:ai:
 /*
  * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
  *
 #include <ft2build.h>
 #include FT_GLYPH_H
 
-#include "mputils.h"
+#include "ass_utils.h"
 #include "ass_bitmap.h"
 
-struct ass_synth_priv_s {
-       int tmp_w, tmp_h;
-       unsigned short* tmp;
+struct ass_synth_priv {
+    int tmp_w, tmp_h;
+    unsigned short *tmp;
 
-       int g_r;
-       int g_w;
+    int g_r;
+    int g_w;
 
-       unsigned *g;
-       unsigned *gt2;
+    unsigned *g;
+    unsigned *gt2;
 
-       double radius;
+    double radius;
 };
 
 static const unsigned int maxcolor = 255;
 static const unsigned base = 256;
 
-static int generate_tables(ass_synth_priv_t* priv, double radius)
+static int generate_tables(ASS_SynthPriv *priv, double radius)
 {
-       double A = log(1.0/base)/(radius*radius*2);
-       int mx, i;
-       double volume_diff, volume_factor = 0;
-       unsigned volume;
-
-       if (priv->radius == radius)
-               return 0;
-       else
-               priv->radius = radius;
-
-       priv->g_r = ceil(radius);
-       priv->g_w = 2*priv->g_r+1;
-
-       if (priv->g_r) {
-               priv->g = realloc(priv->g, priv->g_w * sizeof(unsigned));
-               priv->gt2 = realloc(priv->gt2, 256 * priv->g_w * sizeof(unsigned));
-               if (priv->g==NULL || priv->gt2==NULL) {
-                       return -1;
-               }
-       }
-
-       if (priv->g_r) {
-               // gaussian curve with volume = 256
-               for (volume_diff=10000000; volume_diff>0.0000001; volume_diff*=0.5){
-                       volume_factor+= volume_diff;
-                       volume=0;
-                       for (i = 0; i<priv->g_w; ++i) {
-                               priv->g[i] = (unsigned)(exp(A * (i-priv->g_r)*(i-priv->g_r)) * volume_factor + .5);
-                               volume+= priv->g[i];
-                       }
-                       if(volume>256) volume_factor-= volume_diff;
-               }
-               volume=0;
-               for (i = 0; i<priv->g_w; ++i) {
-                       priv->g[i] = (unsigned)(exp(A * (i-priv->g_r)*(i-priv->g_r)) * volume_factor + .5);
-                       volume+= priv->g[i];
-               }
-
-               // gauss table:
-               for(mx=0;mx<priv->g_w;mx++){
-                       for(i=0;i<256;i++){
-                               priv->gt2[mx+i*priv->g_w] = i*priv->g[mx];
-                       }
-               }
-       }
-
-       return 0;
+    double A = log(1.0 / base) / (radius * radius * 2);
+    int mx, i;
+    double volume_diff, volume_factor = 0;
+    unsigned volume;
+
+    if (priv->radius == radius)
+        return 0;
+    else
+        priv->radius = radius;
+
+    priv->g_r = ceil(radius);
+    priv->g_w = 2 * priv->g_r + 1;
+
+    if (priv->g_r) {
+        priv->g = realloc(priv->g, priv->g_w * sizeof(unsigned));
+        priv->gt2 = realloc(priv->gt2, 256 * priv->g_w * sizeof(unsigned));
+        if (priv->g == NULL || priv->gt2 == NULL) {
+            return -1;
+        }
+    }
+
+    if (priv->g_r) {
+        // gaussian curve with volume = 256
+        for (volume_diff = 10000000; volume_diff > 0.0000001;
+             volume_diff *= 0.5) {
+            volume_factor += volume_diff;
+            volume = 0;
+            for (i = 0; i < priv->g_w; ++i) {
+                priv->g[i] =
+                    (unsigned) (exp(A * (i - priv->g_r) * (i - priv->g_r)) *
+                                volume_factor + .5);
+                volume += priv->g[i];
+            }
+            if (volume > 256)
+                volume_factor -= volume_diff;
+        }
+        volume = 0;
+        for (i = 0; i < priv->g_w; ++i) {
+            priv->g[i] =
+                (unsigned) (exp(A * (i - priv->g_r) * (i - priv->g_r)) *
+                            volume_factor + .5);
+            volume += priv->g[i];
+        }
+
+        // gauss table:
+        for (mx = 0; mx < priv->g_w; mx++) {
+            for (i = 0; i < 256; i++) {
+                priv->gt2[mx + i * priv->g_w] = i * priv->g[mx];
+            }
+        }
+    }
+
+    return 0;
 }
 
-static void resize_tmp(ass_synth_priv_t* priv, int w, int h)
+static void resize_tmp(ASS_SynthPriv *priv, int w, int h)
 {
-       if (priv->tmp_w >= w && priv->tmp_h >= h)
-               return;
-       if (priv->tmp_w == 0)
-               priv->tmp_w = 64;
-       if (priv->tmp_h == 0)
-               priv->tmp_h = 64;
-       while (priv->tmp_w < w) priv->tmp_w *= 2;
-       while (priv->tmp_h < h) priv->tmp_h *= 2;
-       if (priv->tmp)
-               free(priv->tmp);
-       priv->tmp = malloc((priv->tmp_w + 1) * priv->tmp_h * sizeof(short));
+    if (priv->tmp_w >= w && priv->tmp_h >= h)
+        return;
+    if (priv->tmp_w == 0)
+        priv->tmp_w = 64;
+    if (priv->tmp_h == 0)
+        priv->tmp_h = 64;
+    while (priv->tmp_w < w)
+        priv->tmp_w *= 2;
+    while (priv->tmp_h < h)
+        priv->tmp_h *= 2;
+    if (priv->tmp)
+        free(priv->tmp);
+    priv->tmp = malloc((priv->tmp_w + 1) * priv->tmp_h * sizeof(short));
 }
 
-ass_synth_priv_t* ass_synth_init(double radius)
+ASS_SynthPriv *ass_synth_init(double radius)
 {
-       ass_synth_priv_t* priv = calloc(1, sizeof(ass_synth_priv_t));
-       generate_tables(priv, radius);
-       return priv;
+    ASS_SynthPriv *priv = calloc(1, sizeof(ASS_SynthPriv));
+    generate_tables(priv, radius);
+    return priv;
 }
 
-void ass_synth_done(ass_synth_priv_t* priv)
+void ass_synth_done(ASS_SynthPriv *priv)
 {
-       if (priv->tmp)
-               free(priv->tmp);
-       if (priv->g)
-               free(priv->g);
-       if (priv->gt2)
-               free(priv->gt2);
-       free(priv);
+    if (priv->tmp)
+        free(priv->tmp);
+    if (priv->g)
+        free(priv->g);
+    if (priv->gt2)
+        free(priv->gt2);
+    free(priv);
 }
 
-static bitmap_t* alloc_bitmap(int w, int h)
+static Bitmap *alloc_bitmap(int w, int h)
 {
-       bitmap_t* bm;
-       bm = calloc(1, sizeof(bitmap_t));
-       bm->buffer = malloc(w*h);
-       bm->w = w;
-       bm->h = h;
-       bm->left = bm->top = 0;
-       return bm;
+    Bitmap *bm;
+    bm = calloc(1, sizeof(Bitmap));
+    bm->buffer = malloc(w * h);
+    bm->w = w;
+    bm->h = h;
+    bm->left = bm->top = 0;
+    return bm;
 }
 
-void ass_free_bitmap(bitmap_t* bm)
+void ass_free_bitmap(Bitmap *bm)
 {
-       if (bm) {
-               if (bm->buffer) free(bm->buffer);
-               free(bm);
-       }
+    if (bm) {
+        if (bm->buffer)
+            free(bm->buffer);
+        free(bm);
+    }
 }
 
-static bitmap_t* copy_bitmap(const bitmap_t* src)
+static Bitmap *copy_bitmap(const Bitmap *src)
 {
-       bitmap_t* dst = alloc_bitmap(src->w, src->h);
-       dst->left = src->left;
-       dst->top = src->top;
-       memcpy(dst->buffer, src->buffer, src->w * src->h);
-       return dst;
+    Bitmap *dst = alloc_bitmap(src->w, src->h);
+    dst->left = src->left;
+    dst->top = src->top;
+    memcpy(dst->buffer, src->buffer, src->w * src->h);
+    return dst;
 }
 
-static int check_glyph_area(FT_Glyph glyph)
+static int check_glyph_area(ASS_Library *library, FT_Glyph glyph)
 {
-       FT_BBox bbox;
-       long long dx, dy;
-       FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_TRUNCATE, &bbox);
-       dx = bbox.xMax - bbox.xMin;
-       dy = bbox.yMax - bbox.yMin;
-       if (dx * dy > 8000000) {
-               mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_GlyphBBoxTooLarge, (int)dx, (int)dy);
-               return 1;
-       } else
-               return 0;
+    FT_BBox bbox;
+    long long dx, dy;
+    FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_TRUNCATE, &bbox);
+    dx = bbox.xMax - bbox.xMin;
+    dy = bbox.yMax - bbox.yMin;
+    if (dx * dy > 8000000) {
+        ass_msg(library, MSGL_WARN, "Glyph bounding box too large: %dx%dpx",
+               (int) dx, (int) dy);
+        return 1;
+    } else
+        return 0;
 }
 
-static bitmap_t* glyph_to_bitmap_internal(FT_Glyph glyph, int bord)
+static Bitmap *glyph_to_bitmap_internal(ASS_Library *library,
+                                          FT_Glyph glyph, int bord)
 {
-       FT_BitmapGlyph bg;
-       FT_Bitmap* bit;
-       bitmap_t* bm;
-       int w, h;
-       unsigned char* src;
-       unsigned char* dst;
-       int i;
-       int error;
-
-       if (check_glyph_area(glyph))
-               return 0;
-       error = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 0);
-       if (error) {
-               mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FT_Glyph_To_BitmapError, error);
-               return 0;
-       }
-
-       bg = (FT_BitmapGlyph)glyph;
-       bit = &(bg->bitmap);
-       if (bit->pixel_mode != FT_PIXEL_MODE_GRAY) {
-               mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_UnsupportedPixelMode, (int)(bit->pixel_mode));
-               FT_Done_Glyph(glyph);
-               return 0;
-       }
-
-       w = bit->width;
-       h = bit->rows;
-       bm = alloc_bitmap(w + 2*bord, h + 2*bord);
-       memset(bm->buffer, 0, bm->w * bm->h);
-       bm->left = bg->left - bord;
-       bm->top = - bg->top - bord;
-
-       src = bit->buffer;
-       dst = bm->buffer + bord + bm->w * bord;
-       for (i = 0; i < h; ++i) {
-               memcpy(dst, src, w);
-               src += bit->pitch;
-               dst += bm->w;
-       }
-
-       FT_Done_Glyph(glyph);
-       return bm;
+    FT_BitmapGlyph bg;
+    FT_Bitmap *bit;
+    Bitmap *bm;
+    int w, h;
+    unsigned char *src;
+    unsigned char *dst;
+    int i;
+    int error;
+
+    if (check_glyph_area(library, glyph))
+        return 0;
+    error = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 0);
+    if (error) {
+        ass_msg(library, MSGL_WARN, "FT_Glyph_To_Bitmap error %d",
+               error);
+        return 0;
+    }
+
+    bg = (FT_BitmapGlyph) glyph;
+    bit = &(bg->bitmap);
+    if (bit->pixel_mode != FT_PIXEL_MODE_GRAY) {
+        ass_msg(library, MSGL_WARN, "Unsupported pixel mode: %d",
+               (int) (bit->pixel_mode));
+        FT_Done_Glyph(glyph);
+        return 0;
+    }
+
+    w = bit->width;
+    h = bit->rows;
+    bm = alloc_bitmap(w + 2 * bord, h + 2 * bord);
+    memset(bm->buffer, 0, bm->w * bm->h);
+    bm->left = bg->left - bord;
+    bm->top = -bg->top - bord;
+
+    src = bit->buffer;
+    dst = bm->buffer + bord + bm->w * bord;
+    for (i = 0; i < h; ++i) {
+        memcpy(dst, src, w);
+        src += bit->pitch;
+        dst += bm->w;
+    }
+
+    FT_Done_Glyph(glyph);
+    return bm;
 }
 
 /**
- * \brief fix outline bitmap and generate shadow bitmap
- * Two things are done here:
- * 1. Glyph bitmap is subtracted from outline bitmap. This way looks much better in some cases.
- * 2. Shadow bitmap is created as a sum of glyph and outline bitmaps.
+ * \brief fix outline bitmap
+ *
+ * The glyph bitmap is subtracted from outline bitmap. This way looks much
+ * better in some cases.
+ */
+static void fix_outline(Bitmap *bm_g, Bitmap *bm_o)
+{
+    int x, y;
+    const int l = bm_o->left > bm_g->left ? bm_o->left : bm_g->left;
+    const int t = bm_o->top > bm_g->top ? bm_o->top : bm_g->top;
+    const int r =
+        bm_o->left + bm_o->w <
+        bm_g->left + bm_g->w ? bm_o->left + bm_o->w : bm_g->left + bm_g->w;
+    const int b =
+        bm_o->top + bm_o->h <
+        bm_g->top + bm_g->h ? bm_o->top + bm_o->h : bm_g->top + bm_g->h;
+
+    unsigned char *g =
+        bm_g->buffer + (t - bm_g->top) * bm_g->w + (l - bm_g->left);
+    unsigned char *o =
+        bm_o->buffer + (t - bm_o->top) * bm_o->w + (l - bm_o->left);
+
+    for (y = 0; y < b - t; ++y) {
+        for (x = 0; x < r - l; ++x) {
+            unsigned char c_g, c_o;
+            c_g = g[x];
+            c_o = o[x];
+            o[x] = (c_o > c_g) ? c_o - (c_g / 2) : 0;
+        }
+        g += bm_g->w;
+        o += bm_o->w;
+    }
+}
+
+/**
+ * \brief Shift a bitmap by the fraction of a pixel in x and y direction
+ * expressed in 26.6 fixed point
+ */
+static void shift_bitmap(unsigned char *buf, int w, int h, int shift_x,
+                         int shift_y)
+{
+    int x, y, b;
+
+    // Shift in x direction
+    if (shift_x > 0) {
+        for (y = 0; y < h; y++) {
+            for (x = w - 1; x > 0; x--) {
+                b = (buf[x + y * w - 1] * shift_x) >> 6;
+                buf[x + y * w - 1] -= b;
+                buf[x + y * w] += b;
+            }
+        }
+    } else if (shift_x < 0) {
+        shift_x = -shift_x;
+        for (y = 0; y < h; y++) {
+            for (x = 0; x < w - 1; x++) {
+                b = (buf[x + y * w + 1] * shift_x) >> 6;
+                buf[x + y * w + 1] -= b;
+                buf[x + y * w] += b;
+            }
+        }
+    }
+
+    // Shift in y direction
+    if (shift_y > 0) {
+        for (x = 0; x < w; x++) {
+            for (y = h - 1; y > 0; y--) {
+                b = (buf[x + (y - 1) * w] * shift_y) >> 6;
+                buf[x + (y - 1) * w] -= b;
+                buf[x + y * w] += b;
+            }
+        }
+    } else if (shift_y < 0) {
+        shift_y = -shift_y;
+        for (x = 0; x < w; x++) {
+            for (y = 0; y < h - 1; y++) {
+                b = (buf[x + (y + 1) * w] * shift_y) >> 6;
+                buf[x + (y + 1) * w] -= b;
+                buf[x + y * w] += b;
+            }
+        }
+    }
+}
+
+/*
+ * Gaussian blur.  An fast pure C implementation from MPlayer.
  */
-static bitmap_t* fix_outline_and_shadow(bitmap_t* bm_g, bitmap_t* bm_o)
+static void ass_gauss_blur(unsigned char *buffer, unsigned short *tmp2,
+                           int width, int height, int stride, int *m2,
+                           int r, int mwidth)
 {
-       int x, y;
-       const int l = bm_o->left > bm_g->left ? bm_o->left : bm_g->left;
-       const int t = bm_o->top > bm_g->top ? bm_o->top : bm_g->top;
-       const int r = bm_o->left + bm_o->w < bm_g->left + bm_g->w ? bm_o->left + bm_o->w : bm_g->left + bm_g->w;
-       const int b = bm_o->top + bm_o->h < bm_g->top + bm_g->h ? bm_o->top + bm_o->h : bm_g->top + bm_g->h;
-
-       bitmap_t* bm_s = copy_bitmap(bm_o);
-
-       unsigned char* g = bm_g->buffer + (t - bm_g->top) * bm_g->w + (l - bm_g->left);
-       unsigned char* o = bm_o->buffer + (t - bm_o->top) * bm_o->w + (l - bm_o->left);
-       unsigned char* s = bm_s->buffer + (t - bm_s->top) * bm_s->w + (l - bm_s->left);
-
-       for (y = 0; y < b - t; ++y) {
-               for (x = 0; x < r - l; ++x) {
-                       unsigned char c_g, c_o;
-                       c_g = g[x];
-                       c_o = o[x];
-                       o[x] = (c_o > c_g) ? c_o - (c_g/2) : 0;
-                       s[x] = (c_o < 0xFF - c_g) ? c_o + c_g : 0xFF;
-               }
-               g += bm_g->w;
-               o += bm_o->w;
-               s += bm_s->w;
-       }
-
-       assert(bm_s);
-       return bm_s;
+
+    int x, y;
+
+    unsigned char *s = buffer;
+    unsigned short *t = tmp2 + 1;
+    for (y = 0; y < height; y++) {
+        memset(t - 1, 0, (width + 1) * sizeof(short));
+
+        for (x = 0; x < r; x++) {
+            const int src = s[x];
+            if (src) {
+                register unsigned short *dstp = t + x - r;
+                int mx;
+                unsigned *m3 = (unsigned *) (m2 + src * mwidth);
+                for (mx = r - x; mx < mwidth; mx++) {
+                    dstp[mx] += m3[mx];
+                }
+            }
+        }
+
+        for (; x < width - r; x++) {
+            const int src = s[x];
+            if (src) {
+                register unsigned short *dstp = t + x - r;
+                int mx;
+                unsigned *m3 = (unsigned *) (m2 + src * mwidth);
+                for (mx = 0; mx < mwidth; mx++) {
+                    dstp[mx] += m3[mx];
+                }
+            }
+        }
+
+        for (; x < width; x++) {
+            const int src = s[x];
+            if (src) {
+                register unsigned short *dstp = t + x - r;
+                int mx;
+                const int x2 = r + width - x;
+                unsigned *m3 = (unsigned *) (m2 + src * mwidth);
+                for (mx = 0; mx < x2; mx++) {
+                    dstp[mx] += m3[mx];
+                }
+            }
+        }
+
+        s += stride;
+        t += width + 1;
+    }
+
+    t = tmp2;
+    for (x = 0; x < width; x++) {
+        for (y = 0; y < r; y++) {
+            unsigned short *srcp = t + y * (width + 1) + 1;
+            int src = *srcp;
+            if (src) {
+                register unsigned short *dstp = srcp - 1 + width + 1;
+                const int src2 = (src + 128) >> 8;
+                unsigned *m3 = (unsigned *) (m2 + src2 * mwidth);
+
+                int mx;
+                *srcp = 128;
+                for (mx = r - 1; mx < mwidth; mx++) {
+                    *dstp += m3[mx];
+                    dstp += width + 1;
+                }
+            }
+        }
+        for (; y < height - r; y++) {
+            unsigned short *srcp = t + y * (width + 1) + 1;
+            int src = *srcp;
+            if (src) {
+                register unsigned short *dstp = srcp - 1 - r * (width + 1);
+                const int src2 = (src + 128) >> 8;
+                unsigned *m3 = (unsigned *) (m2 + src2 * mwidth);
+
+                int mx;
+                *srcp = 128;
+                for (mx = 0; mx < mwidth; mx++) {
+                    *dstp += m3[mx];
+                    dstp += width + 1;
+                }
+            }
+        }
+        for (; y < height; y++) {
+            unsigned short *srcp = t + y * (width + 1) + 1;
+            int src = *srcp;
+            if (src) {
+                const int y2 = r + height - y;
+                register unsigned short *dstp = srcp - 1 - r * (width + 1);
+                const int src2 = (src + 128) >> 8;
+                unsigned *m3 = (unsigned *) (m2 + src2 * mwidth);
+
+                int mx;
+                *srcp = 128;
+                for (mx = 0; mx < y2; mx++) {
+                    *dstp += m3[mx];
+                    dstp += width + 1;
+                }
+            }
+        }
+        t++;
+    }
+
+    t = tmp2;
+    s = buffer;
+    for (y = 0; y < height; y++) {
+        for (x = 0; x < width; x++) {
+            s[x] = t[x] >> 8;
+        }
+        s += stride;
+        t += width + 1;
+    }
 }
 
 /**
  * \brief Blur with [[1,2,1]. [2,4,2], [1,2,1]] kernel
  * This blur is the same as the one employed by vsfilter.
  */
-static void be_blur(unsigned char *buf, int w, int h) {
-       unsigned int x, y;
-       unsigned int old_sum, new_sum;
-
-       for (y=0; y<h; y++) {
-               old_sum = 2 * buf[y*w];
-               for (x=0; x<w-1; x++) {
-                       new_sum = buf[y*w+x] + buf[y*w+x+1];
-                       buf[y*w+x] = (old_sum + new_sum) >> 2;
-                       old_sum = new_sum;
-               }
-       }
-
-       for (x=0; x<w; x++) {
-               old_sum = 2 * buf[x];
-               for (y=0; y<h-1; y++) {
-                       new_sum = buf[y*w+x] + buf[(y+1)*w+x];
-                       buf[y*w+x] = (old_sum + new_sum) >> 2;
-                       old_sum = new_sum;
-               }
-       }
+static void be_blur(unsigned char *buf, int w, int h)
+{
+    unsigned int x, y;
+    unsigned int old_sum, new_sum;
+
+    for (y = 0; y < h; y++) {
+        old_sum = 2 * buf[y * w];
+        for (x = 0; x < w - 1; x++) {
+            new_sum = buf[y * w + x] + buf[y * w + x + 1];
+            buf[y * w + x] = (old_sum + new_sum) >> 2;
+            old_sum = new_sum;
+        }
+    }
+
+    for (x = 0; x < w; x++) {
+        old_sum = 2 * buf[x];
+        for (y = 0; y < h - 1; y++) {
+            new_sum = buf[y * w + x] + buf[(y + 1) * w + x];
+            buf[y * w + x] = (old_sum + new_sum) >> 2;
+            old_sum = new_sum;
+        }
+    }
 }
 
-int glyph_to_bitmap(ass_synth_priv_t* priv_blur,
-               FT_Glyph glyph, FT_Glyph outline_glyph, bitmap_t** bm_g,
-               bitmap_t** bm_o, bitmap_t** bm_s, int be, double blur_radius)
+int glyph_to_bitmap(ASS_Library *library, ASS_SynthPriv *priv_blur,
+                    FT_Glyph glyph, FT_Glyph outline_glyph,
+                    Bitmap **bm_g, Bitmap **bm_o, Bitmap **bm_s,
+                    int be, double blur_radius, FT_Vector shadow_offset,
+                    int border_style)
 {
-       int bord = be ? (be/4+1) : 0;
-       blur_radius *= 2;
-       bord = (blur_radius > 0.0) ? blur_radius : bord;
-
-       assert(bm_g && bm_o && bm_s);
-
-       *bm_g = *bm_o = *bm_s = 0;
-
-       if (glyph)
-               *bm_g = glyph_to_bitmap_internal(glyph, bord);
-       if (!*bm_g)
-               return 1;
-
-       if (outline_glyph) {
-               *bm_o = glyph_to_bitmap_internal(outline_glyph, bord);
-               if (!*bm_o) {
-                       ass_free_bitmap(*bm_g);
-                       return 1;
-               }
-       }
-       if (*bm_o)
-               resize_tmp(priv_blur, (*bm_o)->w, (*bm_o)->h);
-       resize_tmp(priv_blur, (*bm_g)->w, (*bm_g)->h);
-
-       if (be) {
-               while (be--) {
-                       if (*bm_o)
-                               be_blur((*bm_o)->buffer, (*bm_o)->w, (*bm_o)->h);
-                       else
-                               be_blur((*bm_g)->buffer, (*bm_g)->w, (*bm_g)->h);
-               }
-       } else {
-               if (blur_radius > 0.0) {
-                       generate_tables(priv_blur, blur_radius);
-                       if (*bm_o)
-                               blur((*bm_o)->buffer, priv_blur->tmp, (*bm_o)->w, (*bm_o)->h, (*bm_o)->w, (int*)priv_blur->gt2, priv_blur->g_r, priv_blur->g_w);
-                       else
-                               blur((*bm_g)->buffer, priv_blur->tmp, (*bm_g)->w, (*bm_g)->h, (*bm_g)->w, (int*)priv_blur->gt2, priv_blur->g_r, priv_blur->g_w);
-               }
-       }
-       if (*bm_o)
-               *bm_s = fix_outline_and_shadow(*bm_g, *bm_o);
-       else
-               *bm_s = copy_bitmap(*bm_g);
-
-       assert(bm_s);
-       return 0;
+    blur_radius *= 2;
+    int bbord = be > 0 ? sqrt(2 * be) : 0;
+    int gbord = blur_radius > 0.0 ? blur_radius + 1 : 0;
+    int bord = FFMAX(bbord, gbord);
+    if (bord == 0 && (shadow_offset.x || shadow_offset.y))
+        bord = 1;
+
+    assert(bm_g && bm_o && bm_s);
+
+    *bm_g = *bm_o = *bm_s = 0;
+
+    if (glyph)
+        *bm_g = glyph_to_bitmap_internal(library, glyph, bord);
+    if (!*bm_g)
+        return 1;
+
+    if (outline_glyph) {
+        *bm_o = glyph_to_bitmap_internal(library, outline_glyph, bord);
+        if (!*bm_o) {
+            return 1;
+        }
+    }
+
+    // Apply box blur (multiple passes, if requested)
+    while (be--) {
+        if (*bm_o)
+            be_blur((*bm_o)->buffer, (*bm_o)->w, (*bm_o)->h);
+        else
+            be_blur((*bm_g)->buffer, (*bm_g)->w, (*bm_g)->h);
+    }
+
+    // Apply gaussian blur
+    if (blur_radius > 0.0) {
+        if (*bm_o)
+            resize_tmp(priv_blur, (*bm_o)->w, (*bm_o)->h);
+        else
+            resize_tmp(priv_blur, (*bm_g)->w, (*bm_g)->h);
+        generate_tables(priv_blur, blur_radius);
+        if (*bm_o)
+            ass_gauss_blur((*bm_o)->buffer, priv_blur->tmp,
+                           (*bm_o)->w, (*bm_o)->h, (*bm_o)->w,
+                           (int *) priv_blur->gt2, priv_blur->g_r,
+                           priv_blur->g_w);
+        else
+            ass_gauss_blur((*bm_g)->buffer, priv_blur->tmp,
+                           (*bm_g)->w, (*bm_g)->h, (*bm_g)->w,
+                           (int *) priv_blur->gt2, priv_blur->g_r,
+                           priv_blur->g_w);
+    }
+
+    // Create shadow and fix outline as needed
+    if (*bm_o && border_style != 3) {
+        *bm_s = copy_bitmap(*bm_o);
+        fix_outline(*bm_g, *bm_o);
+    } else if (*bm_o) {
+        *bm_s = copy_bitmap(*bm_o);
+    } else
+        *bm_s = copy_bitmap(*bm_g);
+
+    assert(bm_s);
+
+    shift_bitmap((*bm_s)->buffer, (*bm_s)->w,(*bm_s)->h,
+                 shadow_offset.x, shadow_offset.y);
+
+    return 0;
 }
-
index c0b4ad2..338db01 100644 (file)
@@ -1,5 +1,3 @@
-// -*- c-basic-offset: 8; indent-tabs-mode: t -*-
-// vim:ts=8:sw=8:noet:ai:
 /*
  * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
  *
 #include <ft2build.h>
 #include FT_GLYPH_H
 
-typedef struct ass_synth_priv_s ass_synth_priv_t;
+#include "ass.h"
 
-ass_synth_priv_t* ass_synth_init(double);
-void ass_synth_done(ass_synth_priv_t* priv);
+typedef struct ass_synth_priv ASS_SynthPriv;
 
-typedef struct bitmap_s {
-       int left, top;
-       int w, h; // width, height
-       unsigned char* buffer; // w x h buffer
-} bitmap_t;
+ASS_SynthPriv *ass_synth_init(double);
+void ass_synth_done(ASS_SynthPriv *priv);
+
+typedef struct {
+    int left, top;
+    int w, h;                   // width, height
+    unsigned char *buffer;      // w x h buffer
+} Bitmap;
 
 /**
  * \brief perform glyph rendering
@@ -46,8 +46,12 @@ typedef struct bitmap_s {
  * \param bm_g out: pointer to the bitmap of glyph shadow is returned here
  * \param be 1 = produces blurred bitmaps, 0 = normal bitmaps
  */
-int glyph_to_bitmap(ass_synth_priv_t* priv_blur, FT_Glyph glyph, FT_Glyph outline_glyph, bitmap_t** bm_g, bitmap_t** bm_o, bitmap_t** bm_s, int be, double blur_radius);
+int glyph_to_bitmap(ASS_Library *library, ASS_SynthPriv *priv_blur,
+                    FT_Glyph glyph, FT_Glyph outline_glyph,
+                    Bitmap **bm_g, Bitmap **bm_o, Bitmap **bm_s,
+                    int be, double blur_radius, FT_Vector shadow_offset,
+                    int border_style);
 
-void ass_free_bitmap(bitmap_t* bm);
+void ass_free_bitmap(Bitmap *bm);
 
-#endif /* LIBASS_BITMAP_H */
+#endif                          /* LIBASS_BITMAP_H */
index dc217bd..643d991 100644 (file)
@@ -1,5 +1,3 @@
-// -*- c-basic-offset: 8; indent-tabs-mode: t -*-
-// vim:ts=8:sw=8:noet:ai:
 /*
  * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
  *
 
 #include <assert.h>
 
-#include "mputils.h"
+#include "ass_utils.h"
 #include "ass.h"
 #include "ass_fontconfig.h"
 #include "ass_font.h"
 #include "ass_bitmap.h"
 #include "ass_cache.h"
 
-
-typedef struct hashmap_item_s {
-       void* key;
-       void* value;
-       struct hashmap_item_s* next;
-} hashmap_item_t;
-typedef hashmap_item_t* hashmap_item_p;
-
-struct hashmap_s {
-       int nbuckets;
-       size_t key_size, value_size;
-       hashmap_item_p* root;
-       hashmap_item_dtor_t item_dtor; // a destructor for hashmap key/value pairs
-       hashmap_key_compare_t key_compare;
-       hashmap_hash_t hash;
-       // stats
-       int hit_count;
-       int miss_count;
-       int count;
-};
-
-#define FNV1_32A_INIT (unsigned)0x811c9dc5
-
-static inline unsigned fnv_32a_buf(void* buf, size_t len, unsigned hval)
-{
-       unsigned char *bp = buf;
-       unsigned char *be = bp + len;
-       while (bp < be) {
-               hval ^= (unsigned)*bp++;
-               hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24);
-       }
-       return hval;
-}
-static inline unsigned fnv_32a_str(char* str, unsigned hval)
+static unsigned hashmap_hash(void *buf, size_t len)
 {
-       unsigned char* s = (unsigned char*)str;
-       while (*s) {
-               hval ^= (unsigned)*s++;
-               hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24);
-       }
-       return hval;
+    return fnv_32a_buf(buf, len, FNV1_32A_INIT);
 }
 
-static unsigned hashmap_hash(void* buf, size_t len)
+static int hashmap_key_compare(void *a, void *b, size_t size)
 {
-       return fnv_32a_buf(buf, len, FNV1_32A_INIT);
+    return memcmp(a, b, size) == 0;
 }
 
-static int hashmap_key_compare(void* a, void* b, size_t size)
+static void hashmap_item_dtor(void *key, size_t key_size, void *value,
+                              size_t value_size)
 {
-       return memcmp(a, b, size) == 0;
+    free(key);
+    free(value);
 }
 
-static void hashmap_item_dtor(void* key, size_t key_size, void* value, size_t value_size)
+Hashmap *hashmap_init(ASS_Library *library, size_t key_size,
+                      size_t value_size, int nbuckets,
+                      HashmapItemDtor item_dtor,
+                      HashmapKeyCompare key_compare,
+                      HashmapHash hash)
 {
-       free(key);
-       free(value);
+    Hashmap *map = calloc(1, sizeof(Hashmap));
+    map->library = library;
+    map->nbuckets = nbuckets;
+    map->key_size = key_size;
+    map->value_size = value_size;
+    map->root = calloc(nbuckets, sizeof(hashmap_item_p));
+    map->item_dtor = item_dtor ? item_dtor : hashmap_item_dtor;
+    map->key_compare = key_compare ? key_compare : hashmap_key_compare;
+    map->hash = hash ? hash : hashmap_hash;
+    return map;
 }
 
-hashmap_t* hashmap_init(size_t key_size, size_t value_size, int nbuckets,
-                       hashmap_item_dtor_t item_dtor, hashmap_key_compare_t key_compare,
-                       hashmap_hash_t hash)
+void hashmap_done(Hashmap *map)
 {
-       hashmap_t* map = calloc(1, sizeof(hashmap_t));
-       map->nbuckets = nbuckets;
-       map->key_size = key_size;
-       map->value_size = value_size;
-       map->root = calloc(nbuckets, sizeof(hashmap_item_p));
-       map->item_dtor = item_dtor ? item_dtor : hashmap_item_dtor;
-       map->key_compare = key_compare ? key_compare : hashmap_key_compare;
-       map->hash = hash ? hash : hashmap_hash;
-       return map;
-}
-
-void hashmap_done(hashmap_t* map)
-{
-       int i;
-       // print stats
-       if (map->count > 0 || map->hit_count + map->miss_count > 0)
-               mp_msg(MSGT_ASS, MSGL_V, "cache statistics: \n  total accesses: %d\n  hits: %d\n  misses: %d\n  object count: %d\n",
-                      map->hit_count + map->miss_count, map->hit_count, map->miss_count, map->count);
+    int i;
+    // print stats
+    if (map->count > 0 || map->hit_count + map->miss_count > 0)
+        ass_msg(map->library, MSGL_V,
+               "cache statistics: \n  total accesses: %d\n  hits: %d\n  "
+               "misses: %d\n  object count: %d",
+               map->hit_count + map->miss_count, map->hit_count,
+               map->miss_count, map->count);
 
-       for (i = 0; i < map->nbuckets; ++i) {
-               hashmap_item_t* item = map->root[i];
-               while (item) {
-                       hashmap_item_t* next = item->next;
-                       map->item_dtor(item->key, map->key_size, item->value, map->value_size);
-                       free(item);
-                       item = next;
-               }
-       }
-       free(map->root);
-       free(map);
+    for (i = 0; i < map->nbuckets; ++i) {
+        HashmapItem *item = map->root[i];
+        while (item) {
+            HashmapItem *next = item->next;
+            map->item_dtor(item->key, map->key_size, item->value,
+                           map->value_size);
+            free(item);
+            item = next;
+        }
+    }
+    free(map->root);
+    free(map);
 }
 
 // does nothing if key already exists
-void* hashmap_insert(hashmap_t* map, void* key, void* value)
-{
-       unsigned hash = map->hash(key, map->key_size);
-       hashmap_item_t** next = map->root + (hash % map->nbuckets);
-       while (*next) {
-               if (map->key_compare(key, (*next)->key, map->key_size))
-                       return (*next)->value;
-               next = &((*next)->next);
-               assert(next);
-       }
-       (*next) = malloc(sizeof(hashmap_item_t));
-       (*next)->key = malloc(map->key_size);
-       (*next)->value = malloc(map->value_size);
-       memcpy((*next)->key, key, map->key_size);
-       memcpy((*next)->value, value, map->value_size);
-       (*next)->next = 0;
-
-       map->count ++;
-       return (*next)->value;
-}
-
-void* hashmap_find(hashmap_t* map, void* key)
-{
-       unsigned hash = map->hash(key, map->key_size);
-       hashmap_item_t* item = map->root[hash % map->nbuckets];
-       while (item) {
-               if (map->key_compare(key, item->key, map->key_size)) {
-                       map->hit_count++;
-                       return item->value;
-               }
-               item = item->next;
-       }
-       map->miss_count++;
-       return 0;
+void *hashmap_insert(Hashmap *map, void *key, void *value)
+{
+    unsigned hash = map->hash(key, map->key_size);
+    HashmapItem **next = map->root + (hash % map->nbuckets);
+    while (*next) {
+        if (map->key_compare(key, (*next)->key, map->key_size))
+            return (*next)->value;
+        next = &((*next)->next);
+        assert(next);
+    }
+    (*next) = malloc(sizeof(HashmapItem));
+    (*next)->key = malloc(map->key_size);
+    (*next)->value = malloc(map->value_size);
+    memcpy((*next)->key, key, map->key_size);
+    memcpy((*next)->value, value, map->value_size);
+    (*next)->next = 0;
+
+    map->count++;
+    return (*next)->value;
+}
+
+void *hashmap_find(Hashmap *map, void *key)
+{
+    unsigned hash = map->hash(key, map->key_size);
+    HashmapItem *item = map->root[hash % map->nbuckets];
+    while (item) {
+        if (map->key_compare(key, item->key, map->key_size)) {
+            map->hit_count++;
+            return item->value;
+        }
+        item = item->next;
+    }
+    map->miss_count++;
+    return 0;
 }
 
 //---------------------------------
 // font cache
 
-hashmap_t* font_cache;
-
-static unsigned font_desc_hash(void* buf, size_t len)
+static unsigned font_desc_hash(void *buf, size_t len)
 {
-       ass_font_desc_t* desc = buf;
-       unsigned hval;
-       hval = fnv_32a_str(desc->family, FNV1_32A_INIT);
-       hval = fnv_32a_buf(&desc->bold, sizeof(desc->bold), hval);
-       hval = fnv_32a_buf(&desc->italic, sizeof(desc->italic), hval);
-       return hval;
+    ASS_FontDesc *desc = buf;
+    unsigned hval;
+    hval = fnv_32a_str(desc->family, FNV1_32A_INIT);
+    hval = fnv_32a_buf(&desc->bold, sizeof(desc->bold), hval);
+    hval = fnv_32a_buf(&desc->italic, sizeof(desc->italic), hval);
+    return hval;
 }
 
-static int font_compare(void* key1, void* key2, size_t key_size) {
-       ass_font_desc_t* a = key1;
-       ass_font_desc_t* b = key2;
-       if (strcmp(a->family, b->family) != 0)
-               return 0;
-       if (a->bold != b->bold)
-               return 0;
-       if (a->italic != b->italic)
-               return 0;
-       if (a->treat_family_as_pattern != b->treat_family_as_pattern)
-               return 0;
-       return 1;
+static int font_compare(void *key1, void *key2, size_t key_size)
+{
+    ASS_FontDesc *a = key1;
+    ASS_FontDesc *b = key2;
+    if (strcmp(a->family, b->family) != 0)
+        return 0;
+    if (a->bold != b->bold)
+        return 0;
+    if (a->italic != b->italic)
+        return 0;
+    if (a->treat_family_as_pattern != b->treat_family_as_pattern)
+        return 0;
+    return 1;
 }
 
-static void font_hash_dtor(void* key, size_t key_size, void* value, size_t value_size)
+static void font_hash_dtor(void *key, size_t key_size, void *value,
+                           size_t value_size)
 {
-       ass_font_free(value);
-       free(key);
+    ass_font_free(value);
+    free(key);
 }
 
-ass_font_t* ass_font_cache_find(ass_font_desc_t* desc)
+ASS_Font *ass_font_cache_find(Hashmap *font_cache,
+                              ASS_FontDesc *desc)
 {
-       return hashmap_find(font_cache, desc);
+    return hashmap_find(font_cache, desc);
 }
 
 /**
  * \brief Add a face struct to cache.
  * \param font font struct
 */
-void* ass_font_cache_add(ass_font_t* font)
+void *ass_font_cache_add(Hashmap *font_cache, ASS_Font *font)
 {
-       return hashmap_insert(font_cache, &(font->desc), font);
+    return hashmap_insert(font_cache, &(font->desc), font);
 }
 
-void ass_font_cache_init(void)
+Hashmap *ass_font_cache_init(ASS_Library *library)
 {
-       font_cache = hashmap_init(sizeof(ass_font_desc_t),
-                                 sizeof(ass_font_t),
-                                 1000,
-                                 font_hash_dtor, font_compare, font_desc_hash);
+    Hashmap *font_cache;
+    font_cache = hashmap_init(library, sizeof(ASS_FontDesc),
+                              sizeof(ASS_Font),
+                              1000,
+                              font_hash_dtor, font_compare, font_desc_hash);
+    return font_cache;
 }
 
-void ass_font_cache_done(void)
+void ass_font_cache_done(Hashmap *font_cache)
 {
-       hashmap_done(font_cache);
+    hashmap_done(font_cache);
 }
 
+
+// Create hash/compare functions for bitmap and glyph
+#define CREATE_HASH_FUNCTIONS
+#include "ass_cache_template.h"
+#define CREATE_COMPARISON_FUNCTIONS
+#include "ass_cache_template.h"
+
 //---------------------------------
 // bitmap cache
 
-hashmap_t* bitmap_cache;
-
-static void bitmap_hash_dtor(void* key, size_t key_size, void* value, size_t value_size)
+static void bitmap_hash_dtor(void *key, size_t key_size, void *value,
+                             size_t value_size)
 {
-       bitmap_hash_val_t* v = value;
-       if (v->bm) ass_free_bitmap(v->bm);
-       if (v->bm_o) ass_free_bitmap(v->bm_o);
-       if (v->bm_s) ass_free_bitmap(v->bm_s);
-       free(key);
-       free(value);
+    BitmapHashValue *v = value;
+    if (v->bm)
+        ass_free_bitmap(v->bm);
+    if (v->bm_o)
+        ass_free_bitmap(v->bm_o);
+    if (v->bm_s)
+        ass_free_bitmap(v->bm_s);
+    free(key);
+    free(value);
 }
 
-void* cache_add_bitmap(bitmap_hash_key_t* key, bitmap_hash_val_t* val)
+void *cache_add_bitmap(Hashmap *bitmap_cache, BitmapHashKey *key,
+                       BitmapHashValue *val)
 {
-       return hashmap_insert(bitmap_cache, key, val);
+    // Note: this is only an approximation
+    if (val->bm_o)
+        bitmap_cache->cache_size += val->bm_o->w * val->bm_o->h * 3;
+    else if (val->bm)
+        bitmap_cache->cache_size += val->bm->w * val->bm->h * 3;
+
+    return hashmap_insert(bitmap_cache, key, val);
 }
 
 /**
@@ -255,47 +237,56 @@ void* cache_add_bitmap(bitmap_hash_key_t* key, bitmap_hash_val_t* val)
  * \param key hash key
  * \return requested hash val or 0 if not found
 */
-bitmap_hash_val_t* cache_find_bitmap(bitmap_hash_key_t* key)
+BitmapHashValue *cache_find_bitmap(Hashmap *bitmap_cache,
+                                   BitmapHashKey *key)
 {
-       return hashmap_find(bitmap_cache, key);
+    return hashmap_find(bitmap_cache, key);
 }
 
-void ass_bitmap_cache_init(void)
+Hashmap *ass_bitmap_cache_init(ASS_Library *library)
 {
-       bitmap_cache = hashmap_init(sizeof(bitmap_hash_key_t),
-                                  sizeof(bitmap_hash_val_t),
-                                  0xFFFF + 13,
-                                  bitmap_hash_dtor, NULL, NULL);
+    Hashmap *bitmap_cache;
+    bitmap_cache = hashmap_init(library,
+                                sizeof(BitmapHashKey),
+                                sizeof(BitmapHashValue),
+                                0xFFFF + 13,
+                                bitmap_hash_dtor, bitmap_compare,
+                                bitmap_hash);
+    return bitmap_cache;
 }
 
-void ass_bitmap_cache_done(void)
+void ass_bitmap_cache_done(Hashmap *bitmap_cache)
 {
-       hashmap_done(bitmap_cache);
+    hashmap_done(bitmap_cache);
 }
 
-void ass_bitmap_cache_reset(void)
+Hashmap *ass_bitmap_cache_reset(Hashmap *bitmap_cache)
 {
-       ass_bitmap_cache_done();
-       ass_bitmap_cache_init();
+    ASS_Library *lib = bitmap_cache->library;
+
+    ass_bitmap_cache_done(bitmap_cache);
+    return ass_bitmap_cache_init(lib);
 }
 
 //---------------------------------
 // glyph cache
 
-hashmap_t* glyph_cache;
-
-static void glyph_hash_dtor(void* key, size_t key_size, void* value, size_t value_size)
+static void glyph_hash_dtor(void *key, size_t key_size, void *value,
+                            size_t value_size)
 {
-       glyph_hash_val_t* v = value;
-       if (v->glyph) FT_Done_Glyph(v->glyph);
-       if (v->outline_glyph) FT_Done_Glyph(v->outline_glyph);
-       free(key);
-       free(value);
+    GlyphHashValue *v = value;
+    if (v->glyph)
+        FT_Done_Glyph(v->glyph);
+    if (v->outline_glyph)
+        FT_Done_Glyph(v->outline_glyph);
+    free(key);
+    free(value);
 }
 
-void* cache_add_glyph(glyph_hash_key_t* key, glyph_hash_val_t* val)
+void *cache_add_glyph(Hashmap *glyph_cache, GlyphHashKey *key,
+                      GlyphHashValue *val)
 {
-       return hashmap_insert(glyph_cache, key, val);
+    return hashmap_insert(glyph_cache, key, val);
 }
 
 /**
@@ -303,48 +294,54 @@ void* cache_add_glyph(glyph_hash_key_t* key, glyph_hash_val_t* val)
  * \param key hash key
  * \return requested hash val or 0 if not found
 */
-glyph_hash_val_t* cache_find_glyph(glyph_hash_key_t* key)
+GlyphHashValue *cache_find_glyph(Hashmap *glyph_cache,
+                                 GlyphHashKey *key)
 {
-       return hashmap_find(glyph_cache, key);
+    return hashmap_find(glyph_cache, key);
 }
 
-void ass_glyph_cache_init(void)
+Hashmap *ass_glyph_cache_init(ASS_Library *library)
 {
-       glyph_cache = hashmap_init(sizeof(glyph_hash_key_t),
-                                  sizeof(glyph_hash_val_t),
-                                  0xFFFF + 13,
-                                  glyph_hash_dtor, NULL, NULL);
+    Hashmap *glyph_cache;
+    glyph_cache = hashmap_init(library, sizeof(GlyphHashKey),
+                               sizeof(GlyphHashValue),
+                               0xFFFF + 13,
+                               glyph_hash_dtor, glyph_compare, glyph_hash);
+    return glyph_cache;
 }
 
-void ass_glyph_cache_done(void)
+void ass_glyph_cache_done(Hashmap *glyph_cache)
 {
-       hashmap_done(glyph_cache);
+    hashmap_done(glyph_cache);
 }
 
-void ass_glyph_cache_reset(void)
+Hashmap *ass_glyph_cache_reset(Hashmap *glyph_cache)
 {
-       ass_glyph_cache_done();
-       ass_glyph_cache_init();
+    ASS_Library *lib = glyph_cache->library;
+
+    ass_glyph_cache_done(glyph_cache);
+    return ass_glyph_cache_init(lib);
 }
 
 
 //---------------------------------
 // composite cache
 
-hashmap_t* composite_cache;
-
-static void composite_hash_dtor(void* key, size_t key_size, void* value, size_t value_size)
+static void composite_hash_dtor(void *key, size_t key_size, void *value,
+                                size_t value_size)
 {
-       composite_hash_val_t* v = value;
-       free(v->a);
-       free(v->b);
-       free(key);
-       free(value);
+    CompositeHashValue *v = value;
+    free(v->a);
+    free(v->b);
+    free(key);
+    free(value);
 }
 
-void* cache_add_composite(composite_hash_key_t* key, composite_hash_val_t* val)
+void *cache_add_composite(Hashmap *composite_cache,
+                          CompositeHashKey *key,
+                          CompositeHashValue *val)
 {
-       return hashmap_insert(composite_cache, key, val);
+    return hashmap_insert(composite_cache, key, val);
 }
 
 /**
@@ -352,27 +349,32 @@ void* cache_add_composite(composite_hash_key_t* key, composite_hash_val_t* val)
  * \param key hash key
  * \return requested hash val or 0 if not found
 */
-composite_hash_val_t* cache_find_composite(composite_hash_key_t* key)
+CompositeHashValue *cache_find_composite(Hashmap *composite_cache,
+                                         CompositeHashKey *key)
 {
-       return hashmap_find(composite_cache, key);
+    return hashmap_find(composite_cache, key);
 }
 
-void ass_composite_cache_init(void)
+Hashmap *ass_composite_cache_init(ASS_Library *library)
 {
-       composite_cache = hashmap_init(sizeof(composite_hash_key_t),
-                                  sizeof(composite_hash_val_t),
-                                  0xFFFF + 13,
-                                  composite_hash_dtor, NULL, NULL);
+    Hashmap *composite_cache;
+    composite_cache = hashmap_init(library, sizeof(CompositeHashKey),
+                                   sizeof(CompositeHashValue),
+                                   0xFFFF + 13,
+                                   composite_hash_dtor, composite_compare,
+                                   composite_hash);
+    return composite_cache;
 }
 
-void ass_composite_cache_done(void)
+void ass_composite_cache_done(Hashmap *composite_cache)
 {
-       hashmap_done(composite_cache);
+    hashmap_done(composite_cache);
 }
 
-void ass_composite_cache_reset(void)
+Hashmap *ass_composite_cache_reset(Hashmap *composite_cache)
 {
-       ass_composite_cache_done();
-       ass_composite_cache_init();
-}
+    ASS_Library *lib = composite_cache->library;
 
+    ass_composite_cache_done(composite_cache);
+    return ass_composite_cache_init(lib);
+}
index 59ac8ce..5c9749f 100644 (file)
@@ -1,5 +1,3 @@
-// -*- c-basic-offset: 8; indent-tabs-mode: t -*-
-// vim:ts=8:sw=8:noet:ai:
 /*
  * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
  *
 #include "ass_font.h"
 #include "ass_bitmap.h"
 
-void ass_font_cache_init(void);
-ass_font_t* ass_font_cache_find(ass_font_desc_t* desc);
-void* ass_font_cache_add(ass_font_t* font);
-void ass_font_cache_done(void);
-
-
-// describes a bitmap; bitmaps with equivalents structs are considered identical
-typedef struct bitmap_hash_key_s {
-       char bitmap; // bool : true = bitmap, false = outline
-       ass_font_t* font;
-       double size; // font size
-       uint32_t ch; // character code
-       unsigned outline; // border width, 16.16 fixed point value
-       int bold, italic;
-       char be; // blur edges
-       double blur; // gaussian blur
-
-       unsigned scale_x, scale_y; // 16.16
-       int frx, fry, frz; // signed 16.16
-       int shift_x, shift_y; // shift vector that was added to glyph before applying rotation
-                             // = 0, if frx = fry = frx = 0
-                             // = (glyph base point) - (rotation origin), otherwise
-
-       FT_Vector advance; // subpixel shift vector
-} bitmap_hash_key_t;
-
-typedef struct bitmap_hash_val_s {
-       bitmap_t* bm; // the actual bitmaps
-       bitmap_t* bm_o;
-       bitmap_t* bm_s;
-} bitmap_hash_val_t;
-
-void ass_bitmap_cache_init(void);
-void* cache_add_bitmap(bitmap_hash_key_t* key, bitmap_hash_val_t* val);
-bitmap_hash_val_t* cache_find_bitmap(bitmap_hash_key_t* key);
-void ass_bitmap_cache_reset(void);
-void ass_bitmap_cache_done(void);
-
-
-// Cache for composited bitmaps
-typedef struct composite_hash_key_s {
-       int aw, ah, bw, bh;
-       int ax, ay, bx, by;
-       bitmap_hash_key_t a;
-       bitmap_hash_key_t b;
-} composite_hash_key_t;
-
-typedef struct composite_hash_val_s {
-       unsigned char* a;
-       unsigned char* b;
-} composite_hash_val_t;
-
-void ass_composite_cache_init(void);
-void* cache_add_composite(composite_hash_key_t* key, composite_hash_val_t* val);
-composite_hash_val_t* cache_find_composite(composite_hash_key_t* key);
-void ass_composite_cache_reset(void);
-void ass_composite_cache_done(void);
-
-
-// describes an outline glyph
-typedef struct glyph_hash_key_s {
-       ass_font_t* font;
-       double size; // font size
-       uint32_t ch; // character code
-       int bold, italic;
-       unsigned scale_x, scale_y; // 16.16
-       FT_Vector advance; // subpixel shift vector
-       unsigned outline; // border width, 16.16
-} glyph_hash_key_t;
-
-typedef struct glyph_hash_val_s {
-       FT_Glyph glyph;
-       FT_Glyph outline_glyph;
-       FT_BBox bbox_scaled; // bbox after scaling, but before rotation
-       FT_Vector advance; // 26.6, advance distance to the next bitmap in line
-} glyph_hash_val_t;
-
-void ass_glyph_cache_init(void);
-void* cache_add_glyph(glyph_hash_key_t* key, glyph_hash_val_t* val);
-glyph_hash_val_t* cache_find_glyph(glyph_hash_key_t* key);
-void ass_glyph_cache_reset(void);
-void ass_glyph_cache_done(void);
-
-typedef struct hashmap_s hashmap_t;
-typedef void (*hashmap_item_dtor_t)(void* key, size_t key_size, void* value, size_t value_size);
-typedef int (*hashmap_key_compare_t)(void* key1, void* key2, size_t key_size);
-typedef unsigned (*hashmap_hash_t)(void* key, size_t key_size);
-
-hashmap_t* hashmap_init(size_t key_size, size_t value_size, int nbuckets,
-                       hashmap_item_dtor_t item_dtor, hashmap_key_compare_t key_compare,
-                       hashmap_hash_t hash);
-void hashmap_done(hashmap_t* map);
-void* hashmap_insert(hashmap_t* map, void* key, void* value);
-void* hashmap_find(hashmap_t* map, void* key);
-
-#endif /* LIBASS_CACHE_H */
+typedef void (*HashmapItemDtor) (void *key, size_t key_size,
+                                 void *value, size_t value_size);
+typedef int (*HashmapKeyCompare) (void *key1, void *key2,
+                                  size_t key_size);
+typedef unsigned (*HashmapHash) (void *key, size_t key_size);
+
+typedef struct hashmap_item {
+    void *key;
+    void *value;
+    struct hashmap_item *next;
+} HashmapItem;
+typedef HashmapItem *hashmap_item_p;
+
+typedef struct {
+    int nbuckets;
+    size_t key_size, value_size;
+    hashmap_item_p *root;
+    HashmapItemDtor item_dtor;      // a destructor for hashmap key/value pairs
+    HashmapKeyCompare key_compare;
+    HashmapHash hash;
+    size_t cache_size;
+    // stats
+    int hit_count;
+    int miss_count;
+    int count;
+    ASS_Library *library;
+} Hashmap;
+
+Hashmap *hashmap_init(ASS_Library *library, size_t key_size,
+                      size_t value_size, int nbuckets,
+                      HashmapItemDtor item_dtor,
+                      HashmapKeyCompare key_compare,
+                      HashmapHash hash);
+void hashmap_done(Hashmap *map);
+void *hashmap_insert(Hashmap *map, void *key, void *value);
+void *hashmap_find(Hashmap *map, void *key);
+
+Hashmap *ass_font_cache_init(ASS_Library *library);
+ASS_Font *ass_font_cache_find(Hashmap *, ASS_FontDesc *desc);
+void *ass_font_cache_add(Hashmap *, ASS_Font *font);
+void ass_font_cache_done(Hashmap *);
+
+// Create definitions for bitmap_hash_key and glyph_hash_key
+#define CREATE_STRUCT_DEFINITIONS
+#include "ass_cache_template.h"
+
+typedef struct {
+    Bitmap *bm;               // the actual bitmaps
+    Bitmap *bm_o;
+    Bitmap *bm_s;
+} BitmapHashValue;
+
+Hashmap *ass_bitmap_cache_init(ASS_Library *library);
+void *cache_add_bitmap(Hashmap *, BitmapHashKey *key,
+                       BitmapHashValue *val);
+BitmapHashValue *cache_find_bitmap(Hashmap *bitmap_cache,
+                                   BitmapHashKey *key);
+Hashmap *ass_bitmap_cache_reset(Hashmap *bitmap_cache);
+void ass_bitmap_cache_done(Hashmap *bitmap_cache);
+
+
+typedef struct {
+    unsigned char *a;
+    unsigned char *b;
+} CompositeHashValue;
+
+Hashmap *ass_composite_cache_init(ASS_Library *library);
+void *cache_add_composite(Hashmap *, CompositeHashKey *key,
+                          CompositeHashValue *val);
+CompositeHashValue *cache_find_composite(Hashmap *composite_cache,
+                                         CompositeHashKey *key);
+Hashmap *ass_composite_cache_reset(Hashmap *composite_cache);
+void ass_composite_cache_done(Hashmap *composite_cache);
+
+
+typedef struct {
+    FT_Glyph glyph;
+    FT_Glyph outline_glyph;
+    FT_BBox bbox_scaled;        // bbox after scaling, but before rotation
+    FT_Vector advance;          // 26.6, advance distance to the next bitmap in line
+    int asc, desc;              // ascender/descender of a drawing
+} GlyphHashValue;
+
+Hashmap *ass_glyph_cache_init(ASS_Library *library);
+void *cache_add_glyph(Hashmap *, GlyphHashKey *key,
+                      GlyphHashValue *val);
+GlyphHashValue *cache_find_glyph(Hashmap *glyph_cache,
+                                 GlyphHashKey *key);
+Hashmap *ass_glyph_cache_reset(Hashmap *glyph_cache);
+void ass_glyph_cache_done(Hashmap *glyph_cache);
+
+#endif                          /* LIBASS_CACHE_H */
diff --git a/libass/ass_cache_template.h b/libass/ass_cache_template.h
new file mode 100644 (file)
index 0000000..f335c6b
--- /dev/null
@@ -0,0 +1,122 @@
+#ifdef CREATE_STRUCT_DEFINITIONS
+#undef CREATE_STRUCT_DEFINITIONS
+#define START(funcname, structname) \
+    typedef struct structname {
+#define GENERIC(type, member) \
+        type member;
+#define FTVECTOR(member) \
+        FT_Vector member;
+#define BITMAPHASHKEY(member) \
+        BitmapHashKey member;
+#define END(typedefnamename) \
+    } typedefnamename;
+
+#elif defined(CREATE_COMPARISON_FUNCTIONS)
+#undef CREATE_COMPARISON_FUNCTIONS
+#define START(funcname, structname) \
+    static int funcname##_compare(void *key1, void *key2, size_t key_size) \
+    { \
+        struct structname *a = key1; \
+        struct structname *b = key2; \
+        return // conditions follow
+#define GENERIC(type, member) \
+            a->member == b->member &&
+#define FTVECTOR(member) \
+            a->member.x == b->member.x && a->member.y == b->member.y &&
+#define BITMAPHASHKEY(member) \
+            bitmap_compare(&a->member, &b->member, sizeof(a->member)) &&
+#define END(typedefname) \
+            1; \
+    }
+
+#elif defined(CREATE_HASH_FUNCTIONS)
+#undef CREATE_HASH_FUNCTIONS
+#define START(funcname, structname) \
+    static unsigned funcname##_hash(void *buf, size_t len) \
+    { \
+        struct structname *p = buf; \
+        unsigned hval = FNV1_32A_INIT;
+#define GENERIC(type, member) \
+        hval = fnv_32a_buf(&p->member, sizeof(p->member), hval);
+#define FTVECTOR(member) GENERIC(, member.x); GENERIC(, member.y);
+#define BITMAPHASHKEY(member) { \
+        unsigned temp = bitmap_hash(&p->member, sizeof(p->member)); \
+        hval = fnv_32a_buf(&temp, sizeof(temp), hval); \
+        }
+#define END(typedefname) \
+        return hval; \
+    }
+
+#else
+#error missing defines
+#endif
+
+
+
+// describes a bitmap; bitmaps with equivalents structs are considered identical
+START(bitmap, bitmap_hash_key)
+    GENERIC(char, bitmap) // bool : true = bitmap, false = outline
+    GENERIC(ASS_Font *, font)
+    GENERIC(double, size) // font size
+    GENERIC(uint32_t, ch) // character code
+    FTVECTOR(outline) // border width, 16.16 fixed point value
+    GENERIC(int, bold)
+    GENERIC(int, italic)
+    GENERIC(char, be) // blur edges
+    GENERIC(double, blur) // gaussian blur
+    GENERIC(unsigned, scale_x) // 16.16
+    GENERIC(unsigned, scale_y) // 16.16
+    GENERIC(int, frx) // signed 16.16
+    GENERIC(int, fry) // signed 16.16
+    GENERIC(int, frz) // signed 16.16
+    GENERIC(int, fax) // signed 16.16
+    GENERIC(int, fay) // signed 16.16
+    // shift vector that was added to glyph before applying rotation
+    // = 0, if frx = fry = frx = 0
+    // = (glyph base point) - (rotation origin), otherwise
+    GENERIC(int, shift_x)
+    GENERIC(int, shift_y)
+    FTVECTOR(advance) // subpixel shift vector
+    FTVECTOR(shadow_offset) // shadow subpixel shift
+    GENERIC(unsigned, drawing_hash) // hashcode of a drawing
+    GENERIC(unsigned, flags)    // glyph decoration
+    GENERIC(unsigned, border_style)
+END(BitmapHashKey)
+
+// describes an outline glyph
+START(glyph, glyph_hash_key)
+    GENERIC(ASS_Font *, font)
+    GENERIC(double, size) // font size
+    GENERIC(uint32_t, ch) // character code
+    GENERIC(int, bold)
+    GENERIC(int, italic)
+    GENERIC(unsigned, scale_x) // 16.16
+    GENERIC(unsigned, scale_y) // 16.16
+    FTVECTOR(outline) // border width, 16.16
+    GENERIC(unsigned, drawing_hash) // hashcode of a drawing
+    GENERIC(unsigned, flags)    // glyph decoration flags
+    GENERIC(unsigned, border_style)
+END(GlyphHashKey)
+
+// Cache for composited bitmaps
+START(composite, composite_hash_key)
+    GENERIC(int, aw)
+    GENERIC(int, ah)
+    GENERIC(int, bw)
+    GENERIC(int, bh)
+    GENERIC(int, ax)
+    GENERIC(int, ay)
+    GENERIC(int, bx)
+    GENERIC(int, by)
+    GENERIC(int, as)
+    GENERIC(int, bs)
+    GENERIC(unsigned char *, a)
+    GENERIC(unsigned char *, b)
+END(CompositeHashKey)
+
+
+#undef START
+#undef GENERIC
+#undef FTVECTOR
+#undef BITMAPHASHKEY
+#undef END
diff --git a/libass/ass_drawing.c b/libass/ass_drawing.c
new file mode 100644 (file)
index 0000000..a3207c7
--- /dev/null
@@ -0,0 +1,495 @@
+/*
+ * Copyright (C) 2009 Grigori Goronzy <greg@geekmind.org>
+ *
+ * This file is part of libass.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <ft2build.h>
+#include FT_GLYPH_H
+#include FT_OUTLINE_H
+#include FT_BBOX_H
+#include <math.h>
+
+#include "ass_utils.h"
+#include "ass_font.h"
+#include "ass_drawing.h"
+
+#define CURVE_ACCURACY 64.0
+#define GLYPH_INITIAL_POINTS 100
+#define GLYPH_INITIAL_CONTOURS 5
+
+/*
+ * \brief Get and prepare a FreeType glyph
+ */
+static void drawing_make_glyph(ASS_Drawing *drawing, void *fontconfig_priv,
+                               ASS_Font *font, ASS_Hinting hint)
+{
+    FT_OutlineGlyph glyph;
+
+    // This is hacky...
+    glyph = (FT_OutlineGlyph) ass_font_get_glyph(fontconfig_priv, font,
+                                                 (uint32_t) ' ', hint, 0);
+    if (glyph) {
+        FT_Outline_Done(drawing->ftlibrary, &glyph->outline);
+        FT_Outline_New(drawing->ftlibrary, GLYPH_INITIAL_POINTS,
+                       GLYPH_INITIAL_CONTOURS, &glyph->outline);
+
+        glyph->outline.n_contours = 0;
+        glyph->outline.n_points = 0;
+        glyph->root.advance.x = glyph->root.advance.y = 0;
+    }
+    drawing->glyph = glyph;
+}
+
+/*
+ * \brief Add a single point to a contour.
+ */
+static inline void drawing_add_point(ASS_Drawing *drawing,
+                                     FT_Vector *point)
+{
+    FT_Outline *ol = &drawing->glyph->outline;
+
+    if (ol->n_points >= drawing->max_points) {
+        drawing->max_points *= 2;
+        ol->points = realloc(ol->points, sizeof(FT_Vector) *
+                             drawing->max_points);
+        ol->tags = realloc(ol->tags, drawing->max_points);
+    }
+
+    ol->points[ol->n_points].x = point->x;
+    ol->points[ol->n_points].y = point->y;
+    ol->tags[ol->n_points] = 1;
+    ol->n_points++;
+}
+
+/*
+ * \brief Close a contour and check glyph size overflow.
+ */
+static inline void drawing_close_shape(ASS_Drawing *drawing)
+{
+    FT_Outline *ol = &drawing->glyph->outline;
+
+    if (ol->n_contours >= drawing->max_contours) {
+        drawing->max_contours *= 2;
+        ol->contours = realloc(ol->contours, sizeof(short) *
+                               drawing->max_contours);
+    }
+
+    if (ol->n_points) {
+        ol->contours[ol->n_contours] = ol->n_points - 1;
+        ol->n_contours++;
+    }
+}
+
+/*
+ * \brief Prepare drawing for parsing.  This just sets a few parameters.
+ */
+static void drawing_prepare(ASS_Drawing *drawing)
+{
+    // Scaling parameters
+    drawing->point_scale_x = drawing->scale_x *
+                             64.0 / (1 << (drawing->scale - 1));
+    drawing->point_scale_y = drawing->scale_y *
+                             64.0 / (1 << (drawing->scale - 1));
+}
+
+/*
+ * \brief Finish a drawing.  This only sets the horizontal advance according
+ * to the glyph's bbox at the moment.
+ */
+static void drawing_finish(ASS_Drawing *drawing, int raw_mode)
+{
+    int i, offset;
+    FT_BBox bbox;
+    FT_Outline *ol = &drawing->glyph->outline;
+
+    // Close the last contour
+    drawing_close_shape(drawing);
+
+#if 0
+    // Dump points
+    for (i = 0; i < ol->n_points; i++) {
+        printf("point (%d, %d)\n", (int) ol->points[i].x,
+               (int) ol->points[i].y);
+    }
+
+    // Dump contours
+    for (i = 0; i < ol->n_contours; i++)
+        printf("contour %d\n", ol->contours[i]);
+#endif
+
+    ass_msg(drawing->library, MSGL_V,
+            "Parsed drawing with %d points and %d contours", ol->n_points,
+            ol->n_contours);
+
+    if (raw_mode)
+        return;
+
+    FT_Outline_Get_CBox(&drawing->glyph->outline, &bbox);
+    drawing->glyph->root.advance.x = d6_to_d16(bbox.xMax - bbox.xMin);
+
+    drawing->desc = double_to_d6(-drawing->pbo * drawing->scale_y);
+    drawing->asc = bbox.yMax - bbox.yMin + drawing->desc;
+
+    // Place it onto the baseline
+    offset = (bbox.yMax - bbox.yMin) + double_to_d6(-drawing->pbo *
+                                                    drawing->scale_y);
+    for (i = 0; i < ol->n_points; i++)
+        ol->points[i].y += offset;
+}
+
+/*
+ * \brief Check whether a number of items on the list is available
+ */
+static int token_check_values(ASS_DrawingToken *token, int i, int type)
+{
+    int j;
+    for (j = 0; j < i; j++) {
+        if (!token || token->type != type) return 0;
+        token = token->next;
+    }
+
+    return 1;
+}
+
+/*
+ * \brief Tokenize a drawing string into a list of ASS_DrawingToken
+ * This also expands points for closing b-splines
+ */
+static ASS_DrawingToken *drawing_tokenize(char *str)
+{
+    char *p = str;
+    int i, val, type = -1, is_set = 0;
+    FT_Vector point = {0, 0};
+
+    ASS_DrawingToken *root = NULL, *tail = NULL, *spline_start = NULL;
+
+    while (*p) {
+        if (*p == 'c' && spline_start) {
+            // Close b-splines: add the first three points of the b-spline
+            // back to the end
+            if (token_check_values(spline_start->next, 2, TOKEN_B_SPLINE)) {
+                for (i = 0; i < 3; i++) {
+                    tail->next = calloc(1, sizeof(ASS_DrawingToken));
+                    tail->next->prev = tail;
+                    tail = tail->next;
+                    tail->type = TOKEN_B_SPLINE;
+                    tail->point = spline_start->point;
+                    spline_start = spline_start->next;
+                }
+                spline_start = NULL;
+            }
+        } else if (!is_set && mystrtoi(&p, &val)) {
+            point.x = val;
+            is_set = 1;
+            p--;
+        } else if (is_set == 1 && mystrtoi(&p, &val)) {
+            point.y = val;
+            is_set = 2;
+            p--;
+        } else if (*p == 'm')
+            type = TOKEN_MOVE;
+        else if (*p == 'n')
+            type = TOKEN_MOVE_NC;
+        else if (*p == 'l')
+            type = TOKEN_LINE;
+        else if (*p == 'b')
+            type = TOKEN_CUBIC_BEZIER;
+        else if (*p == 'q')
+            type = TOKEN_CONIC_BEZIER;
+        else if (*p == 's')
+            type = TOKEN_B_SPLINE;
+        // We're simply ignoring TOKEN_EXTEND_B_SPLINE here.
+        // This is not harmful at all, since it can be ommitted with
+        // similar result (the spline is extended anyway).
+
+        if (type != -1 && is_set == 2) {
+            if (root) {
+                tail->next = calloc(1, sizeof(ASS_DrawingToken));
+                tail->next->prev = tail;
+                tail = tail->next;
+            } else
+                root = tail = calloc(1, sizeof(ASS_DrawingToken));
+            tail->type = type;
+            tail->point = point;
+            is_set = 0;
+            if (type == TOKEN_B_SPLINE && !spline_start)
+                spline_start = tail->prev;
+        }
+        p++;
+    }
+
+#if 0
+    // Check tokens
+    ASS_DrawingToken *t = root;
+    while(t) {
+        printf("token %d point (%d, %d)\n", t->type, t->point.x, t->point.y);
+        t = t->next;
+    }
+#endif
+
+    return root;
+}
+
+/*
+ * \brief Free a list of tokens
+ */
+static void drawing_free_tokens(ASS_DrawingToken *token)
+{
+    while (token) {
+        ASS_DrawingToken *at = token;
+        token = token->next;
+        free(at);
+    }
+}
+
+/*
+ * \brief Translate and scale a point coordinate according to baseline
+ * offset and scale.
+ */
+static inline void translate_point(ASS_Drawing *drawing, FT_Vector *point)
+{
+    point->x = drawing->point_scale_x * point->x;
+    point->y = drawing->point_scale_y * -point->y;
+}
+
+/*
+ * \brief Evaluate a curve into lines
+ * This curve evaluator is also used in VSFilter (RTS.cpp); it's a simple
+ * implementation of the De Casteljau algorithm.
+ */
+static void drawing_evaluate_curve(ASS_Drawing *drawing,
+                                   ASS_DrawingToken *token, char spline,
+                                   int started)
+{
+    double cx3, cx2, cx1, cx0, cy3, cy2, cy1, cy0;
+    double t, h, max_accel, max_accel1, max_accel2;
+    FT_Vector cur = {0, 0};
+
+    cur = token->point;
+    translate_point(drawing, &cur);
+    int x0 = cur.x;
+    int y0 = cur.y;
+    token = token->next;
+    cur = token->point;
+    translate_point(drawing, &cur);
+    int x1 = cur.x;
+    int y1 = cur.y;
+    token = token->next;
+    cur = token->point;
+    translate_point(drawing, &cur);
+    int x2 = cur.x;
+    int y2 = cur.y;
+    token = token->next;
+    cur = token->point;
+    translate_point(drawing, &cur);
+    int x3 = cur.x;
+    int y3 = cur.y;
+
+    if (spline) {
+        // 1   [-1 +3 -3 +1]
+        // - * [+3 -6 +3  0]
+        // 6   [-3  0 +3  0]
+        //        [+1 +4 +1  0]
+
+        double div6 = 1.0/6.0;
+
+        cx3 = div6*(-  x0+3*x1-3*x2+x3);
+        cx2 = div6*( 3*x0-6*x1+3*x2);
+        cx1 = div6*(-3*x0         +3*x2);
+        cx0 = div6*(   x0+4*x1+1*x2);
+
+        cy3 = div6*(-  y0+3*y1-3*y2+y3);
+        cy2 = div6*( 3*y0-6*y1+3*y2);
+        cy1 = div6*(-3*y0     +3*y2);
+        cy0 = div6*(   y0+4*y1+1*y2);
+    } else {
+        // [-1 +3 -3 +1]
+        // [+3 -6 +3  0]
+        // [-3 +3  0  0]
+        // [+1  0  0  0]
+
+        cx3 = -  x0+3*x1-3*x2+x3;
+        cx2 =  3*x0-6*x1+3*x2;
+        cx1 = -3*x0+3*x1;
+        cx0 =    x0;
+
+        cy3 = -  y0+3*y1-3*y2+y3;
+        cy2 =  3*y0-6*y1+3*y2;
+        cy1 = -3*y0+3*y1;
+        cy0 =    y0;
+    }
+
+    max_accel1 = fabs(2 * cy2) + fabs(6 * cy3);
+    max_accel2 = fabs(2 * cx2) + fabs(6 * cx3);
+
+    max_accel = FFMAX(max_accel1, max_accel2);
+    h = 1.0;
+
+    if (max_accel > CURVE_ACCURACY)
+        h = sqrt(CURVE_ACCURACY / max_accel);
+
+    if (!started) {
+        cur.x = cx0;
+        cur.y = cy0;
+        drawing_add_point(drawing, &cur);
+    }
+
+    for (t = 0; t < 1.0; t += h) {
+        cur.x = cx0 + t * (cx1 + t * (cx2 + t * cx3));
+        cur.y = cy0 + t * (cy1 + t * (cy2 + t * cy3));
+        drawing_add_point(drawing, &cur);
+    }
+
+    cur.x = cx0 + cx1 + cx2 + cx3;
+    cur.y = cy0 + cy1 + cy2 + cy3;
+    drawing_add_point(drawing, &cur);
+}
+
+/*
+ * \brief Create and initialize a new drawing and return it
+ */
+ASS_Drawing *ass_drawing_new(void *fontconfig_priv, ASS_Font *font,
+                             ASS_Hinting hint, FT_Library lib)
+{
+    ASS_Drawing *drawing;
+
+    drawing = calloc(1, sizeof(*drawing));
+    drawing->text = calloc(1, DRAWING_INITIAL_SIZE);
+    drawing->size = DRAWING_INITIAL_SIZE;
+
+    drawing->ftlibrary = lib;
+    if (font) {
+        drawing->library = font->library;
+        drawing_make_glyph(drawing, fontconfig_priv, font, hint);
+    }
+
+    drawing->scale_x = 1.;
+    drawing->scale_y = 1.;
+    drawing->max_contours = GLYPH_INITIAL_CONTOURS;
+    drawing->max_points = GLYPH_INITIAL_POINTS;
+
+    return drawing;
+}
+
+/*
+ * \brief Free a drawing
+ */
+void ass_drawing_free(ASS_Drawing* drawing)
+{
+    if (drawing) {
+        if (drawing->glyph)
+            FT_Done_Glyph((FT_Glyph) drawing->glyph);
+        free(drawing->text);
+    }
+    free(drawing);
+}
+
+/*
+ * \brief Add one ASCII character to the drawing text buffer
+ */
+void ass_drawing_add_char(ASS_Drawing* drawing, char symbol)
+{
+    drawing->text[drawing->i++] = symbol;
+    drawing->text[drawing->i] = 0;
+
+    if (drawing->i + 1 >= drawing->size) {
+        drawing->size *= 2;
+        drawing->text = realloc(drawing->text, drawing->size);
+    }
+}
+
+/*
+ * \brief Create a hashcode for the drawing
+ * XXX: To avoid collisions a better hash algorithm might be useful.
+ */
+void ass_drawing_hash(ASS_Drawing* drawing)
+{
+    drawing->hash = fnv_32a_str(drawing->text, FNV1_32A_INIT);
+}
+
+/*
+ * \brief Convert token list to outline.  Calls the line and curve evaluators.
+ */
+FT_OutlineGlyph *ass_drawing_parse(ASS_Drawing *drawing, int raw_mode)
+{
+    int started = 0;
+    ASS_DrawingToken *token;
+    FT_Vector pen = {0, 0};
+
+    if (!drawing->glyph)
+        return NULL;
+
+    drawing->tokens = drawing_tokenize(drawing->text);
+    drawing_prepare(drawing);
+
+    token = drawing->tokens;
+    while (token) {
+        // Draw something according to current command
+        switch (token->type) {
+        case TOKEN_MOVE_NC:
+            pen = token->point;
+            translate_point(drawing, &pen);
+            token = token->next;
+            break;
+        case TOKEN_MOVE:
+            pen = token->point;
+            translate_point(drawing, &pen);
+            if (started) {
+                drawing_close_shape(drawing);
+                started = 0;
+            }
+            token = token->next;
+            break;
+        case TOKEN_LINE: {
+            FT_Vector to;
+            to = token->point;
+            translate_point(drawing, &to);
+            if (!started) drawing_add_point(drawing, &pen);
+            drawing_add_point(drawing, &to);
+            started = 1;
+            token = token->next;
+            break;
+        }
+        case TOKEN_CUBIC_BEZIER:
+            if (token_check_values(token, 3, TOKEN_CUBIC_BEZIER) &&
+                token->prev) {
+                drawing_evaluate_curve(drawing, token->prev, 0, started);
+                token = token->next;
+                token = token->next;
+                token = token->next;
+                started = 1;
+            } else
+                token = token->next;
+            break;
+        case TOKEN_B_SPLINE:
+            if (token_check_values(token, 3, TOKEN_B_SPLINE) &&
+                token->prev) {
+                drawing_evaluate_curve(drawing, token->prev, 1, started);
+                token = token->next;
+                started = 1;
+            } else
+                token = token->next;
+            break;
+        default:
+            token = token->next;
+            break;
+        }
+    }
+
+    drawing_finish(drawing, raw_mode);
+    drawing_free_tokens(drawing->tokens);
+    return &drawing->glyph;
+}
diff --git a/libass/ass_drawing.h b/libass/ass_drawing.h
new file mode 100644 (file)
index 0000000..913588e
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2009 Grigori Goronzy <greg@geekmind.org>
+ *
+ * This file is part of libass.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef LIBASS_DRAWING_H
+#define LIBASS_DRAWING_H
+
+#include <ft2build.h>
+#include FT_GLYPH_H
+
+#include "ass.h"
+
+#define DRAWING_INITIAL_SIZE 256
+
+typedef enum {
+    TOKEN_MOVE,
+    TOKEN_MOVE_NC,
+    TOKEN_LINE,
+    TOKEN_CUBIC_BEZIER,
+    TOKEN_CONIC_BEZIER,
+    TOKEN_B_SPLINE,
+    TOKEN_EXTEND_SPLINE,
+    TOKEN_CLOSE
+} ASS_TokenType;
+
+typedef struct ass_drawing_token {
+    ASS_TokenType type;
+    FT_Vector point;
+    struct ass_drawing_token *next;
+    struct ass_drawing_token *prev;
+} ASS_DrawingToken;
+
+typedef struct {
+    char *text; // drawing string
+    int i;      // text index
+    int scale;  // scale (1-64) for subpixel accuracy
+    double pbo; // drawing will be shifted in y direction by this amount
+    double scale_x;     // FontScaleX
+    double scale_y;     // FontScaleY
+    int asc;            // ascender
+    int desc;           // descender
+    FT_OutlineGlyph glyph;  // the "fake" glyph created for later rendering
+    int hash;           // hash value (for caching)
+
+    // private
+    FT_Library ftlibrary;   // FT library instance, needed for font ops
+    ASS_Library *library;
+    int size;           // current buffer size
+    ASS_DrawingToken *tokens;    // tokenized drawing
+    int max_points;     // current maximum size
+    int max_contours;
+    double point_scale_x;
+    double point_scale_y;
+} ASS_Drawing;
+
+ASS_Drawing *ass_drawing_new(void *fontconfig_priv, ASS_Font *font,
+                             ASS_Hinting hint, FT_Library lib);
+void ass_drawing_free(ASS_Drawing* drawing);
+void ass_drawing_add_char(ASS_Drawing* drawing, char symbol);
+void ass_drawing_hash(ASS_Drawing* drawing);
+FT_OutlineGlyph *ass_drawing_parse(ASS_Drawing *drawing, int raw_mode);
+
+#endif /* LIBASS_DRAWING_H */
index 73ee1c4..7db1f07 100644 (file)
@@ -1,5 +1,3 @@
-// -*- c-basic-offset: 8; indent-tabs-mode: t -*-
-// vim:ts=8:sw=8:noet:ai:
 /*
  * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
  *
@@ -28,6 +26,7 @@
 #include FT_SYNTHESIS_H
 #include FT_GLYPH_H
 #include FT_TRUETYPE_TABLES_H
+#include FT_OUTLINE_H
 
 #include "ass.h"
 #include "ass_library.h"
 #include "ass_cache.h"
 #include "ass_fontconfig.h"
 #include "ass_utils.h"
-#include "mputils.h"
 
 /**
  * Select Microfost Unicode CharMap, if the font has one.
  * Otherwise, let FreeType decide.
  */
-static void charmap_magic(FT_Face face)
+static void charmap_magic(ASS_Library *library, FT_Face face)
 {
-       int i;
-       for (i = 0; i < face->num_charmaps; ++i) {
-               FT_CharMap cmap = face->charmaps[i];
-               unsigned pid = cmap->platform_id;
-               unsigned eid = cmap->encoding_id;
-               if (pid == 3 /*microsoft*/ && (eid == 1 /*unicode bmp*/ || eid == 10 /*full unicode*/)) {
-                       FT_Set_Charmap(face, cmap);
-                       return;
-               }
-       }
-
-       if (!face->charmap) {
-               if (face->num_charmaps == 0) {
-                       mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_NoCharmaps);
-                       return;
-               }
-               mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_NoCharmapAutodetected);
-               FT_Set_Charmap(face, face->charmaps[0]);
-               return;
-       }
+    int i;
+    for (i = 0; i < face->num_charmaps; ++i) {
+        FT_CharMap cmap = face->charmaps[i];
+        unsigned pid = cmap->platform_id;
+        unsigned eid = cmap->encoding_id;
+        if (pid == 3 /*microsoft */
+            && (eid == 1 /*unicode bmp */
+                || eid == 10 /*full unicode */ )) {
+            FT_Set_Charmap(face, cmap);
+            return;
+        }
+    }
+
+    if (!face->charmap) {
+        if (face->num_charmaps == 0) {
+            ass_msg(library, MSGL_WARN, "Font face with no charmaps");
+            return;
+        }
+        ass_msg(library, MSGL_WARN,
+                "No charmap autodetected, trying the first one");
+        FT_Set_Charmap(face, face->charmaps[0]);
+        return;
+    }
 }
 
-static void update_transform(ass_font_t* font)
+static void update_transform(ASS_Font *font)
 {
-       int i;
-       FT_Matrix m;
-       m.xx = double_to_d16(font->scale_x);
-       m.yy = double_to_d16(font->scale_y);
-       m.xy = m.yx = 0;
-       for (i = 0; i < font->n_faces; ++i)
-               FT_Set_Transform(font->faces[i], &m, &font->v);
+    int i;
+    FT_Matrix m;
+    m.xx = double_to_d16(font->scale_x);
+    m.yy = double_to_d16(font->scale_y);
+    m.xy = m.yx = 0;
+    for (i = 0; i < font->n_faces; ++i)
+        FT_Set_Transform(font->faces[i], &m, &font->v);
 }
 
 /**
  * \brief find a memory font by name
  */
-static int find_font(ass_library_t* library, char* name)
+static int find_font(ASS_Library *library, char *name)
 {
-       int i;
-       for (i = 0; i < library->num_fontdata; ++i)
-               if (strcasecmp(name, library->fontdata[i].name) == 0)
-                       return i;
-       return -1;
+    int i;
+    for (i = 0; i < library->num_fontdata; ++i)
+        if (strcasecmp(name, library->fontdata[i].name) == 0)
+            return i;
+    return -1;
 }
 
 static void face_set_size(FT_Face face, double size);
 
 static void buggy_font_workaround(FT_Face face)
 {
-       // Some fonts have zero Ascender/Descender fields in 'hhea' table.
-       // In this case, get the information from 'os2' table or, as
-       // a last resort, from face.bbox.
-       if (face->ascender + face->descender == 0 || face->height == 0) {
-               TT_OS2 *os2 = FT_Get_Sfnt_Table(face, ft_sfnt_os2);
-               if (os2) {
-                       face->ascender = os2->sTypoAscender;
-                       face->descender = os2->sTypoDescender;
-                       face->height = face->ascender - face->descender;
-               } else {
-                       face->ascender = face->bbox.yMax;
-                       face->descender = face->bbox.yMin;
-                       face->height = face->ascender - face->descender;
-               }
-       }
+    // Some fonts have zero Ascender/Descender fields in 'hhea' table.
+    // In this case, get the information from 'os2' table or, as
+    // a last resort, from face.bbox.
+    if (face->ascender + face->descender == 0 || face->height == 0) {
+        TT_OS2 *os2 = FT_Get_Sfnt_Table(face, ft_sfnt_os2);
+        if (os2) {
+            face->ascender = os2->sTypoAscender;
+            face->descender = os2->sTypoDescender;
+            face->height = face->ascender - face->descender;
+        } else {
+            face->ascender = face->bbox.yMax;
+            face->descender = face->bbox.yMin;
+            face->height = face->ascender - face->descender;
+        }
+    }
 }
 
 /**
- * \brief Select a face with the given charcode and add it to ass_font_t
+ * \brief Select a face with the given charcode and add it to ASS_Font
  * \return index of the new face in font->faces, -1 if failed
  */
-static int add_face(void* fc_priv, ass_font_t* font, uint32_t ch)
+static int add_face(void *fc_priv, ASS_Font *font, uint32_t ch)
 {
-       char* path;
-       int index;
-       FT_Face face;
-       int error;
-       int mem_idx;
-
-       if (font->n_faces == ASS_FONT_MAX_FACES)
-               return -1;
-
-       path = fontconfig_select(fc_priv, font->desc.family, font->desc.treat_family_as_pattern, font->desc.bold,
-                                             font->desc.italic, &index, ch);
-       if (!path)
-               return -1;
-
-       mem_idx = find_font(font->library, path);
-       if (mem_idx >= 0) {
-               error = FT_New_Memory_Face(font->ftlibrary, (unsigned char*)font->library->fontdata[mem_idx].data,
-                                          font->library->fontdata[mem_idx].size, 0, &face);
-               if (error) {
-                       mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_ErrorOpeningMemoryFont, path);
-                       return -1;
-               }
-       } else {
-               error = FT_New_Face(font->ftlibrary, path, index, &face);
-               if (error) {
-                       mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_ErrorOpeningFont, path, index);
-                       return -1;
-               }
-       }
-       charmap_magic(face);
-       buggy_font_workaround(face);
-
-       font->faces[font->n_faces++] = face;
-       update_transform(font);
-       face_set_size(face, font->size);
-       return font->n_faces - 1;
+    char *path;
+    int index;
+    FT_Face face;
+    int error;
+    int mem_idx;
+
+    if (font->n_faces == ASS_FONT_MAX_FACES)
+        return -1;
+
+    path =
+        fontconfig_select(font->library, fc_priv, font->desc.family,
+                          font->desc.treat_family_as_pattern,
+                          font->desc.bold, font->desc.italic, &index, ch);
+    if (!path)
+        return -1;
+
+    mem_idx = find_font(font->library, path);
+    if (mem_idx >= 0) {
+        error =
+            FT_New_Memory_Face(font->ftlibrary,
+                               (unsigned char *) font->library->
+                               fontdata[mem_idx].data,
+                               font->library->fontdata[mem_idx].size, 0,
+                               &face);
+        if (error) {
+            ass_msg(font->library, MSGL_WARN,
+                    "Error opening memory font: '%s'", path);
+            free(path);
+            return -1;
+        }
+    } else {
+        error = FT_New_Face(font->ftlibrary, path, index, &face);
+        if (error) {
+            ass_msg(font->library, MSGL_WARN,
+                    "Error opening font: '%s', %d", path, index);
+            free(path);
+            return -1;
+        }
+    }
+    charmap_magic(font->library, face);
+    buggy_font_workaround(face);
+
+    font->faces[font->n_faces++] = face;
+    update_transform(font);
+    face_set_size(face, font->size);
+    free(path);
+    return font->n_faces - 1;
 }
 
 /**
- * \brief Create a new ass_font_t according to "desc" argument
+ * \brief Create a new ASS_Font according to "desc" argument
  */
-ass_font_t* ass_font_new(ass_library_t* library, FT_Library ftlibrary, void* fc_priv, ass_font_desc_t* desc)
+ASS_Font *ass_font_new(void *font_cache, ASS_Library *library,
+                       FT_Library ftlibrary, void *fc_priv,
+                       ASS_FontDesc *desc)
 {
-       int error;
-       ass_font_t* fontp;
-       ass_font_t font;
-
-       fontp = ass_font_cache_find(desc);
-       if (fontp)
-               return fontp;
-
-       font.library = library;
-       font.ftlibrary = ftlibrary;
-       font.n_faces = 0;
-       font.desc.family = strdup(desc->family);
-       font.desc.treat_family_as_pattern = desc->treat_family_as_pattern;
-       font.desc.bold = desc->bold;
-       font.desc.italic = desc->italic;
-
-       font.scale_x = font.scale_y = 1.;
-       font.v.x = font.v.y = 0;
-       font.size = 0.;
-
-       error = add_face(fc_priv, &font, 0);
-       if (error == -1) {
-               free(font.desc.family);
-               return 0;
-       } else
-               return ass_font_cache_add(&font);
+    int error;
+    ASS_Font *fontp;
+    ASS_Font font;
+
+    fontp = ass_font_cache_find((Hashmap *) font_cache, desc);
+    if (fontp)
+        return fontp;
+
+    font.library = library;
+    font.ftlibrary = ftlibrary;
+    font.n_faces = 0;
+    font.desc.family = strdup(desc->family);
+    font.desc.treat_family_as_pattern = desc->treat_family_as_pattern;
+    font.desc.bold = desc->bold;
+    font.desc.italic = desc->italic;
+
+    font.scale_x = font.scale_y = 1.;
+    font.v.x = font.v.y = 0;
+    font.size = 0.;
+
+    error = add_face(fc_priv, &font, 0);
+    if (error == -1) {
+        free(font.desc.family);
+        return 0;
+    } else
+        return ass_font_cache_add((Hashmap *) font_cache, &font);
 }
 
 /**
  * \brief Set font transformation matrix and shift vector
  **/
-void ass_font_set_transform(ass_font_t* font, double scale_x, double scale_y, FT_Vector* v)
+void ass_font_set_transform(ASS_Font *font, double scale_x,
+                            double scale_y, FT_Vector *v)
 {
-       font->scale_x = scale_x;
-       font->scale_y = scale_y;
-       font->v.x = v->x;
-       font->v.y = v->y;
-       update_transform(font);
+    font->scale_x = scale_x;
+    font->scale_y = scale_y;
+    if (v) {
+        font->v.x = v->x;
+        font->v.y = v->y;
+    }
+    update_transform(font);
 }
 
 static void face_set_size(FT_Face face, double size)
 {
-#if (FREETYPE_MAJOR > 2) || ((FREETYPE_MAJOR == 2) && (FREETYPE_MINOR > 1))
-       TT_HoriHeader *hori = FT_Get_Sfnt_Table(face, ft_sfnt_hhea);
-       TT_OS2 *os2 = FT_Get_Sfnt_Table(face, ft_sfnt_os2);
-       double mscale = 1.;
-       FT_Size_RequestRec rq;
-       FT_Size_Metrics *m = &face->size->metrics;
-       // VSFilter uses metrics from TrueType OS/2 table
-       // The idea was borrowed from asa (http://asa.diac24.net)
-       if (hori && os2) {
-               int hori_height = hori->Ascender - hori->Descender;
-               int os2_height = os2->usWinAscent + os2->usWinDescent;
-               if (hori_height && os2_height)
-                       mscale = (double)hori_height / os2_height;
-       }
-       memset(&rq, 0, sizeof(rq));
-       rq.type = FT_SIZE_REQUEST_TYPE_REAL_DIM;
-       rq.width = 0;
-       rq.height = double_to_d6(size * mscale);
-       rq.horiResolution = rq.vertResolution = 0;
-       FT_Request_Size(face, &rq);
-       m->ascender /= mscale;
-       m->descender /= mscale;
-       m->height /= mscale;
-#else
-       FT_Set_Char_Size(face, 0, double_to_d6(size), 0, 0);
-#endif
+    TT_HoriHeader *hori = FT_Get_Sfnt_Table(face, ft_sfnt_hhea);
+    TT_OS2 *os2 = FT_Get_Sfnt_Table(face, ft_sfnt_os2);
+    double mscale = 1.;
+    FT_Size_RequestRec rq;
+    FT_Size_Metrics *m = &face->size->metrics;
+    // VSFilter uses metrics from TrueType OS/2 table
+    // The idea was borrowed from asa (http://asa.diac24.net)
+    if (hori && os2) {
+        int hori_height = hori->Ascender - hori->Descender;
+        int os2_height = os2->usWinAscent + os2->usWinDescent;
+        if (hori_height && os2_height)
+            mscale = (double) hori_height / os2_height;
+    }
+    memset(&rq, 0, sizeof(rq));
+    rq.type = FT_SIZE_REQUEST_TYPE_REAL_DIM;
+    rq.width = 0;
+    rq.height = double_to_d6(size * mscale);
+    rq.horiResolution = rq.vertResolution = 0;
+    FT_Request_Size(face, &rq);
+    m->ascender /= mscale;
+    m->descender /= mscale;
+    m->height /= mscale;
 }
 
 /**
  * \brief Set font size
  **/
-void ass_font_set_size(ass_font_t* font, double size)
+void ass_font_set_size(ASS_Font *font, double size)
 {
-       int i;
-       if (font->size != size) {
-               font->size = size;
-               for (i = 0; i < font->n_faces; ++i)
-                       face_set_size(font->faces[i], size);
-       }
+    int i;
+    if (font->size != size) {
+        font->size = size;
+        for (i = 0; i < font->n_faces; ++i)
+            face_set_size(font->faces[i], size);
+    }
 }
 
 /**
@@ -247,125 +260,273 @@ void ass_font_set_size(ass_font_t* font, double size)
  * \param ch character code
  * The values are extracted from the font face that provides glyphs for the given character
  **/
-void ass_font_get_asc_desc(ass_font_t* font, uint32_t ch, int* asc, int* desc)
+void ass_font_get_asc_desc(ASS_Font *font, uint32_t ch, int *asc,
+                           int *desc)
 {
-       int i;
-       for (i = 0; i < font->n_faces; ++i) {
-               FT_Face face = font->faces[i];
-               if (FT_Get_Char_Index(face, ch)) {
-                       *asc = face->size->metrics.ascender;
-                       *desc = - face->size->metrics.descender;
-                       return;
-               }
-       }
-
-       *asc = *desc = 0;
+    int i;
+    for (i = 0; i < font->n_faces; ++i) {
+        FT_Face face = font->faces[i];
+        TT_OS2 *os2 = FT_Get_Sfnt_Table(face, ft_sfnt_os2);
+        if (FT_Get_Char_Index(face, ch)) {
+            int y_scale = face->size->metrics.y_scale;
+            if (os2) {
+                *asc = FT_MulFix(os2->usWinAscent, y_scale);
+                *desc = FT_MulFix(os2->usWinDescent, y_scale);
+            } else {
+                *asc = FT_MulFix(face->ascender, y_scale);
+                *desc = FT_MulFix(-face->descender, y_scale);
+            }
+            return;
+        }
+    }
+
+    *asc = *desc = 0;
+}
+
+/*
+ * Strike a glyph with a horizontal line; it's possible to underline it
+ * and/or strike through it.  For the line's position and size, truetype
+ * tables are consulted.  Obviously this relies on the data in the tables
+ * being accurate.
+ *
+ */
+static int ass_strike_outline_glyph(FT_Face face, ASS_Font *font,
+                                    FT_Glyph glyph, int under, int through)
+{
+    TT_OS2 *os2 = FT_Get_Sfnt_Table(face, ft_sfnt_os2);
+    TT_Postscript *ps = FT_Get_Sfnt_Table(face, ft_sfnt_post);
+    FT_Outline *ol = &((FT_OutlineGlyph) glyph)->outline;
+    int bear, advance, y_scale, i, dir;
+
+    if (!under && !through)
+        return 0;
+
+    // Grow outline
+    i = (under ? 4 : 0) + (through ? 4 : 0);
+    ol->points = realloc(ol->points, sizeof(FT_Vector) *
+                         (ol->n_points + i));
+    ol->tags = realloc(ol->tags, ol->n_points + i);
+    i = !!under + !!through;
+    ol->contours = realloc(ol->contours, sizeof(short) *
+                           (ol->n_contours + i));
+
+    // If the bearing is negative, the glyph starts left of the current
+    // pen position
+    bear = FFMIN(face->glyph->metrics.horiBearingX, 0);
+    // We're adding half a pixel to avoid small gaps
+    advance = d16_to_d6(glyph->advance.x) + 32;
+    y_scale = face->size->metrics.y_scale;
+
+    // Reverse drawing direction for non-truetype fonts
+    dir = FT_Outline_Get_Orientation(ol);
+
+    // Add points to the outline
+    if (under && ps) {
+        int pos, size;
+        pos = FT_MulFix(ps->underlinePosition, y_scale * font->scale_y);
+        size = FT_MulFix(ps->underlineThickness,
+                         y_scale * font->scale_y / 2);
+
+        if (pos > 0 || size <= 0)
+            return 1;
+
+        FT_Vector points[4] = {
+            {.x = bear,      .y = pos + size},
+            {.x = advance,   .y = pos + size},
+            {.x = advance,   .y = pos - size},
+            {.x = bear,      .y = pos - size},
+        };
+
+        if (dir == FT_ORIENTATION_TRUETYPE) {
+            for (i = 0; i < 4; i++) {
+                ol->points[ol->n_points] = points[i];
+                ol->tags[ol->n_points++] = 1;
+            }
+        } else {
+            for (i = 3; i >= 0; i--) {
+                ol->points[ol->n_points] = points[i];
+                ol->tags[ol->n_points++] = 1;
+            }
+        }
+
+        ol->contours[ol->n_contours++] = ol->n_points - 1;
+    }
+
+    if (through && os2) {
+        int pos, size;
+        pos = FT_MulFix(os2->yStrikeoutPosition, y_scale * font->scale_y);
+        size = FT_MulFix(os2->yStrikeoutSize, y_scale * font->scale_y / 2);
+
+        if (pos < 0 || size <= 0)
+            return 1;
+
+        FT_Vector points[4] = {
+            {.x = bear,      .y = pos + size},
+            {.x = advance,   .y = pos + size},
+            {.x = advance,   .y = pos - size},
+            {.x = bear,      .y = pos - size},
+        };
+
+        if (dir == FT_ORIENTATION_TRUETYPE) {
+            for (i = 0; i < 4; i++) {
+                ol->points[ol->n_points] = points[i];
+                ol->tags[ol->n_points++] = 1;
+            }
+        } else {
+            for (i = 3; i >= 0; i--) {
+                ol->points[ol->n_points] = points[i];
+                ol->tags[ol->n_points++] = 1;
+            }
+        }
+
+        ol->contours[ol->n_contours++] = ol->n_points - 1;
+    }
+
+    return 0;
+}
+
+/**
+ * Slightly embold a glyph without touching its metrics
+ */
+static void ass_glyph_embolden(FT_GlyphSlot slot)
+{
+    int str;
+
+    if (slot->format != FT_GLYPH_FORMAT_OUTLINE)
+        return;
+
+    str = FT_MulFix(slot->face->units_per_EM,
+                    slot->face->size->metrics.y_scale) / 64;
+
+    FT_Outline_Embolden(&slot->outline, str);
 }
 
 /**
  * \brief Get a glyph
  * \param ch character code
  **/
-FT_Glyph ass_font_get_glyph(void* fontconfig_priv, ass_font_t* font, uint32_t ch, ass_hinting_t hinting)
+FT_Glyph ass_font_get_glyph(void *fontconfig_priv, ASS_Font *font,
+                            uint32_t ch, ASS_Hinting hinting, int deco)
 {
-       int error;
-       int index = 0;
-       int i;
-       FT_Glyph glyph;
-       FT_Face face = 0;
-       int flags = 0;
-
-       if (ch < 0x20)
-               return 0;
-       if (font->n_faces == 0)
-               return 0;
-
-       for (i = 0; i < font->n_faces; ++i) {
-               face = font->faces[i];
-               index = FT_Get_Char_Index(face, ch);
-               if (index)
-                       break;
-       }
+    int error;
+    int index = 0;
+    int i;
+    FT_Glyph glyph;
+    FT_Face face = 0;
+    int flags = 0;
+
+    if (ch < 0x20)
+        return 0;
+    // Handle NBSP like a regular space when rendering the glyph
+    if (ch == 0xa0)
+        ch = ' ';
+    if (font->n_faces == 0)
+        return 0;
+
+    for (i = 0; i < font->n_faces; ++i) {
+        face = font->faces[i];
+        index = FT_Get_Char_Index(face, ch);
+        if (index)
+            break;
+    }
 
 #ifdef CONFIG_FONTCONFIG
-       if (index == 0) {
-               int face_idx;
-               mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_GlyphNotFoundReselectingFont,
-                      ch, font->desc.family, font->desc.bold, font->desc.italic);
-               face_idx = add_face(fontconfig_priv, font, ch);
-               if (face_idx >= 0) {
-                       face = font->faces[face_idx];
-                       index = FT_Get_Char_Index(face, ch);
-                       if (index == 0) {
-                               mp_msg(MSGT_ASS, MSGL_ERR, MSGTR_LIBASS_GlyphNotFound,
-                                      ch, font->desc.family, font->desc.bold, font->desc.italic);
-                       }
-               }
-       }
-#endif
-
-       switch (hinting) {
-       case ASS_HINTING_NONE: flags = FT_LOAD_NO_HINTING; break;
-       case ASS_HINTING_LIGHT: flags = FT_LOAD_FORCE_AUTOHINT | FT_LOAD_TARGET_LIGHT; break;
-       case ASS_HINTING_NORMAL: flags = FT_LOAD_FORCE_AUTOHINT; break;
-       case ASS_HINTING_NATIVE: flags = 0; break;
-       }
-
-       error = FT_Load_Glyph(face, index, FT_LOAD_NO_BITMAP | flags);
-       if (error) {
-               mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_ErrorLoadingGlyph);
-               return 0;
-       }
-
-#if (FREETYPE_MAJOR > 2) || \
-    ((FREETYPE_MAJOR == 2) && (FREETYPE_MINOR >= 2)) || \
-    ((FREETYPE_MAJOR == 2) && (FREETYPE_MINOR == 1) && (FREETYPE_PATCH >= 10))
-// FreeType >= 2.1.10 required
-       if (!(face->style_flags & FT_STYLE_FLAG_ITALIC) &&
-                       (font->desc.italic > 55)) {
-               FT_GlyphSlot_Oblique(face->glyph);
-       }
+    if (index == 0) {
+        int face_idx;
+        ass_msg(font->library, MSGL_INFO,
+                "Glyph 0x%X not found, selecting one more "
+                "font for (%s, %d, %d)", ch, font->desc.family,
+                font->desc.bold, font->desc.italic);
+        face_idx = add_face(fontconfig_priv, font, ch);
+        if (face_idx >= 0) {
+            face = font->faces[face_idx];
+            index = FT_Get_Char_Index(face, ch);
+            if (index == 0) {
+                ass_msg(font->library, MSGL_ERR,
+                        "Glyph 0x%X not found in font for (%s, %d, %d)",
+                        ch, font->desc.family, font->desc.bold,
+                        font->desc.italic);
+            }
+        }
+    }
 #endif
-       error = FT_Get_Glyph(face->glyph, &glyph);
-       if (error) {
-               mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_ErrorLoadingGlyph);
-               return 0;
-       }
 
-       return glyph;
+    switch (hinting) {
+    case ASS_HINTING_NONE:
+        flags = FT_LOAD_NO_HINTING;
+        break;
+    case ASS_HINTING_LIGHT:
+        flags = FT_LOAD_FORCE_AUTOHINT | FT_LOAD_TARGET_LIGHT;
+        break;
+    case ASS_HINTING_NORMAL:
+        flags = FT_LOAD_FORCE_AUTOHINT;
+        break;
+    case ASS_HINTING_NATIVE:
+        flags = 0;
+        break;
+    }
+
+    error = FT_Load_Glyph(face, index, FT_LOAD_NO_BITMAP | flags);
+    if (error) {
+        ass_msg(font->library, MSGL_WARN, "Error loading glyph, index %d",
+                index);
+        return 0;
+    }
+    if (!(face->style_flags & FT_STYLE_FLAG_ITALIC) &&
+        (font->desc.italic > 55)) {
+        FT_GlyphSlot_Oblique(face->glyph);
+    }
+
+    if (!(face->style_flags & FT_STYLE_FLAG_BOLD) &&
+        (font->desc.bold > 80)) {
+        ass_glyph_embolden(face->glyph);
+    }
+    error = FT_Get_Glyph(face->glyph, &glyph);
+    if (error) {
+        ass_msg(font->library, MSGL_WARN, "Error loading glyph, index %d",
+                index);
+        return 0;
+    }
+
+    ass_strike_outline_glyph(face, font, glyph, deco & DECO_UNDERLINE,
+                             deco & DECO_STRIKETHROUGH);
+
+    return glyph;
 }
 
 /**
  * \brief Get kerning for the pair of glyphs.
  **/
-FT_Vector ass_font_get_kerning(ass_font_t* font, uint32_t c1, uint32_t c2)
+FT_Vector ass_font_get_kerning(ASS_Font *font, uint32_t c1, uint32_t c2)
 {
-       FT_Vector v = {0, 0};
-       int i;
-
-       for (i = 0; i < font->n_faces; ++i) {
-               FT_Face face = font->faces[i];
-               int i1 = FT_Get_Char_Index(face, c1);
-               int i2 = FT_Get_Char_Index(face, c2);
-               if (i1 && i2) {
-                       if (FT_HAS_KERNING(face))
-                               FT_Get_Kerning(face, i1, i2, FT_KERNING_DEFAULT, &v);
-                       return v;
-               }
-               if (i1 || i2) // these glyphs are from different font faces, no kerning information
-                       return v;
-       }
-       return v;
+    FT_Vector v = { 0, 0 };
+    int i;
+
+    for (i = 0; i < font->n_faces; ++i) {
+        FT_Face face = font->faces[i];
+        int i1 = FT_Get_Char_Index(face, c1);
+        int i2 = FT_Get_Char_Index(face, c2);
+        if (i1 && i2) {
+            if (FT_HAS_KERNING(face))
+                FT_Get_Kerning(face, i1, i2, FT_KERNING_DEFAULT, &v);
+            return v;
+        }
+        if (i1 || i2)           // these glyphs are from different font faces, no kerning information
+            return v;
+    }
+    return v;
 }
 
 /**
- * \brief Deallocate ass_font_t
+ * \brief Deallocate ASS_Font
  **/
-void ass_font_free(ass_font_t* font)
+void ass_font_free(ASS_Font *font)
 {
-       int i;
-       for (i = 0; i < font->n_faces; ++i)
-               if (font->faces[i]) FT_Done_Face(font->faces[i]);
-       if (font->desc.family) free(font->desc.family);
-       free(font);
+    int i;
+    for (i = 0; i < font->n_faces; ++i)
+        if (font->faces[i])
+            FT_Done_Face(font->faces[i]);
+    if (font->desc.family)
+        free(font->desc.family);
+    free(font);
 }
index 5204318..ca0c213 100644 (file)
@@ -1,5 +1,3 @@
-// -*- c-basic-offset: 8; indent-tabs-mode: t -*-
-// vim:ts=8:sw=8:noet:ai:
 /*
  * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
  *
 #include "ass.h"
 #include "ass_types.h"
 
-typedef struct ass_font_desc_s {
-       char* family;
-       unsigned bold;
-       unsigned italic;
-       int treat_family_as_pattern;
-} ass_font_desc_t;
-
 #define ASS_FONT_MAX_FACES 10
+#define DECO_UNDERLINE 1
+#define DECO_STRIKETHROUGH 2
+
+typedef struct {
+    char *family;
+    unsigned bold;
+    unsigned italic;
+    int treat_family_as_pattern;
+} ASS_FontDesc;
 
-typedef struct ass_font_s {
-       ass_font_desc_t desc;
-       ass_library_t* library;
-       FT_Library ftlibrary;
-       FT_Face faces[ASS_FONT_MAX_FACES];
-       int n_faces;
-       double scale_x, scale_y; // current transform
-       FT_Vector v; // current shift
-       double size;
-} ass_font_t;
+typedef struct {
+    ASS_FontDesc desc;
+    ASS_Library *library;
+    FT_Library ftlibrary;
+    FT_Face faces[ASS_FONT_MAX_FACES];
+    int n_faces;
+    double scale_x, scale_y;    // current transform
+    FT_Vector v;                // current shift
+    double size;
+} ASS_Font;
 
-ass_font_t* ass_font_new(ass_library_t* library, FT_Library ftlibrary, void* fc_priv, ass_font_desc_t* desc);
-void ass_font_set_transform(ass_font_t* font, double scale_x, double scale_y, FT_Vector* v);
-void ass_font_set_size(ass_font_t* font, double size);
-void ass_font_get_asc_desc(ass_font_t* font, uint32_t ch, int* asc, int* desc);
-FT_Glyph ass_font_get_glyph(void* fontconfig_priv, ass_font_t* font, uint32_t ch, ass_hinting_t hinting);
-FT_Vector ass_font_get_kerning(ass_font_t* font, uint32_t c1, uint32_t c2);
-void ass_font_free(ass_font_t* font);
+// FIXME: passing the hashmap via a void pointer is very ugly.
+ASS_Font *ass_font_new(void *font_cache, ASS_Library *library,
+                       FT_Library ftlibrary, void *fc_priv,
+                       ASS_FontDesc *desc);
+void ass_font_set_transform(ASS_Font *font, double scale_x,
+                            double scale_y, FT_Vector *v);
+void ass_font_set_size(ASS_Font *font, double size);
+void ass_font_get_asc_desc(ASS_Font *font, uint32_t ch, int *asc,
+                           int *desc);
+FT_Glyph ass_font_get_glyph(void *fontconfig_priv, ASS_Font *font,
+                            uint32_t ch, ASS_Hinting hinting, int flags);
+FT_Vector ass_font_get_kerning(ASS_Font *font, uint32_t c1, uint32_t c2);
+void ass_font_free(ASS_Font *font);
 
-#endif /* LIBASS_FONT_H */
+#endif                          /* LIBASS_FONT_H */
index 2e7b5f0..2a43694 100644 (file)
@@ -1,5 +1,3 @@
-// -*- c-basic-offset: 8; indent-tabs-mode: t -*-
-// vim:ts=8:sw=8:noet:ai:
 /*
  * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
  *
@@ -32,7 +30,7 @@
 #include <ft2build.h>
 #include FT_FREETYPE_H
 
-#include "mputils.h"
+#include "ass_utils.h"
 #include "ass.h"
 #include "ass_library.h"
 #include "ass_fontconfig.h"
 #include <fontconfig/fcfreetype.h>
 #endif
 
-struct fc_instance_s {
+struct fc_instance {
 #ifdef CONFIG_FONTCONFIG
-       FcConfig* config;
+    FcConfig *config;
 #endif
-       char* family_default;
-       char* path_default;
-       int index_default;
+    char *family_default;
+    char *path_default;
+    int index_default;
 };
 
 #ifdef CONFIG_FONTCONFIG
 
-// 4yo fontconfig does not have these.
-// They are only needed for debug output, anyway.
-#ifndef FC_FULLNAME
-#define FC_FULLNAME "fullname"
-#endif
-#ifndef FC_EMBOLDEN
-#define FC_EMBOLDEN "embolden"
-#endif
-
 /**
  * \brief Low-level font selection.
  * \param priv private data
@@ -73,156 +62,162 @@ struct fc_instance_s {
  * \param code: the character that should be present in the font, can be 0
  * \return font file path
 */
-static char* _select_font(fc_instance_t* priv, const char* family, int treat_family_as_pattern,
-                         unsigned bold, unsigned italic, int* index, uint32_t code)
+static char *_select_font(ASS_Library *library, FCInstance *priv,
+                          const char *family, int treat_family_as_pattern,
+                          unsigned bold, unsigned italic, int *index,
+                          uint32_t code)
 {
-       FcBool rc;
-       FcResult result;
-       FcPattern *pat = NULL, *rpat = NULL;
-       int r_index, r_slant, r_weight;
-       FcChar8 *r_family, *r_style, *r_file, *r_fullname;
-       FcBool r_outline, r_embolden;
-       FcCharSet* r_charset;
-       FcFontSet* fset = NULL;
-       int curf;
-       char* retval = NULL;
-       int family_cnt;
-
-       *index = 0;
-
-       if (treat_family_as_pattern)
-               pat = FcNameParse((const FcChar8*)family);
-       else
-               pat = FcPatternCreate();
-
-       if (!pat)
-               goto error;
-
-       if (!treat_family_as_pattern) {
-               FcPatternAddString(pat, FC_FAMILY, (const FcChar8*)family);
-
-               // In SSA/ASS fonts are sometimes referenced by their "full name",
-               // which is usually a concatenation of family name and font
-               // style (ex. Ottawa Bold). Full name is available from
-               // FontConfig pattern element FC_FULLNAME, but it is never
-               // used for font matching.
-               // Therefore, I'm removing words from the end of the name one
-               // by one, and adding shortened names to the pattern. It seems
-               // that the first value (full name in this case) has
-               // precedence in matching.
-               // An alternative approach could be to reimplement FcFontSort
-               // using FC_FULLNAME instead of FC_FAMILY.
-               family_cnt = 1;
-               {
-                       char* s = strdup(family);
-                       char* p = s + strlen(s);
-                       while (--p > s)
-                               if (*p == ' ' || *p == '-') {
-                                       *p = '\0';
-                                       FcPatternAddString(pat, FC_FAMILY, (const FcChar8*)s);
-                                       ++ family_cnt;
-                               }
-                       free(s);
-               }
-       }
-       FcPatternAddBool(pat, FC_OUTLINE, FcTrue);
-       FcPatternAddInteger(pat, FC_SLANT, italic);
-       FcPatternAddInteger(pat, FC_WEIGHT, bold);
-
-       FcDefaultSubstitute(pat);
-
-       rc = FcConfigSubstitute(priv->config, pat, FcMatchPattern);
-       if (!rc)
-               goto error;
-
-       fset = FcFontSort(priv->config, pat, FcTrue, NULL, &result);
-       if (!fset)
-               goto error;
-
-       for (curf = 0; curf < fset->nfont; ++curf) {
-               FcPattern* curp = fset->fonts[curf];
-
-               result = FcPatternGetBool(curp, FC_OUTLINE, 0, &r_outline);
-               if (result != FcResultMatch)
-                       continue;
-               if (r_outline != FcTrue)
-                       continue;
-               if (!code)
-                       break;
-               result = FcPatternGetCharSet(curp, FC_CHARSET, 0, &r_charset);
-               if (result != FcResultMatch)
-                       continue;
-               if (FcCharSetHasChar(r_charset, code))
-                       break;
-       }
-
-       if (curf >= fset->nfont)
-               goto error;
-
-#if (FC_VERSION >= 20297)
-       if (!treat_family_as_pattern) {
-               // Remove all extra family names from original pattern.
-               // After this, FcFontRenderPrepare will select the most relevant family
-               // name in case there are more than one of them.
-               for (; family_cnt > 1; --family_cnt)
-                       FcPatternRemove(pat, FC_FAMILY, family_cnt - 1);
-       }
-#endif
-
-       rpat = FcFontRenderPrepare(priv->config, pat, fset->fonts[curf]);
-       if (!rpat)
-               goto error;
-
-       result = FcPatternGetInteger(rpat, FC_INDEX, 0, &r_index);
-       if (result != FcResultMatch)
-               goto error;
-       *index = r_index;
-
-       result = FcPatternGetString(rpat, FC_FILE, 0, &r_file);
-       if (result != FcResultMatch)
-               goto error;
-       retval = strdup((const char*)r_file);
-
-       result = FcPatternGetString(rpat, FC_FAMILY, 0, &r_family);
-       if (result != FcResultMatch)
-               r_family = NULL;
-
-       result = FcPatternGetString(rpat, FC_FULLNAME, 0, &r_fullname);
-       if (result != FcResultMatch)
-               r_fullname = NULL;
-
-       if (!treat_family_as_pattern &&
-               !(r_family && strcasecmp((const char*)r_family, family) == 0) &&
-           !(r_fullname && strcasecmp((const char*)r_fullname, family) == 0))
-               mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_SelectedFontFamilyIsNotTheRequestedOne,
-                      (const char*)(r_fullname ? r_fullname : r_family), family);
-
-       result = FcPatternGetString(rpat, FC_STYLE, 0, &r_style);
-       if (result != FcResultMatch)
-               r_style = NULL;
-
-       result = FcPatternGetInteger(rpat, FC_SLANT, 0, &r_slant);
-       if (result != FcResultMatch)
-               r_slant = 0;
-
-       result = FcPatternGetInteger(rpat, FC_WEIGHT, 0, &r_weight);
-       if (result != FcResultMatch)
-               r_weight = 0;
-
-       result = FcPatternGetBool(rpat, FC_EMBOLDEN, 0, &r_embolden);
-       if (result != FcResultMatch)
-               r_embolden = 0;
-
-       mp_msg(MSGT_ASS, MSGL_V, "[ass] Font info: family '%s', style '%s', fullname '%s',"
-              " slant %d, weight %d%s\n",
-              (const char*)r_family, (const char*)r_style, (const char*)r_fullname,
-              r_slant, r_weight, r_embolden ? ", embolden" : "");
-
- error:
-       if (pat) FcPatternDestroy(pat);
-       if (rpat) FcPatternDestroy(rpat);
-       if (fset) FcFontSetDestroy(fset);
-       return retval;
+    FcBool rc;
+    FcResult result;
+    FcPattern *pat = NULL, *rpat = NULL;
+    int r_index, r_slant, r_weight;
+    FcChar8 *r_family, *r_style, *r_file, *r_fullname;
+    FcBool r_outline, r_embolden;
+    FcCharSet *r_charset;
+    FcFontSet *fset = NULL;
+    int curf;
+    char *retval = NULL;
+    int family_cnt = 0;
+
+    *index = 0;
+
+    if (treat_family_as_pattern)
+        pat = FcNameParse((const FcChar8 *) family);
+    else
+        pat = FcPatternCreate();
+
+    if (!pat)
+        goto error;
+
+    if (!treat_family_as_pattern) {
+        FcPatternAddString(pat, FC_FAMILY, (const FcChar8 *) family);
+
+        // In SSA/ASS fonts are sometimes referenced by their "full name",
+        // which is usually a concatenation of family name and font
+        // style (ex. Ottawa Bold). Full name is available from
+        // FontConfig pattern element FC_FULLNAME, but it is never
+        // used for font matching.
+        // Therefore, I'm removing words from the end of the name one
+        // by one, and adding shortened names to the pattern. It seems
+        // that the first value (full name in this case) has
+        // precedence in matching.
+        // An alternative approach could be to reimplement FcFontSort
+        // using FC_FULLNAME instead of FC_FAMILY.
+        family_cnt = 1;
+        {
+            char *s = strdup(family);
+            char *p = s + strlen(s);
+            while (--p > s)
+                if (*p == ' ' || *p == '-') {
+                    *p = '\0';
+                    FcPatternAddString(pat, FC_FAMILY, (const FcChar8 *) s);
+                    ++family_cnt;
+                }
+            free(s);
+        }
+    }
+    FcPatternAddBool(pat, FC_OUTLINE, FcTrue);
+    FcPatternAddInteger(pat, FC_SLANT, italic);
+    FcPatternAddInteger(pat, FC_WEIGHT, bold);
+
+    FcDefaultSubstitute(pat);
+
+    rc = FcConfigSubstitute(priv->config, pat, FcMatchPattern);
+    if (!rc)
+        goto error;
+
+    fset = FcFontSort(priv->config, pat, FcTrue, NULL, &result);
+    if (!fset)
+        goto error;
+
+    for (curf = 0; curf < fset->nfont; ++curf) {
+        FcPattern *curp = fset->fonts[curf];
+
+        result = FcPatternGetBool(curp, FC_OUTLINE, 0, &r_outline);
+        if (result != FcResultMatch)
+            continue;
+        if (r_outline != FcTrue)
+            continue;
+        if (!code)
+            break;
+        result = FcPatternGetCharSet(curp, FC_CHARSET, 0, &r_charset);
+        if (result != FcResultMatch)
+            continue;
+        if (FcCharSetHasChar(r_charset, code))
+            break;
+    }
+
+    if (curf >= fset->nfont)
+        goto error;
+
+    if (!treat_family_as_pattern) {
+        // Remove all extra family names from original pattern.
+        // After this, FcFontRenderPrepare will select the most relevant family
+        // name in case there are more than one of them.
+        for (; family_cnt > 1; --family_cnt)
+            FcPatternRemove(pat, FC_FAMILY, family_cnt - 1);
+    }
+
+    rpat = FcFontRenderPrepare(priv->config, pat, fset->fonts[curf]);
+    if (!rpat)
+        goto error;
+
+    result = FcPatternGetInteger(rpat, FC_INDEX, 0, &r_index);
+    if (result != FcResultMatch)
+        goto error;
+    *index = r_index;
+
+    result = FcPatternGetString(rpat, FC_FILE, 0, &r_file);
+    if (result != FcResultMatch)
+        goto error;
+    retval = strdup((const char *) r_file);
+
+    result = FcPatternGetString(rpat, FC_FAMILY, 0, &r_family);
+    if (result != FcResultMatch)
+        r_family = NULL;
+
+    result = FcPatternGetString(rpat, FC_FULLNAME, 0, &r_fullname);
+    if (result != FcResultMatch)
+        r_fullname = NULL;
+
+    if (!treat_family_as_pattern &&
+        !(r_family && strcasecmp((const char *) r_family, family) == 0) &&
+        !(r_fullname && strcasecmp((const char *) r_fullname, family) == 0))
+        ass_msg(library, MSGL_WARN,
+               "fontconfig: Selected font is not the requested one: "
+               "'%s' != '%s'",
+               (const char *) (r_fullname ? r_fullname : r_family), family);
+
+    result = FcPatternGetString(rpat, FC_STYLE, 0, &r_style);
+    if (result != FcResultMatch)
+        r_style = NULL;
+
+    result = FcPatternGetInteger(rpat, FC_SLANT, 0, &r_slant);
+    if (result != FcResultMatch)
+        r_slant = 0;
+
+    result = FcPatternGetInteger(rpat, FC_WEIGHT, 0, &r_weight);
+    if (result != FcResultMatch)
+        r_weight = 0;
+
+    result = FcPatternGetBool(rpat, FC_EMBOLDEN, 0, &r_embolden);
+    if (result != FcResultMatch)
+        r_embolden = 0;
+
+    ass_msg(library, MSGL_V,
+           "Font info: family '%s', style '%s', fullname '%s',"
+           " slant %d, weight %d%s", (const char *) r_family,
+           (const char *) r_style, (const char *) r_fullname, r_slant,
+           r_weight, r_embolden ? ", embolden" : "");
+
+  error:
+    if (pat)
+        FcPatternDestroy(pat);
+    if (rpat)
+        FcPatternDestroy(rpat);
+    if (fset)
+        FcFontSetDestroy(fset);
+    return retval;
 }
 
 /**
@@ -236,78 +231,52 @@ static char* _select_font(fc_instance_t* priv, const char* family, int treat_fam
  * \param code: the character that should be present in the font, can be 0
  * \return font file path
 */
-char* fontconfig_select(fc_instance_t* priv, const char* family, int treat_family_as_pattern,
-                       unsigned bold, unsigned italic, int* index, uint32_t code)
+char *fontconfig_select(ASS_Library *library, FCInstance *priv,
+                        const char *family, int treat_family_as_pattern,
+                        unsigned bold, unsigned italic, int *index,
+                        uint32_t code)
 {
-       char* res = 0;
-       if (!priv->config) {
-               *index = priv->index_default;
-               return priv->path_default;
-       }
-       if (family && *family)
-               res = _select_font(priv, family, treat_family_as_pattern, bold, italic, index, code);
-       if (!res && priv->family_default) {
-               res = _select_font(priv, priv->family_default, 0, bold, italic, index, code);
-               if (res)
-                       mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_UsingDefaultFontFamily,
-                                       family, bold, italic, res, *index);
-       }
-       if (!res && priv->path_default) {
-               res = priv->path_default;
-               *index = priv->index_default;
-               mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_UsingDefaultFont,
-                      family, bold, italic, res, *index);
-       }
-       if (!res) {
-               res = _select_font(priv, "Arial", 0, bold, italic, index, code);
-               if (res)
-                       mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_UsingArialFontFamily,
-                                       family, bold, italic, res, *index);
-       }
-       if (res)
-               mp_msg(MSGT_ASS, MSGL_V, "fontconfig_select: (%s, %d, %d) -> %s, %d\n",
-                               family, bold, italic, res, *index);
-       return res;
+    char *res = 0;
+    if (!priv->config) {
+        *index = priv->index_default;
+        res = priv->path_default ? strdup(priv->path_default) : 0;
+        return res;
+    }
+    if (family && *family)
+        res =
+            _select_font(library, priv, family, treat_family_as_pattern,
+                         bold, italic, index, code);
+    if (!res && priv->family_default) {
+        res =
+            _select_font(library, priv, priv->family_default, 0, bold,
+                         italic, index, code);
+        if (res)
+            ass_msg(library, MSGL_WARN, "fontconfig_select: Using default "
+                    "font family: (%s, %d, %d) -> %s, %d",
+                    family, bold, italic, res, *index);
+    }
+    if (!res && priv->path_default) {
+        res = strdup(priv->path_default);
+        *index = priv->index_default;
+        ass_msg(library, MSGL_WARN, "fontconfig_select: Using default font: "
+                "(%s, %d, %d) -> %s, %d", family, bold, italic,
+                res, *index);
+    }
+    if (!res) {
+        res = _select_font(library, priv, "Arial", 0, bold, italic,
+                           index, code);
+        if (res)
+            ass_msg(library, MSGL_WARN, "fontconfig_select: Using 'Arial' "
+                    "font family: (%s, %d, %d) -> %s, %d", family, bold,
+                    italic, res, *index);
+    }
+    if (res)
+        ass_msg(library, MSGL_V,
+                "fontconfig_select: (%s, %d, %d) -> %s, %d", family, bold,
+                italic, res, *index);
+    return res;
 }
 
-#if (FC_VERSION < 20402)
-static char* validate_fname(char* name)
-{
-       char* fname;
-       char* p;
-       char* q;
-       unsigned code;
-       int sz = strlen(name);
-
-       q = fname = malloc(sz + 1);
-       p = name;
-       while (*p) {
-               code = utf8_get_char(&p);
-               if (code == 0)
-                       break;
-               if (    (code > 0x7F) ||
-                       (code == '\\') ||
-                       (code == '/') ||
-                       (code == ':') ||
-                       (code == '*') ||
-                       (code == '?') ||
-                       (code == '<') ||
-                       (code == '>') ||
-                       (code == '|') ||
-                       (code == 0))
-               {
-                       *q++ = '_';
-               } else {
-                       *q++ = code;
-               }
-               if (p - name > sz)
-                       break;
-       }
-       *q = 0;
-       return fname;
-}
-#endif
-
 /**
  * \brief Process memory font.
  * \param priv private data
@@ -317,87 +286,55 @@ static char* validate_fname(char* name)
  * With FontConfig >= 2.4.2, builds a font pattern in memory via FT_New_Memory_Face/FcFreeTypeQueryFace.
  * With older FontConfig versions, save the font to ~/.mplayer/fonts.
 */
-static void process_fontdata(fc_instance_t* priv, ass_library_t* library, FT_Library ftlibrary, int idx)
+static void process_fontdata(FCInstance *priv, ASS_Library *library,
+                             FT_Library ftlibrary, int idx)
 {
-       int rc;
-       const char* name = library->fontdata[idx].name;
-       const char* data = library->fontdata[idx].data;
-       int data_size = library->fontdata[idx].size;
-
-#if (FC_VERSION < 20402)
-       struct stat st;
-       char* fname;
-       const char* fonts_dir = library->fonts_dir;
-       char buf[1000];
-       FILE* fp = NULL;
-
-       if (!fonts_dir)
-               return;
-       rc = stat(fonts_dir, &st);
-       if (rc) {
-               int res;
-#ifndef __MINGW32__
-               res = mkdir(fonts_dir, 0700);
-#else
-               res = mkdir(fonts_dir);
-#endif
-               if (res) {
-                       mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FailedToCreateDirectory, fonts_dir);
-               }
-       } else if (!S_ISDIR(st.st_mode)) {
-               mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_NotADirectory, fonts_dir);
-       }
-
-       fname = validate_fname((char*)name);
-
-       snprintf(buf, 1000, "%s/%s", fonts_dir, fname);
-       free(fname);
-
-       fp = fopen(buf, "wb");
-       if (!fp) return;
-
-       fwrite(data, data_size, 1, fp);
-       fclose(fp);
-
-#else // (FC_VERSION >= 20402)
-       FT_Face face;
-       FcPattern* pattern;
-       FcFontSet* fset;
-       FcBool res;
-       int face_index, num_faces = 1;
-
-       for (face_index = 0; face_index < num_faces; ++face_index) {
-               rc = FT_New_Memory_Face(ftlibrary, (unsigned char*)data, data_size, face_index, &face);
-               if (rc) {
-                       mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_ErrorOpeningMemoryFont, name);
-                       return;
-               }
-               num_faces = face->num_faces;
-
-               pattern = FcFreeTypeQueryFace(face, (unsigned char*)name, 0, FcConfigGetBlanks(priv->config));
-               if (!pattern) {
-                       mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FunctionCallFailed, "FcFreeTypeQueryFace");
-                       FT_Done_Face(face);
-                       return;
-               }
-
-               fset = FcConfigGetFonts(priv->config, FcSetSystem); // somehow it failes when asked for FcSetApplication
-               if (!fset) {
-                       mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FunctionCallFailed, "FcConfigGetFonts");
-                       FT_Done_Face(face);
-                       return;
-               }
-
-               res = FcFontSetAdd(fset, pattern);
-               if (!res) {
-                       mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FunctionCallFailed, "FcFontSetAdd");
-                       FT_Done_Face(face);
-                       return;
-               }
-
-               FT_Done_Face(face);
-       }
-#endif
+    int rc;
+    const char *name = library->fontdata[idx].name;
+    const char *data = library->fontdata[idx].data;
+    int data_size = library->fontdata[idx].size;
+
+    FT_Face face;
+    FcPattern *pattern;
+    FcFontSet *fset;
+    FcBool res;
+    int face_index, num_faces = 1;
+
+    for (face_index = 0; face_index < num_faces; ++face_index) {
+        rc = FT_New_Memory_Face(ftlibrary, (unsigned char *) data,
+                                data_size, face_index, &face);
+        if (rc) {
+            ass_msg(library, MSGL_WARN, "Error opening memory font: %s",
+                   name);
+            return;
+        }
+        num_faces = face->num_faces;
+
+        pattern =
+            FcFreeTypeQueryFace(face, (unsigned char *) name, 0,
+                                FcConfigGetBlanks(priv->config));
+        if (!pattern) {
+            ass_msg(library, MSGL_WARN, "%s failed", "FcFreeTypeQueryFace");
+            FT_Done_Face(face);
+            return;
+        }
+
+        fset = FcConfigGetFonts(priv->config, FcSetSystem);     // somehow it failes when asked for FcSetApplication
+        if (!fset) {
+            ass_msg(library, MSGL_WARN, "%s failed", "FcConfigGetFonts");
+            FT_Done_Face(face);
+            return;
+        }
+
+        res = FcFontSetAdd(fset, pattern);
+        if (!res) {
+            ass_msg(library, MSGL_WARN, "%s failed", "FcFontSetAdd");
+            FT_Done_Face(face);
+            return;
+        }
+
+        FT_Done_Face(face);
+    }
 }
 
 /**
@@ -406,113 +343,119 @@ static void process_fontdata(fc_instance_t* priv, ass_library_t* library, FT_Lib
  * \param ftlibrary freetype library object
  * \param family default font family
  * \param path default font path
+ * \param fc whether fontconfig should be used
+ * \param config path to a fontconfig configuration file, or NULL
+ * \param update whether the fontconfig cache should be built/updated
  * \return pointer to fontconfig private data
 */
-fc_instance_t* fontconfig_init(ass_library_t* library, FT_Library ftlibrary, const char* family, const char* path, int fc)
+FCInstance *fontconfig_init(ASS_Library *library,
+                            FT_Library ftlibrary, const char *family,
+                            const char *path, int fc, const char *config,
+                            int update)
 {
-       int rc;
-       fc_instance_t* priv = calloc(1, sizeof(fc_instance_t));
-       const char* dir = library->fonts_dir;
-       int i;
-
-       if (!fc) {
-               mp_msg(MSGT_ASS, MSGL_WARN,
-                      MSGTR_LIBASS_FontconfigDisabledDefaultFontWillBeUsed);
-               goto exit;
-       }
-
-       rc = FcInit();
-       assert(rc);
-
-       priv->config = FcConfigGetCurrent();
-       if (!priv->config) {
-               mp_msg(MSGT_ASS, MSGL_FATAL, MSGTR_LIBASS_FcInitLoadConfigAndFontsFailed);
-               goto exit;
-       }
-
-       for (i = 0; i < library->num_fontdata; ++i)
-               process_fontdata(priv, library, ftlibrary, i);
-
-       if (dir) {
-               if (FcDirCacheValid((const FcChar8 *)dir) == FcFalse)
-                       {
-                               mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_UpdatingFontCache);
-                               if (FcGetVersion() >= 20390 && FcGetVersion() < 20400)
-                                       mp_msg(MSGT_ASS, MSGL_WARN,
-                                              MSGTR_LIBASS_BetaVersionsOfFontconfigAreNotSupported);
-                               // FontConfig >= 2.4.0 updates cache automatically in FcConfigAppFontAddDir()
-                               if (FcGetVersion() < 20390) {
-                                       FcFontSet* fcs;
-                                       FcStrSet* fss;
-                                       fcs = FcFontSetCreate();
-                                       fss = FcStrSetCreate();
-                                       rc = FcStrSetAdd(fss, (const FcChar8*)dir);
-                                       if (!rc) {
-                                               mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FcStrSetAddFailed);
-                                               goto ErrorFontCache;
-                                       }
-
-                                       rc = FcDirScan(fcs, fss, NULL, FcConfigGetBlanks(priv->config),
-                                                      (const FcChar8 *)dir, FcFalse);
-                                       if (!rc) {
-                                               mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FcDirScanFailed);
-                                               goto ErrorFontCache;
-                                       }
-
-                                       rc = FcDirSave(fcs, fss, (const FcChar8 *)dir);
-                                       if (!rc) {
-                                               mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FcDirSave);
-                                               goto ErrorFontCache;
-                                       }
-                               ErrorFontCache:
-                                       ;
-                               }
-                       }
-
-               rc = FcConfigAppFontAddDir(priv->config, (const FcChar8*)dir);
-               if (!rc) {
-                       mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FcConfigAppFontAddDirFailed);
-               }
-       }
-
-       priv->family_default = family ? strdup(family) : NULL;
+    int rc;
+    FCInstance *priv = calloc(1, sizeof(FCInstance));
+    const char *dir = library->fonts_dir;
+    int i;
+
+    if (!fc) {
+        ass_msg(library, MSGL_WARN,
+               "Fontconfig disabled, only default font will be used.");
+        goto exit;
+    }
+
+    priv->config = FcConfigCreate();
+    rc = FcConfigParseAndLoad(priv->config, (unsigned char *) config, FcTrue);
+    if (!rc) {
+        ass_msg(library, MSGL_WARN, "No usable fontconfig configuration "
+                "file found, using fallback.");
+        FcConfigDestroy(priv->config);
+        priv->config = FcInitLoadConfig();
+        rc++;
+    }
+    if (rc && update) {
+        FcConfigBuildFonts(priv->config);
+    }
+
+    if (!rc || !priv->config) {
+        ass_msg(library, MSGL_FATAL,
+                "No valid fontconfig configuration found!");
+        FcConfigDestroy(priv->config);
+        goto exit;
+    }
+
+    for (i = 0; i < library->num_fontdata; ++i)
+        process_fontdata(priv, library, ftlibrary, i);
+
+    if (dir) {
+        ass_msg(library, MSGL_INFO, "Updating font cache");
+
+        rc = FcConfigAppFontAddDir(priv->config, (const FcChar8 *) dir);
+        if (!rc) {
+            ass_msg(library, MSGL_WARN, "%s failed", "FcConfigAppFontAddDir");
+        }
+    }
+
+    priv->family_default = family ? strdup(family) : NULL;
 exit:
-       priv->path_default = path ? strdup(path) : NULL;
-       priv->index_default = 0;
+    priv->path_default = path ? strdup(path) : NULL;
+    priv->index_default = 0;
 
-       return priv;
+    return priv;
 }
 
-#else /* CONFIG_FONTCONFIG */
+int fontconfig_update(FCInstance *priv)
+{
+        return FcConfigBuildFonts(priv->config);
+}
+
+#else                           /* CONFIG_FONTCONFIG */
 
-char* fontconfig_select(fc_instance_t* priv, const char* family, int treat_family_as_pattern,
-                       unsigned bold, unsigned italic, int* index, uint32_t code)
+char *fontconfig_select(ASS_Library *library, FCInstance *priv,
+                        const char *family, int treat_family_as_pattern,
+                        unsigned bold, unsigned italic, int *index,
+                        uint32_t code)
 {
-       *index = priv->index_default;
-       return priv->path_default;
+    *index = priv->index_default;
+    char* res = priv->path_default ? strdup(priv->path_default) : 0;
+    return res;
 }
 
-fc_instance_t* fontconfig_init(ass_library_t* library, FT_Library ftlibrary, const char* family, const char* path, int fc)
+FCInstance *fontconfig_init(ASS_Library *library,
+                            FT_Library ftlibrary, const char *family,
+                            const char *path, int fc, const char *config,
+                            int update)
 {
-       fc_instance_t* priv;
+    FCInstance *priv;
 
-       mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FontconfigDisabledDefaultFontWillBeUsed);
+    ass_msg(library, MSGL_WARN,
+        "Fontconfig disabled, only default font will be used.");
 
-       priv = calloc(1, sizeof(fc_instance_t));
+    priv = calloc(1, sizeof(FCInstance));
 
-       priv->path_default = strdup(path);
-       priv->index_default = 0;
-       return priv;
+    priv->path_default = path ? strdup(path) : 0;
+    priv->index_default = 0;
+    return priv;
 }
 
-#endif
-
-void fontconfig_done(fc_instance_t* priv)
+int fontconfig_update(FCInstance *priv)
 {
-       // don't call FcFini() here, library can still be used by some code
-       if (priv && priv->path_default) free(priv->path_default);
-       if (priv && priv->family_default) free(priv->family_default);
-       if (priv) free(priv);
+    // Do nothing
+    return 1;
 }
 
+#endif
 
+void fontconfig_done(FCInstance *priv)
+{
+#ifdef CONFIG_FONTCONFIG
+    if (priv && priv->config)
+        FcConfigDestroy(priv->config);
+#endif
+    if (priv && priv->path_default)
+        free(priv->path_default);
+    if (priv && priv->family_default)
+        free(priv->family_default);
+    if (priv)
+        free(priv);
+}
index 7780690..ad5b9f0 100644 (file)
@@ -1,5 +1,3 @@
-// -*- c-basic-offset: 8; indent-tabs-mode: t -*-
-// vim:ts=8:sw=8:noet:ai:
 /*
  * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
  *
@@ -25,6 +23,7 @@
 
 #include <stdint.h>
 #include "ass_types.h"
+#include "ass.h"
 #include <ft2build.h>
 #include FT_FREETYPE_H
 
 #include <fontconfig/fontconfig.h>
 #endif
 
-typedef struct fc_instance_s fc_instance_t;
+typedef struct fc_instance FCInstance;
 
-fc_instance_t* fontconfig_init(ass_library_t* library, FT_Library ftlibrary, const char* family, const char* path, int fc);
-char* fontconfig_select(fc_instance_t* priv, const char* family, int treat_family_as_pattern, unsigned bold, unsigned italic, int* index, uint32_t code);
-void fontconfig_done(fc_instance_t* priv);
+FCInstance *fontconfig_init(ASS_Library *library,
+                            FT_Library ftlibrary, const char *family,
+                            const char *path, int fc, const char *config,
+                            int update);
+char *fontconfig_select(ASS_Library *library, FCInstance *priv,
+                        const char *family, int treat_family_as_pattern,
+                        unsigned bold, unsigned italic, int *index,
+                        uint32_t code);
+void fontconfig_done(FCInstance *priv);
+int fontconfig_update(FCInstance *priv);
 
-#endif /* LIBASS_FONTCONFIG_H */
+#endif                          /* LIBASS_FONTCONFIG_H */
index 304f232..53b91af 100644 (file)
@@ -1,5 +1,3 @@
-// -*- c-basic-offset: 8; indent-tabs-mode: t -*-
-// vim:ts=8:sw=8:noet:ai:
 /*
  * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
  *
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <stdarg.h>
 
 #include "ass.h"
 #include "ass_library.h"
+#include "ass_utils.h"
 
+static void ass_msg_handler(int level, const char *fmt, va_list va, void *data)
+{
+    if (level > MSGL_INFO)
+        return;
+    fprintf(stderr, "[ass] ");
+    vfprintf(stderr, fmt, va);
+    fprintf(stderr, "\n");
+}
 
-ass_library_t* ass_library_init(void)
+ASS_Library *ass_library_init(void)
 {
-       return calloc(1, sizeof(ass_library_t));
+    ASS_Library* lib = calloc(1, sizeof(*lib));
+    lib->msg_callback = ass_msg_handler;
+
+    return lib;
 }
 
-void ass_library_done(ass_library_t* priv)
+void ass_library_done(ASS_Library *priv)
 {
-       if (priv) {
-               ass_set_fonts_dir(priv, NULL);
-               ass_set_style_overrides(priv, NULL);
-               ass_clear_fonts(priv);
-               free(priv);
-       }
+    if (priv) {
+        ass_set_fonts_dir(priv, NULL);
+        ass_set_style_overrides(priv, NULL);
+        ass_clear_fonts(priv);
+        free(priv);
+    }
 }
 
-void ass_set_fonts_dir(ass_library_t* priv, const char* fonts_dir)
+void ass_set_fonts_dir(ASS_Library *priv, const char *fonts_dir)
 {
-       if (priv->fonts_dir)
-               free(priv->fonts_dir);
+    if (priv->fonts_dir)
+        free(priv->fonts_dir);
 
-       priv->fonts_dir = fonts_dir ? strdup(fonts_dir) : 0;
+    priv->fonts_dir = fonts_dir ? strdup(fonts_dir) : 0;
 }
 
-void ass_set_extract_fonts(ass_library_t* priv, int extract)
+void ass_set_extract_fonts(ASS_Library *priv, int extract)
 {
-       priv->extract_fonts = !!extract;
+    priv->extract_fonts = !!extract;
 }
 
-void ass_set_style_overrides(ass_library_t* priv, char** list)
+void ass_set_style_overrides(ASS_Library *priv, char **list)
 {
-       char** p;
-       char** q;
-       int cnt;
-
-       if (priv->style_overrides) {
-               for (p = priv->style_overrides; *p; ++p)
-                       free(*p);
-               free(priv->style_overrides);
-       }
-
-       if (!list) return;
-
-       for (p = list, cnt = 0; *p; ++p, ++cnt) {}
-
-       priv->style_overrides = malloc((cnt + 1) * sizeof(char*));
-       for (p = list, q = priv->style_overrides; *p; ++p, ++q)
-               *q = strdup(*p);
-       priv->style_overrides[cnt] = NULL;
+    char **p;
+    char **q;
+    int cnt;
+
+    if (priv->style_overrides) {
+        for (p = priv->style_overrides; *p; ++p)
+            free(*p);
+        free(priv->style_overrides);
+    }
+
+    if (!list)
+        return;
+
+    for (p = list, cnt = 0; *p; ++p, ++cnt) {
+    }
+
+    priv->style_overrides = malloc((cnt + 1) * sizeof(char *));
+    for (p = list, q = priv->style_overrides; *p; ++p, ++q)
+        *q = strdup(*p);
+    priv->style_overrides[cnt] = NULL;
 }
 
 static void grow_array(void **array, int nelem, size_t elsize)
 {
-       if (!(nelem & 31))
-               *array = realloc(*array, (nelem + 32) * elsize);
+    if (!(nelem & 31))
+        *array = realloc(*array, (nelem + 32) * elsize);
 }
 
-void ass_add_font(ass_library_t* priv, char* name, char* data, int size)
+void ass_add_font(ASS_Library *priv, char *name, char *data, int size)
 {
-       int idx = priv->num_fontdata;
-       if (!name || !data || !size)
-               return;
-       grow_array((void**)&priv->fontdata, priv->num_fontdata, sizeof(*priv->fontdata));
+    int idx = priv->num_fontdata;
+    if (!name || !data || !size)
+        return;
+    grow_array((void **) &priv->fontdata, priv->num_fontdata,
+               sizeof(*priv->fontdata));
 
-       priv->fontdata[idx].name = strdup(name);
+    priv->fontdata[idx].name = strdup(name);
 
-       priv->fontdata[idx].data = malloc(size);
-       memcpy(priv->fontdata[idx].data, data, size);
+    priv->fontdata[idx].data = malloc(size);
+    memcpy(priv->fontdata[idx].data, data, size);
 
-       priv->fontdata[idx].size = size;
+    priv->fontdata[idx].size = size;
 
-       priv->num_fontdata ++;
+    priv->num_fontdata++;
 }
 
-void ass_clear_fonts(ass_library_t* priv)
+void ass_clear_fonts(ASS_Library *priv)
+{
+    int i;
+    for (i = 0; i < priv->num_fontdata; ++i) {
+        free(priv->fontdata[i].name);
+        free(priv->fontdata[i].data);
+    }
+    free(priv->fontdata);
+    priv->fontdata = NULL;
+    priv->num_fontdata = 0;
+}
+
+/*
+ * Register a message callback function with libass.  Without setting one,
+ * a default handler is used which prints everything with MSGL_INFO or
+ * higher to the standard output.
+ *
+ * \param msg_cb the callback function
+ * \param data additional data that will be passed to the callback
+ */
+void ass_set_message_cb(ASS_Library *priv,
+                        void (*msg_cb)(int, const char *, va_list, void *),
+                        void *data)
 {
-       int i;
-       for (i = 0; i < priv->num_fontdata; ++i) {
-               free(priv->fontdata[i].name);
-               free(priv->fontdata[i].data);
-       }
-       free(priv->fontdata);
-       priv->fontdata = NULL;
-       priv->num_fontdata = 0;
+    if (msg_cb) {
+        priv->msg_callback = msg_cb;
+        priv->msg_callback_data = data;
+    }
 }
index ecf46f3..e0db5c9 100644 (file)
@@ -1,5 +1,3 @@
-// -*- c-basic-offset: 8; indent-tabs-mode: t -*-
-// vim:ts=8:sw=8:noet:ai:
 /*
  * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
  *
 #ifndef LIBASS_LIBRARY_H
 #define LIBASS_LIBRARY_H
 
-typedef struct ass_fontdata_s {
-       char* name;
-       char* data;
-       int size;
-} ass_fontdata_t;
+#include <stdarg.h>
 
-struct ass_library_s {
-       char* fonts_dir;
-       int extract_fonts;
-       char** style_overrides;
+typedef struct {
+    char *name;
+    char *data;
+    int size;
+} ASS_Fontdata;
 
-       ass_fontdata_t* fontdata;
-       int num_fontdata;
+struct ass_library {
+    char *fonts_dir;
+    int extract_fonts;
+    char **style_overrides;
+
+    ASS_Fontdata *fontdata;
+    int num_fontdata;
+    void (*msg_callback)(int, const char *, va_list, void *);
+    void *msg_callback_data;
 };
 
-#endif /* LIBASS_LIBRARY_H */
+#endif                          /* LIBASS_LIBRARY_H */
diff --git a/libass/ass_parse.c b/libass/ass_parse.c
new file mode 100644 (file)
index 0000000..0ccb5a2
--- /dev/null
@@ -0,0 +1,926 @@
+/*
+ * Copyright (C) 2009 Grigori Goronzy <greg@geekmind.org>
+ *
+ * This file is part of libass.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#include "ass_render.h"
+#include "ass_parse.h"
+
+#define MAX_BE 127
+#define NBSP 0xa0   // unicode non-breaking space character
+
+#define skip_to(x) while ((*p != (x)) && (*p != '}') && (*p != 0)) { ++p;}
+#define skip(x) if (*p == (x)) ++p; else { return p; }
+#define skipopt(x) if (*p == (x)) { ++p; }
+
+/**
+ * \brief Check if starting part of (*p) matches sample.
+ * If true, shift p to the first symbol after the matching part.
+ */
+static inline int mystrcmp(char **p, const char *sample)
+{
+    int len = strlen(sample);
+    if (strncmp(*p, sample, len) == 0) {
+        (*p) += len;
+        return 1;
+    } else
+        return 0;
+}
+
+static void change_font_size(ASS_Renderer *render_priv, double sz)
+{
+    double size = sz * render_priv->font_scale;
+
+    if (size < 1)
+        size = 1;
+    else if (size > render_priv->height * 2)
+        size = render_priv->height * 2;
+
+    ass_font_set_size(render_priv->state.font, size);
+
+    render_priv->state.font_size = sz;
+}
+
+/**
+ * \brief Change current font, using setting from render_priv->state.
+ */
+void update_font(ASS_Renderer *render_priv)
+{
+    unsigned val;
+    ASS_FontDesc desc;
+    desc.family = strdup(render_priv->state.family);
+    desc.treat_family_as_pattern =
+        render_priv->state.treat_family_as_pattern;
+
+    val = render_priv->state.bold;
+    // 0 = normal, 1 = bold, >1 = exact weight
+    if (val == 1 || val == -1)
+        val = 200;              // bold
+    else if (val <= 0)
+        val = 80;               // normal
+    desc.bold = val;
+
+    val = render_priv->state.italic;
+    if (val == 1 || val == -1)
+        val = 110;              // italic
+    else if (val <= 0)
+        val = 0;                // normal
+    desc.italic = val;
+
+    render_priv->state.font =
+        ass_font_new(render_priv->cache.font_cache, render_priv->library,
+                     render_priv->ftlibrary, render_priv->fontconfig_priv,
+                     &desc);
+    free(desc.family);
+
+    if (render_priv->state.font)
+        change_font_size(render_priv, render_priv->state.font_size);
+}
+
+/**
+ * \brief Change border width
+ * negative value resets border to style value
+ */
+void change_border(ASS_Renderer *render_priv, double border_x,
+                   double border_y)
+{
+    int bord;
+    if (!render_priv->state.font)
+        return;
+
+    if (border_x < 0 && border_y < 0) {
+        if (render_priv->state.style->BorderStyle == 1 ||
+            render_priv->state.style->BorderStyle == 3)
+            border_x = border_y = render_priv->state.style->Outline;
+        else
+            border_x = border_y = 1.;
+    }
+
+    render_priv->state.border_x = border_x;
+    render_priv->state.border_y = border_y;
+
+    bord = 64 * border_x * render_priv->border_scale;
+    if (bord > 0 && border_x == border_y) {
+        if (!render_priv->state.stroker) {
+            int error;
+            error =
+                FT_Stroker_New(render_priv->ftlibrary,
+                               &render_priv->state.stroker);
+            if (error) {
+                ass_msg(render_priv->library, MSGL_V,
+                        "failed to get stroker");
+                render_priv->state.stroker = 0;
+            }
+        }
+        if (render_priv->state.stroker)
+            FT_Stroker_Set(render_priv->state.stroker, bord,
+                           FT_STROKER_LINECAP_ROUND,
+                           FT_STROKER_LINEJOIN_ROUND, 0);
+    } else {
+        FT_Stroker_Done(render_priv->state.stroker);
+        render_priv->state.stroker = 0;
+    }
+}
+
+/**
+ * \brief Calculate a weighted average of two colors
+ * calculates c1*(1-a) + c2*a, but separately for each component except alpha
+ */
+static void change_color(uint32_t *var, uint32_t new, double pwr)
+{
+    (*var) = ((uint32_t) (_r(*var) * (1 - pwr) + _r(new) * pwr) << 24) +
+        ((uint32_t) (_g(*var) * (1 - pwr) + _g(new) * pwr) << 16) +
+        ((uint32_t) (_b(*var) * (1 - pwr) + _b(new) * pwr) << 8) + _a(*var);
+}
+
+// like change_color, but for alpha component only
+inline void change_alpha(uint32_t *var, uint32_t new, double pwr)
+{
+    *var =
+        (_r(*var) << 24) + (_g(*var) << 16) + (_b(*var) << 8) +
+        (uint32_t) (_a(*var) * (1 - pwr) + _a(new) * pwr);
+}
+
+/**
+ * \brief Multiply two alpha values
+ * \param a first value
+ * \param b second value
+ * \return result of multiplication
+ * Parameters and result are limited by 0xFF.
+ */
+inline uint32_t mult_alpha(uint32_t a, uint32_t b)
+{
+    return 0xFF - (0xFF - a) * (0xFF - b) / 0xFF;
+}
+
+/**
+ * \brief Calculate alpha value by piecewise linear function
+ * Used for \fad, \fade implementation.
+ */
+static unsigned
+interpolate_alpha(long long now, long long t1, long long t2, long long t3,
+                  long long t4, unsigned a1, unsigned a2, unsigned a3)
+{
+    unsigned a;
+    double cf;
+    if (now <= t1) {
+        a = a1;
+    } else if (now >= t4) {
+        a = a3;
+    } else if (now < t2) {      // and > t1
+        cf = ((double) (now - t1)) / (t2 - t1);
+        a = a1 * (1 - cf) + a2 * cf;
+    } else if (now > t3) {
+        cf = ((double) (now - t3)) / (t4 - t3);
+        a = a2 * (1 - cf) + a3 * cf;
+    } else {                    // t2 <= now <= t3
+        a = a2;
+    }
+
+    return a;
+}
+
+/**
+ * Parse a vector clip into an outline, using the proper scaling
+ * parameters.  Translate it to correct for screen borders, if needed.
+ */
+static char *parse_vector_clip(ASS_Renderer *render_priv, char *p)
+{
+    int scale = 1;
+    int res = 0;
+    ASS_Drawing *drawing;
+
+    render_priv->state.clip_drawing = ass_drawing_new(
+        render_priv->fontconfig_priv,
+        render_priv->state.font,
+        render_priv->settings.hinting,
+        render_priv->ftlibrary);
+    drawing = render_priv->state.clip_drawing;
+    skipopt('(');
+    res = mystrtoi(&p, &scale);
+    skipopt(',')
+    if (!res)
+        scale = 1;
+    drawing->scale = scale;
+    drawing->scale_x = render_priv->font_scale_x * render_priv->font_scale;
+    drawing->scale_y = render_priv->font_scale;
+    while (*p != ')' && *p != '}' && p != 0)
+        ass_drawing_add_char(drawing, *p++);
+    skipopt(')');
+    if (ass_drawing_parse(drawing, 1)) {
+        // We need to translate the clip according to screen borders
+        if (render_priv->settings.left_margin != 0 ||
+            render_priv->settings.top_margin != 0) {
+            FT_Vector trans = {
+                .x = int_to_d6(render_priv->settings.left_margin),
+                .y = -int_to_d6(render_priv->settings.top_margin),
+            };
+            FT_Outline_Translate(&drawing->glyph->outline, trans.x, trans.y);
+        }
+        ass_msg(render_priv->library, MSGL_DBG2,
+                "Parsed vector clip: scale %d, scales (%f, %f) string [%s]\n",
+                scale, drawing->scale_x, drawing->scale_y, drawing->text);
+    }
+
+    return p;
+}
+
+/**
+ * \brief Parse style override tag.
+ * \param p string to parse
+ * \param pwr multiplier for some tag effects (comes from \t tags)
+ */
+static char *parse_tag(ASS_Renderer *render_priv, char *p, double pwr)
+{
+    skip_to('\\');
+    skip('\\');
+    if ((*p == '}') || (*p == 0))
+        return p;
+
+    // New tags introduced in vsfilter 2.39
+    if (mystrcmp(&p, "xbord")) {
+        double val;
+        if (mystrtod(&p, &val))
+            val = render_priv->state.border_x * (1 - pwr) + val * pwr;
+        else
+            val = -1.;
+        change_border(render_priv, val, render_priv->state.border_y);
+    } else if (mystrcmp(&p, "ybord")) {
+        double val;
+        if (mystrtod(&p, &val))
+            val = render_priv->state.border_y * (1 - pwr) + val * pwr;
+        else
+            val = -1.;
+        change_border(render_priv, render_priv->state.border_x, val);
+    } else if (mystrcmp(&p, "xshad")) {
+        double val;
+        if (mystrtod(&p, &val))
+            val = render_priv->state.shadow_x * (1 - pwr) + val * pwr;
+        else
+            val = 0.;
+        render_priv->state.shadow_x = val;
+    } else if (mystrcmp(&p, "yshad")) {
+        double val;
+        if (mystrtod(&p, &val))
+            val = render_priv->state.shadow_y * (1 - pwr) + val * pwr;
+        else
+            val = 0.;
+        render_priv->state.shadow_y = val;
+    } else if (mystrcmp(&p, "fax")) {
+        double val;
+        if (mystrtod(&p, &val))
+            render_priv->state.fax =
+                val * pwr + render_priv->state.fax * (1 - pwr);
+        else
+            render_priv->state.fax = 0.;
+    } else if (mystrcmp(&p, "fay")) {
+        double val;
+        if (mystrtod(&p, &val))
+            render_priv->state.fay =
+                val * pwr + render_priv->state.fay * (1 - pwr);
+        else
+            render_priv->state.fay = 0.;
+    } else if (mystrcmp(&p, "iclip")) {
+        int x0, y0, x1, y1;
+        int res = 1;
+        char *start = p;
+        skipopt('(');
+        res &= mystrtoi(&p, &x0);
+        skipopt(',');
+        res &= mystrtoi(&p, &y0);
+        skipopt(',');
+        res &= mystrtoi(&p, &x1);
+        skipopt(',');
+        res &= mystrtoi(&p, &y1);
+        skipopt(')');
+        if (res) {
+            render_priv->state.clip_x0 =
+                render_priv->state.clip_x0 * (1 - pwr) + x0 * pwr;
+            render_priv->state.clip_x1 =
+                render_priv->state.clip_x1 * (1 - pwr) + x1 * pwr;
+            render_priv->state.clip_y0 =
+                render_priv->state.clip_y0 * (1 - pwr) + y0 * pwr;
+            render_priv->state.clip_y1 =
+                render_priv->state.clip_y1 * (1 - pwr) + y1 * pwr;
+            render_priv->state.clip_mode = 1;
+        } else if (!render_priv->state.clip_drawing) {
+            p = parse_vector_clip(render_priv, start);
+            render_priv->state.clip_drawing_mode = 1;
+        } else
+            render_priv->state.clip_mode = 0;
+    } else if (mystrcmp(&p, "blur")) {
+        double val;
+        if (mystrtod(&p, &val)) {
+            val = render_priv->state.blur * (1 - pwr) + val * pwr;
+            val = (val < 0) ? 0 : val;
+            val = (val > BLUR_MAX_RADIUS) ? BLUR_MAX_RADIUS : val;
+            render_priv->state.blur = val;
+        } else
+            render_priv->state.blur = 0.0;
+        // ASS standard tags
+    } else if (mystrcmp(&p, "fsc")) {
+        char tp = *p++;
+        double val;
+        if (tp == 'x') {
+            if (mystrtod(&p, &val)) {
+                val /= 100;
+                render_priv->state.scale_x =
+                    render_priv->state.scale_x * (1 - pwr) + val * pwr;
+            } else
+                render_priv->state.scale_x =
+                    render_priv->state.style->ScaleX;
+        } else if (tp == 'y') {
+            if (mystrtod(&p, &val)) {
+                val /= 100;
+                render_priv->state.scale_y =
+                    render_priv->state.scale_y * (1 - pwr) + val * pwr;
+            } else
+                render_priv->state.scale_y =
+                    render_priv->state.style->ScaleY;
+        }
+    } else if (mystrcmp(&p, "fsp")) {
+        double val;
+        if (mystrtod(&p, &val))
+            render_priv->state.hspacing =
+                render_priv->state.hspacing * (1 - pwr) + val * pwr;
+        else
+            render_priv->state.hspacing = render_priv->state.style->Spacing;
+    } else if (mystrcmp(&p, "fs")) {
+        double val;
+        if (mystrtod(&p, &val))
+            val = render_priv->state.font_size * (1 - pwr) + val * pwr;
+        else
+            val = render_priv->state.style->FontSize;
+        if (render_priv->state.font)
+            change_font_size(render_priv, val);
+    } else if (mystrcmp(&p, "bord")) {
+        double val;
+        if (mystrtod(&p, &val)) {
+            if (render_priv->state.border_x == render_priv->state.border_y)
+                val = render_priv->state.border_x * (1 - pwr) + val * pwr;
+        } else
+            val = -1.;          // reset to default
+        change_border(render_priv, val, val);
+    } else if (mystrcmp(&p, "move")) {
+        double x1, x2, y1, y2;
+        long long t1, t2, delta_t, t;
+        double x, y;
+        double k;
+        skip('(');
+        mystrtod(&p, &x1);
+        skip(',');
+        mystrtod(&p, &y1);
+        skip(',');
+        mystrtod(&p, &x2);
+        skip(',');
+        mystrtod(&p, &y2);
+        if (*p == ',') {
+            skip(',');
+            mystrtoll(&p, &t1);
+            skip(',');
+            mystrtoll(&p, &t2);
+            ass_msg(render_priv->library, MSGL_DBG2,
+                   "movement6: (%f, %f) -> (%f, %f), (%" PRId64 " .. %"
+                   PRId64 ")\n", x1, y1, x2, y2, (int64_t) t1,
+                   (int64_t) t2);
+        } else {
+            t1 = 0;
+            t2 = render_priv->state.event->Duration;
+            ass_msg(render_priv->library, MSGL_DBG2,
+                   "movement: (%f, %f) -> (%f, %f)", x1, y1, x2, y2);
+        }
+        skip(')');
+        delta_t = t2 - t1;
+        t = render_priv->time - render_priv->state.event->Start;
+        if (t < t1)
+            k = 0.;
+        else if (t > t2)
+            k = 1.;
+        else
+            k = ((double) (t - t1)) / delta_t;
+        x = k * (x2 - x1) + x1;
+        y = k * (y2 - y1) + y1;
+        if (render_priv->state.evt_type != EVENT_POSITIONED) {
+            render_priv->state.pos_x = x;
+            render_priv->state.pos_y = y;
+            render_priv->state.detect_collisions = 0;
+            render_priv->state.evt_type = EVENT_POSITIONED;
+        }
+    } else if (mystrcmp(&p, "frx")) {
+        double val;
+        if (mystrtod(&p, &val)) {
+            val *= M_PI / 180;
+            render_priv->state.frx =
+                val * pwr + render_priv->state.frx * (1 - pwr);
+        } else
+            render_priv->state.frx = 0.;
+    } else if (mystrcmp(&p, "fry")) {
+        double val;
+        if (mystrtod(&p, &val)) {
+            val *= M_PI / 180;
+            render_priv->state.fry =
+                val * pwr + render_priv->state.fry * (1 - pwr);
+        } else
+            render_priv->state.fry = 0.;
+    } else if (mystrcmp(&p, "frz") || mystrcmp(&p, "fr")) {
+        double val;
+        if (mystrtod(&p, &val)) {
+            val *= M_PI / 180;
+            render_priv->state.frz =
+                val * pwr + render_priv->state.frz * (1 - pwr);
+        } else
+            render_priv->state.frz =
+                M_PI * render_priv->state.style->Angle / 180.;
+    } else if (mystrcmp(&p, "fn")) {
+        char *start = p;
+        char *family;
+        skip_to('\\');
+        if (p > start) {
+            family = malloc(p - start + 1);
+            strncpy(family, start, p - start);
+            family[p - start] = '\0';
+        } else
+            family = strdup(render_priv->state.style->FontName);
+        if (render_priv->state.family)
+            free(render_priv->state.family);
+        render_priv->state.family = family;
+        update_font(render_priv);
+    } else if (mystrcmp(&p, "alpha")) {
+        uint32_t val;
+        int i;
+        int hex = render_priv->track->track_type == TRACK_TYPE_ASS;
+        if (strtocolor(render_priv->library, &p, &val, hex)) {
+            unsigned char a = val >> 24;
+            for (i = 0; i < 4; ++i)
+                change_alpha(&render_priv->state.c[i], a, pwr);
+        } else {
+            change_alpha(&render_priv->state.c[0],
+                         render_priv->state.style->PrimaryColour, pwr);
+            change_alpha(&render_priv->state.c[1],
+                         render_priv->state.style->SecondaryColour, pwr);
+            change_alpha(&render_priv->state.c[2],
+                         render_priv->state.style->OutlineColour, pwr);
+            change_alpha(&render_priv->state.c[3],
+                         render_priv->state.style->BackColour, pwr);
+        }
+        // FIXME: simplify
+    } else if (mystrcmp(&p, "an")) {
+        int val;
+        if (mystrtoi(&p, &val) && val) {
+            int v = (val - 1) / 3;      // 0, 1 or 2 for vertical alignment
+            ass_msg(render_priv->library, MSGL_DBG2, "an %d", val);
+            if (v != 0)
+                v = 3 - v;
+            val = ((val - 1) % 3) + 1;  // horizontal alignment
+            val += v * 4;
+            ass_msg(render_priv->library, MSGL_DBG2, "align %d", val);
+            render_priv->state.alignment = val;
+        } else
+            render_priv->state.alignment =
+                render_priv->state.style->Alignment;
+    } else if (mystrcmp(&p, "a")) {
+        int val;
+        if (mystrtoi(&p, &val) && val)
+            // take care of a vsfilter quirk: handle illegal \a8 like \a5
+            render_priv->state.alignment = (val == 8) ? 5 : val;
+        else
+            render_priv->state.alignment =
+                render_priv->state.style->Alignment;
+    } else if (mystrcmp(&p, "pos")) {
+        double v1, v2;
+        skip('(');
+        mystrtod(&p, &v1);
+        skip(',');
+        mystrtod(&p, &v2);
+        skip(')');
+        ass_msg(render_priv->library, MSGL_DBG2, "pos(%f, %f)", v1, v2);
+        if (render_priv->state.evt_type == EVENT_POSITIONED) {
+            ass_msg(render_priv->library, MSGL_V, "Subtitle has a new \\pos "
+                   "after \\move or \\pos, ignoring");
+        } else {
+            render_priv->state.evt_type = EVENT_POSITIONED;
+            render_priv->state.detect_collisions = 0;
+            render_priv->state.pos_x = v1;
+            render_priv->state.pos_y = v2;
+        }
+    } else if (mystrcmp(&p, "fad")) {
+        int a1, a2, a3;
+        long long t1, t2, t3, t4;
+        if (*p == 'e')
+            ++p;                // either \fad or \fade
+        skip('(');
+        mystrtoi(&p, &a1);
+        skip(',');
+        mystrtoi(&p, &a2);
+        if (*p == ')') {
+            // 2-argument version (\fad, according to specs)
+            // a1 and a2 are fade-in and fade-out durations
+            t1 = 0;
+            t4 = render_priv->state.event->Duration;
+            t2 = a1;
+            t3 = t4 - a2;
+            a1 = 0xFF;
+            a2 = 0;
+            a3 = 0xFF;
+        } else {
+            // 6-argument version (\fade)
+            // a1 and a2 (and a3) are opacity values
+            skip(',');
+            mystrtoi(&p, &a3);
+            skip(',');
+            mystrtoll(&p, &t1);
+            skip(',');
+            mystrtoll(&p, &t2);
+            skip(',');
+            mystrtoll(&p, &t3);
+            skip(',');
+            mystrtoll(&p, &t4);
+        }
+        skip(')');
+        render_priv->state.fade =
+            interpolate_alpha(render_priv->time -
+                              render_priv->state.event->Start, t1, t2,
+                              t3, t4, a1, a2, a3);
+    } else if (mystrcmp(&p, "org")) {
+        int v1, v2;
+        skip('(');
+        mystrtoi(&p, &v1);
+        skip(',');
+        mystrtoi(&p, &v2);
+        skip(')');
+        ass_msg(render_priv->library, MSGL_DBG2, "org(%d, %d)", v1, v2);
+        if (!render_priv->state.have_origin) {
+            render_priv->state.org_x = v1;
+            render_priv->state.org_y = v2;
+            render_priv->state.have_origin = 1;
+            render_priv->state.detect_collisions = 0;
+        }
+    } else if (mystrcmp(&p, "t")) {
+        double v[3];
+        int v1, v2;
+        double v3;
+        int cnt;
+        long long t1, t2, t, delta_t;
+        double k;
+        skip('(');
+        for (cnt = 0; cnt < 3; ++cnt) {
+            if (*p == '\\')
+                break;
+            v[cnt] = strtod(p, &p);
+            skip(',');
+        }
+        if (cnt == 3) {
+            v1 = v[0];
+            v2 = (v[1] < v1) ? render_priv->state.event->Duration : v[1];
+            v3 = v[2];
+        } else if (cnt == 2) {
+            v1 = v[0];
+            v2 = (v[1] < v1) ? render_priv->state.event->Duration : v[1];
+            v3 = 1.;
+        } else if (cnt == 1) {
+            v1 = 0;
+            v2 = render_priv->state.event->Duration;
+            v3 = v[0];
+        } else {                // cnt == 0
+            v1 = 0;
+            v2 = render_priv->state.event->Duration;
+            v3 = 1.;
+        }
+        render_priv->state.detect_collisions = 0;
+        t1 = v1;
+        t2 = v2;
+        delta_t = v2 - v1;
+        if (v3 < 0.)
+            v3 = 0.;
+        t = render_priv->time - render_priv->state.event->Start;        // FIXME: move to render_context
+        if (t <= t1)
+            k = 0.;
+        else if (t >= t2)
+            k = 1.;
+        else {
+            assert(delta_t != 0.);
+            k = pow(((double) (t - t1)) / delta_t, v3);
+        }
+        while (*p == '\\')
+            p = parse_tag(render_priv, p, k);   // maybe k*pwr ? no, specs forbid nested \t's
+        skip_to(')');           // in case there is some unknown tag or a comment
+        skip(')');
+    } else if (mystrcmp(&p, "clip")) {
+        char *start = p;
+        int x0, y0, x1, y1;
+        int res = 1;
+        skipopt('(');
+        res &= mystrtoi(&p, &x0);
+        skipopt(',');
+        res &= mystrtoi(&p, &y0);
+        skipopt(',');
+        res &= mystrtoi(&p, &x1);
+        skipopt(',');
+        res &= mystrtoi(&p, &y1);
+        skipopt(')');
+        if (res) {
+            render_priv->state.clip_x0 =
+                render_priv->state.clip_x0 * (1 - pwr) + x0 * pwr;
+            render_priv->state.clip_x1 =
+                render_priv->state.clip_x1 * (1 - pwr) + x1 * pwr;
+            render_priv->state.clip_y0 =
+                render_priv->state.clip_y0 * (1 - pwr) + y0 * pwr;
+            render_priv->state.clip_y1 =
+                render_priv->state.clip_y1 * (1 - pwr) + y1 * pwr;
+        // Might be a vector clip
+        } else if (!render_priv->state.clip_drawing) {
+            p = parse_vector_clip(render_priv, start);
+            render_priv->state.clip_drawing_mode = 0;
+        } else {
+            render_priv->state.clip_x0 = 0;
+            render_priv->state.clip_y0 = 0;
+            render_priv->state.clip_x1 = render_priv->track->PlayResX;
+            render_priv->state.clip_y1 = render_priv->track->PlayResY;
+        }
+    } else if (mystrcmp(&p, "c")) {
+        uint32_t val;
+        int hex = render_priv->track->track_type == TRACK_TYPE_ASS;
+        if (!strtocolor(render_priv->library, &p, &val, hex))
+            val = render_priv->state.style->PrimaryColour;
+        ass_msg(render_priv->library, MSGL_DBG2, "color: %X", val);
+        change_color(&render_priv->state.c[0], val, pwr);
+    } else if ((*p >= '1') && (*p <= '4') && (++p)
+               && (mystrcmp(&p, "c") || mystrcmp(&p, "a"))) {
+        char n = *(p - 2);
+        int cidx = n - '1';
+        char cmd = *(p - 1);
+        uint32_t val;
+        int hex = render_priv->track->track_type == TRACK_TYPE_ASS;
+        assert((n >= '1') && (n <= '4'));
+        if (!strtocolor(render_priv->library, &p, &val, hex))
+            switch (n) {
+            case '1':
+                val = render_priv->state.style->PrimaryColour;
+                break;
+            case '2':
+                val = render_priv->state.style->SecondaryColour;
+                break;
+            case '3':
+                val = render_priv->state.style->OutlineColour;
+                break;
+            case '4':
+                val = render_priv->state.style->BackColour;
+                break;
+            default:
+                val = 0;
+                break;          // impossible due to assert; avoid compilation warning
+            }
+        switch (cmd) {
+        case 'c':
+            change_color(render_priv->state.c + cidx, val, pwr);
+            break;
+        case 'a':
+            change_alpha(render_priv->state.c + cidx, val >> 24, pwr);
+            break;
+        default:
+            ass_msg(render_priv->library, MSGL_WARN, "Bad command: %c%c",
+                    n, cmd);
+            break;
+        }
+        ass_msg(render_priv->library, MSGL_DBG2, "single c/a at %f: %c%c = %X",
+               pwr, n, cmd, render_priv->state.c[cidx]);
+    } else if (mystrcmp(&p, "r")) {
+        reset_render_context(render_priv);
+    } else if (mystrcmp(&p, "be")) {
+        int val;
+        if (mystrtoi(&p, &val)) {
+            // Clamp to a safe upper limit, since high values need excessive CPU
+            val = (val < 0) ? 0 : val;
+            val = (val > MAX_BE) ? MAX_BE : val;
+            render_priv->state.be = val;
+        } else
+            render_priv->state.be = 0;
+    } else if (mystrcmp(&p, "b")) {
+        int b;
+        if (mystrtoi(&p, &b)) {
+            if (pwr >= .5)
+                render_priv->state.bold = b;
+        } else
+            render_priv->state.bold = render_priv->state.style->Bold;
+        update_font(render_priv);
+    } else if (mystrcmp(&p, "i")) {
+        int i;
+        if (mystrtoi(&p, &i)) {
+            if (pwr >= .5)
+                render_priv->state.italic = i;
+        } else
+            render_priv->state.italic = render_priv->state.style->Italic;
+        update_font(render_priv);
+    } else if (mystrcmp(&p, "kf") || mystrcmp(&p, "K")) {
+        int val = 0;
+        mystrtoi(&p, &val);
+        render_priv->state.effect_type = EF_KARAOKE_KF;
+        if (render_priv->state.effect_timing)
+            render_priv->state.effect_skip_timing +=
+                render_priv->state.effect_timing;
+        render_priv->state.effect_timing = val * 10;
+    } else if (mystrcmp(&p, "ko")) {
+        int val = 0;
+        mystrtoi(&p, &val);
+        render_priv->state.effect_type = EF_KARAOKE_KO;
+        if (render_priv->state.effect_timing)
+            render_priv->state.effect_skip_timing +=
+                render_priv->state.effect_timing;
+        render_priv->state.effect_timing = val * 10;
+    } else if (mystrcmp(&p, "k")) {
+        int val = 0;
+        mystrtoi(&p, &val);
+        render_priv->state.effect_type = EF_KARAOKE;
+        if (render_priv->state.effect_timing)
+            render_priv->state.effect_skip_timing +=
+                render_priv->state.effect_timing;
+        render_priv->state.effect_timing = val * 10;
+    } else if (mystrcmp(&p, "shad")) {
+        double val;
+        if (mystrtod(&p, &val)) {
+            if (render_priv->state.shadow_x == render_priv->state.shadow_y)
+                val = render_priv->state.shadow_x * (1 - pwr) + val * pwr;
+        } else
+            val = 0.;
+        render_priv->state.shadow_x = render_priv->state.shadow_y = val;
+    } else if (mystrcmp(&p, "s")) {
+        int val;
+        if (mystrtoi(&p, &val) && val)
+            render_priv->state.flags |= DECO_STRIKETHROUGH;
+        else
+            render_priv->state.flags &= ~DECO_STRIKETHROUGH;
+    } else if (mystrcmp(&p, "u")) {
+        int val;
+        if (mystrtoi(&p, &val) && val)
+            render_priv->state.flags |= DECO_UNDERLINE;
+        else
+            render_priv->state.flags &= ~DECO_UNDERLINE;
+    } else if (mystrcmp(&p, "pbo")) {
+        double val = 0;
+        if (mystrtod(&p, &val))
+            render_priv->state.drawing->pbo = val;
+    } else if (mystrcmp(&p, "p")) {
+        int val;
+        if (!mystrtoi(&p, &val))
+            val = 0;
+        if (val)
+            render_priv->state.drawing->scale = val;
+        render_priv->state.drawing_mode = !!val;
+    } else if (mystrcmp(&p, "q")) {
+        int val;
+        if (!mystrtoi(&p, &val))
+            val = render_priv->track->WrapStyle;
+        render_priv->state.wrap_style = val;
+    }
+
+    return p;
+}
+
+void apply_transition_effects(ASS_Renderer *render_priv, ASS_Event *event)
+{
+    int v[4];
+    int cnt;
+    char *p = event->Effect;
+
+    if (!p || !*p)
+        return;
+
+    cnt = 0;
+    while (cnt < 4 && (p = strchr(p, ';'))) {
+        v[cnt++] = atoi(++p);
+    }
+
+    if (strncmp(event->Effect, "Banner;", 7) == 0) {
+        int delay;
+        if (cnt < 1) {
+            ass_msg(render_priv->library, MSGL_V,
+                    "Error parsing effect: '%s'", event->Effect);
+            return;
+        }
+        if (cnt >= 2 && v[1] == 0)      // right-to-left
+            render_priv->state.scroll_direction = SCROLL_RL;
+        else                    // left-to-right
+            render_priv->state.scroll_direction = SCROLL_LR;
+
+        delay = v[0];
+        if (delay == 0)
+            delay = 1;          // ?
+        render_priv->state.scroll_shift =
+            (render_priv->time - render_priv->state.event->Start) / delay;
+        render_priv->state.evt_type = EVENT_HSCROLL;
+        return;
+    }
+
+    if (strncmp(event->Effect, "Scroll up;", 10) == 0) {
+        render_priv->state.scroll_direction = SCROLL_BT;
+    } else if (strncmp(event->Effect, "Scroll down;", 12) == 0) {
+        render_priv->state.scroll_direction = SCROLL_TB;
+    } else {
+        ass_msg(render_priv->library, MSGL_V,
+                "Unknown transition effect: '%s'", event->Effect);
+        return;
+    }
+    // parse scroll up/down parameters
+    {
+        int delay;
+        int y0, y1;
+        if (cnt < 3) {
+            ass_msg(render_priv->library, MSGL_V,
+                    "Error parsing effect: '%s'", event->Effect);
+            return;
+        }
+        delay = v[2];
+        if (delay == 0)
+            delay = 1;          // ?
+        render_priv->state.scroll_shift =
+            (render_priv->time - render_priv->state.event->Start) / delay;
+        if (v[0] < v[1]) {
+            y0 = v[0];
+            y1 = v[1];
+        } else {
+            y0 = v[1];
+            y1 = v[0];
+        }
+        if (y1 == 0)
+            y1 = render_priv->track->PlayResY;  // y0=y1=0 means fullscreen scrolling
+        render_priv->state.clip_y0 = y0;
+        render_priv->state.clip_y1 = y1;
+        render_priv->state.evt_type = EVENT_VSCROLL;
+        render_priv->state.detect_collisions = 0;
+    }
+
+}
+
+/**
+ * \brief Get next ucs4 char from string, parsing and executing style overrides
+ * \param str string pointer
+ * \return ucs4 code of the next char
+ * On return str points to the unparsed part of the string
+ */
+unsigned get_next_char(ASS_Renderer *render_priv, char **str)
+{
+    char *p = *str;
+    unsigned chr;
+    if (*p == '{') {            // '\0' goes here
+        p++;
+        while (1) {
+            p = parse_tag(render_priv, p, 1.);
+            if (*p == '}') {    // end of tag
+                p++;
+                if (*p == '{') {
+                    p++;
+                    continue;
+                } else
+                    break;
+            } else if (*p != '\\')
+                ass_msg(render_priv->library, MSGL_V,
+                        "Unable to parse: '%s'", p);
+            if (*p == 0)
+                break;
+        }
+    }
+    if (*p == '\t') {
+        ++p;
+        *str = p;
+        return ' ';
+    }
+    if (*p == '\\') {
+        if ((p[1] == 'N') || ((p[1] == 'n') &&
+                              (render_priv->state.wrap_style == 2))) {
+            p += 2;
+            *str = p;
+            return '\n';
+        } else if (p[1] == 'n') {
+            p += 2;
+            *str = p;
+            return ' ';
+        } else if (p[1] == 'h') {
+            p += 2;
+            *str = p;
+            return NBSP;
+        }
+    }
+    chr = ass_utf8_get_char((char **) &p);
+    *str = p;
+    return chr;
+}
diff --git a/libass/ass_parse.h b/libass/ass_parse.h
new file mode 100644 (file)
index 0000000..c65b565
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2009 Grigori Goronzy <greg@geekmind.org>
+ *
+ * This file is part of libass.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef LIBASS_PARSE_H
+#define LIBASS_PARSE_H
+
+#define BLUR_MAX_RADIUS 100.0
+
+#define _r(c)   ((c) >> 24)
+#define _g(c)   (((c) >> 16) & 0xFF)
+#define _b(c)   (((c) >> 8) & 0xFF)
+#define _a(c)   ((c) & 0xFF)
+
+void update_font(ASS_Renderer *render_priv);
+void change_border(ASS_Renderer *render_priv, double border_x,
+                   double border_y);
+void apply_transition_effects(ASS_Renderer *render_priv, ASS_Event *event);
+unsigned get_next_char(ASS_Renderer *render_priv, char **str);
+extern void change_alpha(uint32_t *var, uint32_t new, double pwr);
+extern uint32_t mult_alpha(uint32_t a, uint32_t b);
+
+
+#endif /* LIBASS_PARSE_H */
index 3048a2a..6bc0c61 100644 (file)
@@ -1,5 +1,3 @@
-// -*- c-basic-offset: 8; indent-tabs-mode: t -*-
-// vim:ts=8:sw=8:noet:ai:
 /*
  * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
  *
@@ -31,8 +29,6 @@
 #include FT_GLYPH_H
 #include FT_SYNTHESIS_H
 
-#include "mputils.h"
-
 #include "ass.h"
 #include "ass_font.h"
 #include "ass_bitmap.h"
 #include "ass_utils.h"
 #include "ass_fontconfig.h"
 #include "ass_library.h"
+#include "ass_drawing.h"
+#include "ass_render.h"
+#include "ass_parse.h"
+
+#define MAX_GLYPHS_INITIAL 1024
+#define MAX_LINES_INITIAL 64
+#define SUBPIXEL_MASK 63
+#define SUBPIXEL_ACCURACY 7    // d6 mask for subpixel accuracy adjustment
+#define GLYPH_CACHE_MAX 1000
+#define BITMAP_CACHE_MAX_SIZE 50 * 1048576
+
+static void ass_lazy_track_init(ASS_Renderer *render_priv)
+{
+    ASS_Track *track = render_priv->track;
+
+    if (track->PlayResX && track->PlayResY)
+        return;
+    if (!track->PlayResX && !track->PlayResY) {
+        ass_msg(render_priv->library, MSGL_WARN,
+               "Neither PlayResX nor PlayResY defined. Assuming 384x288");
+        track->PlayResX = 384;
+        track->PlayResY = 288;
+    } else {
+        if (!track->PlayResY && track->PlayResX == 1280) {
+            track->PlayResY = 1024;
+            ass_msg(render_priv->library, MSGL_WARN,
+                   "PlayResY undefined, setting to %d", track->PlayResY);
+        } else if (!track->PlayResY) {
+            track->PlayResY = track->PlayResX * 3 / 4;
+            ass_msg(render_priv->library, MSGL_WARN,
+                   "PlayResY undefined, setting to %d", track->PlayResY);
+        } else if (!track->PlayResX && track->PlayResY == 1024) {
+            track->PlayResX = 1280;
+            ass_msg(render_priv->library, MSGL_WARN,
+                   "PlayResX undefined, setting to %d", track->PlayResX);
+        } else if (!track->PlayResX) {
+            track->PlayResX = track->PlayResY * 4 / 3;
+            ass_msg(render_priv->library, MSGL_WARN,
+                   "PlayResX undefined, setting to %d", track->PlayResX);
+        }
+    }
+}
 
-#define MAX_GLYPHS 3000
-#define MAX_LINES 300
-#define BLUR_MAX_RADIUS 50.0
-#define MAX_BE 100
-#define ROUND(x) ((int) ((x) + .5))
-#define SUBPIXEL_MASK 56       // d6 bitmask for subpixel accuracy adjustment
-
-static int last_render_id = 0;
-
-typedef struct ass_settings_s {
-       int frame_width;
-       int frame_height;
-       double font_size_coeff; // font size multiplier
-       double line_spacing; // additional line spacing (in frame pixels)
-       int top_margin; // height of top margin. Everything except toptitles is shifted down by top_margin.
-       int bottom_margin; // height of bottom margin. (frame_height - top_margin - bottom_margin) is original video height.
-       int left_margin;
-       int right_margin;
-       int use_margins; // 0 - place all subtitles inside original frame
-                        // 1 - use margins for placing toptitles and subtitles
-       double aspect; // frame aspect ratio, d_width / d_height.
-       ass_hinting_t hinting;
-
-       char* default_font;
-       char* default_family;
-} ass_settings_t;
-
-// a rendered event
-typedef struct event_images_s {
-       ass_image_t* imgs;
-       int top, height;
-       int detect_collisions;
-       int shift_direction;
-       ass_event_t* event;
-} event_images_t;
-
-struct ass_renderer_s {
-       ass_library_t* library;
-       FT_Library ftlibrary;
-       fc_instance_t* fontconfig_priv;
-       ass_settings_t settings;
-       int render_id;
-       ass_synth_priv_t* synth_priv;
-
-       ass_image_t* images_root; // rendering result is stored here
-       ass_image_t* prev_images_root;
-
-       event_images_t* eimg; // temporary buffer for sorting rendered events
-       int eimg_size; // allocated buffer size
-};
-
-typedef enum {EF_NONE = 0, EF_KARAOKE, EF_KARAOKE_KF, EF_KARAOKE_KO} effect_t;
-
-// describes a glyph
-// glyph_info_t and text_info_t are used for text centering and word-wrapping operations
-typedef struct glyph_info_s {
-       unsigned symbol;
-       FT_Glyph glyph;
-       FT_Glyph outline_glyph;
-       bitmap_t* bm; // glyph bitmap
-       bitmap_t* bm_o; // outline bitmap
-       bitmap_t* bm_s; // shadow bitmap
-       FT_BBox bbox;
-       FT_Vector pos;
-       char linebreak; // the first (leading) glyph of some line ?
-       uint32_t c[4]; // colors
-       FT_Vector advance; // 26.6
-       effect_t effect_type;
-       int effect_timing; // time duration of current karaoke word
-                          // after process_karaoke_effects: distance in pixels from the glyph origin.
-                          // part of the glyph to the left of it is displayed in a different color.
-       int effect_skip_timing; // delay after the end of last karaoke word
-       int asc, desc; // font max ascender and descender
-//     int height;
-       int be; // blur edges
-       double blur; // gaussian blur
-       double shadow;
-       double frx, fry, frz; // rotation
-
-       bitmap_hash_key_t hash_key;
-} glyph_info_t;
-
-typedef struct line_info_s {
-       int asc, desc;
-} line_info_t;
-
-typedef struct text_info_s {
-       glyph_info_t* glyphs;
-       int length;
-       line_info_t lines[MAX_LINES];
-       int n_lines;
-       int height;
-} text_info_t;
-
-
-// Renderer state.
-// Values like current font face, color, screen position, clipping and so on are stored here.
-typedef struct render_context_s {
-       ass_event_t* event;
-       ass_style_t* style;
-
-       ass_font_t* font;
-       char* font_path;
-       double font_size;
-
-       FT_Stroker stroker;
-       int alignment; // alignment overrides go here; if zero, style value will be used
-       double frx, fry, frz;
-       enum {  EVENT_NORMAL, // "normal" top-, sub- or mid- title
-               EVENT_POSITIONED, // happens after pos(,), margins are ignored
-               EVENT_HSCROLL, // "Banner" transition effect, text_width is unlimited
-               EVENT_VSCROLL // "Scroll up", "Scroll down" transition effects
-               } evt_type;
-       double pos_x, pos_y; // position
-       double org_x, org_y; // origin
-       char have_origin; // origin is explicitly defined; if 0, get_base_point() is used
-       double scale_x, scale_y;
-       double hspacing; // distance between letters, in pixels
-       double border; // outline width
-       uint32_t c[4]; // colors(Primary, Secondary, so on) in RGBA
-       int clip_x0, clip_y0, clip_x1, clip_y1;
-       char detect_collisions;
-       uint32_t fade; // alpha from \fad
-       char be; // blur edges
-       double blur; // gaussian blur
-       double shadow;
-       int drawing_mode; // not implemented; when != 0 text is discarded, except for style override tags
-
-       effect_t effect_type;
-       int effect_timing;
-       int effect_skip_timing;
-
-       enum { SCROLL_LR, // left-to-right
-              SCROLL_RL,
-              SCROLL_TB, // top-to-bottom
-              SCROLL_BT
-              } scroll_direction; // for EVENT_HSCROLL, EVENT_VSCROLL
-       int scroll_shift;
-
-       // face properties
-       char* family;
-       unsigned bold;
-       unsigned italic;
-       int treat_family_as_pattern;
-
-} render_context_t;
-
-// frame-global data
-typedef struct frame_context_s {
-       ass_renderer_t* ass_priv;
-       int width, height; // screen dimensions
-       int orig_height; // frame height ( = screen height - margins )
-       int orig_width; // frame width ( = screen width - margins )
-       int orig_height_nocrop; // frame height ( = screen height - margins + cropheight)
-       int orig_width_nocrop; // frame width ( = screen width - margins + cropwidth)
-       ass_track_t* track;
-       long long time; // frame's timestamp, ms
-       double font_scale;
-       double font_scale_x; // x scale applied to all glyphs to preserve text aspect ratio
-       double border_scale;
-} frame_context_t;
-
-static ass_renderer_t* ass_renderer;
-static ass_settings_t* global_settings;
-static text_info_t text_info;
-static render_context_t render_context;
-static frame_context_t frame_context;
-
-struct render_priv_s {
-       int top, height;
-       int render_id;
-};
-
-static void ass_lazy_track_init(void)
+ASS_Renderer *ass_renderer_init(ASS_Library *library)
 {
-       ass_track_t* track = frame_context.track;
-       if (track->PlayResX && track->PlayResY)
-               return;
-       if (!track->PlayResX && !track->PlayResY) {
-               mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_NeitherPlayResXNorPlayResYDefined);
-               track->PlayResX = 384;
-               track->PlayResY = 288;
-       } else {
-               double orig_aspect = (global_settings->aspect * frame_context.height * frame_context.orig_width) /
-                       frame_context.orig_height / frame_context.width;
-               if (!track->PlayResY && track->PlayResX == 1280) {
-                       track->PlayResY = 1024;
-                       mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_PlayResYUndefinedSettingY, track->PlayResY);
-               } else if (!track->PlayResY) {
-                       track->PlayResY = track->PlayResX / orig_aspect + .5;
-                       mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_PlayResYUndefinedSettingY, track->PlayResY);
-               } else if (!track->PlayResX && track->PlayResY == 1024) {
-                       track->PlayResX = 1280;
-                       mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_PlayResXUndefinedSettingX, track->PlayResX);
-               } else if (!track->PlayResX) {
-                       track->PlayResX = track->PlayResY * orig_aspect + .5;
-                       mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_PlayResXUndefinedSettingX, track->PlayResX);
-               }
-       }
+    int error;
+    FT_Library ft;
+    ASS_Renderer *priv = 0;
+    int vmajor, vminor, vpatch;
+
+    error = FT_Init_FreeType(&ft);
+    if (error) {
+        ass_msg(library, MSGL_FATAL, "%s failed", "FT_Init_FreeType");
+        goto ass_init_exit;
+    }
+
+    FT_Library_Version(ft, &vmajor, &vminor, &vpatch);
+    ass_msg(library, MSGL_V, "FreeType library version: %d.%d.%d",
+           vmajor, vminor, vpatch);
+    ass_msg(library, MSGL_V, "FreeType headers version: %d.%d.%d",
+           FREETYPE_MAJOR, FREETYPE_MINOR, FREETYPE_PATCH);
+
+    priv = calloc(1, sizeof(ASS_Renderer));
+    if (!priv) {
+        FT_Done_FreeType(ft);
+        goto ass_init_exit;
+    }
+
+    priv->synth_priv = ass_synth_init(BLUR_MAX_RADIUS);
+
+    priv->library = library;
+    priv->ftlibrary = ft;
+    // images_root and related stuff is zero-filled in calloc
+
+    priv->cache.font_cache = ass_font_cache_init(library);
+    priv->cache.bitmap_cache = ass_bitmap_cache_init(library);
+    priv->cache.composite_cache = ass_composite_cache_init(library);
+    priv->cache.glyph_cache = ass_glyph_cache_init(library);
+    priv->cache.glyph_max = GLYPH_CACHE_MAX;
+    priv->cache.bitmap_max_size = BITMAP_CACHE_MAX_SIZE;
+
+    priv->text_info.max_glyphs = MAX_GLYPHS_INITIAL;
+    priv->text_info.max_lines = MAX_LINES_INITIAL;
+    priv->text_info.glyphs =
+        calloc(MAX_GLYPHS_INITIAL, sizeof(GlyphInfo));
+    priv->text_info.lines = calloc(MAX_LINES_INITIAL, sizeof(LineInfo));
+
+  ass_init_exit:
+    if (priv)
+        ass_msg(library, MSGL_INFO, "Init");
+    else
+        ass_msg(library, MSGL_ERR, "Init failed");
+
+    return priv;
 }
 
-ass_renderer_t* ass_renderer_init(ass_library_t* library)
+void ass_set_cache_limits(ASS_Renderer *render_priv, int glyph_max,
+                          int bitmap_max)
 {
-       int error;
-       FT_Library ft;
-       ass_renderer_t* priv = 0;
-       int vmajor, vminor, vpatch;
-
-       memset(&render_context, 0, sizeof(render_context));
-       memset(&frame_context, 0, sizeof(frame_context));
-       memset(&text_info, 0, sizeof(text_info));
-
-       error = FT_Init_FreeType( &ft );
-       if ( error ) {
-               mp_msg(MSGT_ASS, MSGL_FATAL, MSGTR_LIBASS_FT_Init_FreeTypeFailed);
-               goto ass_init_exit;
-       }
-
-       FT_Library_Version(ft, &vmajor, &vminor, &vpatch);
-       mp_msg(MSGT_ASS, MSGL_V, "FreeType library version: %d.%d.%d\n",
-              vmajor, vminor, vpatch);
-       mp_msg(MSGT_ASS, MSGL_V, "FreeType headers version: %d.%d.%d\n",
-              FREETYPE_MAJOR, FREETYPE_MINOR, FREETYPE_PATCH);
-
-       priv = calloc(1, sizeof(ass_renderer_t));
-       if (!priv) {
-               FT_Done_FreeType(ft);
-               goto ass_init_exit;
-       }
-
-       priv->synth_priv = ass_synth_init(BLUR_MAX_RADIUS);
-
-       priv->library = library;
-       priv->ftlibrary = ft;
-       // images_root and related stuff is zero-filled in calloc
-
-       ass_font_cache_init();
-       ass_bitmap_cache_init();
-       ass_composite_cache_init();
-       ass_glyph_cache_init();
-
-       text_info.glyphs = calloc(MAX_GLYPHS, sizeof(glyph_info_t));
-
-ass_init_exit:
-       if (priv) mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_Init);
-       else mp_msg(MSGT_ASS, MSGL_ERR, MSGTR_LIBASS_InitFailed);
-
-       return priv;
+    render_priv->cache.glyph_max = glyph_max ? glyph_max : GLYPH_CACHE_MAX;
+    render_priv->cache.bitmap_max_size = bitmap_max ? 1048576 * bitmap_max :
+                                         BITMAP_CACHE_MAX_SIZE;
 }
 
-void ass_renderer_done(ass_renderer_t* priv)
+static void free_list_clear(ASS_Renderer *render_priv)
 {
-       ass_font_cache_done();
-       ass_bitmap_cache_done();
-       ass_composite_cache_done();
-       ass_glyph_cache_done();
-       if (render_context.stroker) {
-               FT_Stroker_Done(render_context.stroker);
-               render_context.stroker = 0;
-       }
-       if (priv && priv->ftlibrary) FT_Done_FreeType(priv->ftlibrary);
-       if (priv && priv->fontconfig_priv) fontconfig_done(priv->fontconfig_priv);
-       if (priv && priv->synth_priv) ass_synth_done(priv->synth_priv);
-       if (priv && priv->eimg) free(priv->eimg);
-       if (priv) free(priv);
-       if (text_info.glyphs) free(text_info.glyphs);
+    if (render_priv->free_head) {
+        FreeList *item = render_priv->free_head;
+        while(item) {
+            FreeList *oi = item;
+            free(item->object);
+            item = item->next;
+            free(oi);
+        }
+        render_priv->free_head = NULL;
+    }
+}
+
+static void ass_free_images(ASS_Image *img);
+
+void ass_renderer_done(ASS_Renderer *render_priv)
+{
+    ass_font_cache_done(render_priv->cache.font_cache);
+    ass_bitmap_cache_done(render_priv->cache.bitmap_cache);
+    ass_composite_cache_done(render_priv->cache.composite_cache);
+    ass_glyph_cache_done(render_priv->cache.glyph_cache);
+
+    ass_free_images(render_priv->images_root);
+    ass_free_images(render_priv->prev_images_root);
+
+    if (render_priv->state.stroker) {
+        FT_Stroker_Done(render_priv->state.stroker);
+        render_priv->state.stroker = 0;
+    }
+    if (render_priv && render_priv->ftlibrary)
+        FT_Done_FreeType(render_priv->ftlibrary);
+    if (render_priv && render_priv->fontconfig_priv)
+        fontconfig_done(render_priv->fontconfig_priv);
+    if (render_priv && render_priv->synth_priv)
+        ass_synth_done(render_priv->synth_priv);
+    if (render_priv && render_priv->eimg)
+        free(render_priv->eimg);
+    free(render_priv->text_info.glyphs);
+    free(render_priv->text_info.lines);
+
+    free(render_priv->settings.default_font);
+    free(render_priv->settings.default_family);
+
+    free_list_clear(render_priv);
+    free(render_priv);
 }
 
 /**
- * \brief Create a new ass_image_t
- * Parameters are the same as ass_image_t fields.
+ * \brief Create a new ASS_Image
+ * Parameters are the same as ASS_Image fields.
  */
-static ass_image_t* my_draw_bitmap(unsigned char* bitmap, int bitmap_w, int bitmap_h, int stride, int dst_x, int dst_y, uint32_t color)
+static ASS_Image *my_draw_bitmap(unsigned char *bitmap, int bitmap_w,
+                                 int bitmap_h, int stride, int dst_x,
+                                 int dst_y, uint32_t color)
 {
-       ass_image_t* img = calloc(1, sizeof(ass_image_t));
-
-       assert(dst_x >= 0);
-       assert(dst_y >= 0);
-       assert(dst_x + bitmap_w <= frame_context.width);
-       assert(dst_y + bitmap_h <= frame_context.height);
-
-       img->w = bitmap_w;
-       img->h = bitmap_h;
-       img->stride = stride;
-       img->bitmap = bitmap;
-       img->color = color;
-       img->dst_x = dst_x;
-       img->dst_y = dst_y;
-
-       return img;
+    ASS_Image *img = calloc(1, sizeof(ASS_Image));
+
+    img->w = bitmap_w;
+    img->h = bitmap_h;
+    img->stride = stride;
+    img->bitmap = bitmap;
+    img->color = color;
+    img->dst_x = dst_x;
+    img->dst_y = dst_y;
+
+    return img;
+}
+
+static double x2scr_pos(ASS_Renderer *render_priv, double x);
+static double y2scr_pos(ASS_Renderer *render_priv, double y);
+
+/*
+ * \brief Convert bitmap glyphs into ASS_Image list with inverse clipping
+ *
+ * Inverse clipping with the following strategy:
+ * - find rectangle from (x0, y0) to (cx0, y1)
+ * - find rectangle from (cx0, y0) to (cx1, cy0)
+ * - find rectangle from (cx0, cy1) to (cx1, y1)
+ * - find rectangle from (cx1, y0) to (x1, y1)
+ * These rectangles can be invalid and in this case are discarded.
+ * Afterwards, they are clipped against the screen coordinates.
+ * In an additional pass, the rectangles need to be split up left/right for
+ * karaoke effects.  This can result in a lot of bitmaps (6 to be exact).
+ */
+static ASS_Image **render_glyph_i(ASS_Renderer *render_priv,
+                                  Bitmap *bm, int dst_x, int dst_y,
+                                  uint32_t color, uint32_t color2, int brk,
+                                  ASS_Image **tail)
+{
+    int i, j, x0, y0, x1, y1, cx0, cy0, cx1, cy1, sx, sy, zx, zy;
+    Rect r[4];
+    ASS_Image *img;
+
+    dst_x += bm->left;
+    dst_y += bm->top;
+
+    // we still need to clip against screen boundaries
+    zx = x2scr_pos(render_priv, 0);
+    zy = y2scr_pos(render_priv, 0);
+    sx = x2scr_pos(render_priv, render_priv->track->PlayResX);
+    sy = y2scr_pos(render_priv, render_priv->track->PlayResY);
+
+    x0 = 0;
+    y0 = 0;
+    x1 = bm->w;
+    y1 = bm->h;
+    cx0 = render_priv->state.clip_x0 - dst_x;
+    cy0 = render_priv->state.clip_y0 - dst_y;
+    cx1 = render_priv->state.clip_x1 - dst_x;
+    cy1 = render_priv->state.clip_y1 - dst_y;
+
+    // calculate rectangles and discard invalid ones while we're at it.
+    i = 0;
+    r[i].x0 = x0;
+    r[i].y0 = y0;
+    r[i].x1 = (cx0 > x1) ? x1 : cx0;
+    r[i].y1 = y1;
+    if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++;
+    r[i].x0 = (cx0 < 0) ? x0 : cx0;
+    r[i].y0 = y0;
+    r[i].x1 = (cx1 > x1) ? x1 : cx1;
+    r[i].y1 = (cy0 > y1) ? y1 : cy0;
+    if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++;
+    r[i].x0 = (cx0 < 0) ? x0 : cx0;
+    r[i].y0 = (cy1 < 0) ? y0 : cy1;
+    r[i].x1 = (cx1 > x1) ? x1 : cx1;
+    r[i].y1 = y1;
+    if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++;
+    r[i].x0 = (cx1 < 0) ? x0 : cx1;
+    r[i].y0 = y0;
+    r[i].x1 = x1;
+    r[i].y1 = y1;
+    if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++;
+
+    // clip each rectangle to screen coordinates
+    for (j = 0; j < i; j++) {
+        r[j].x0 = (r[j].x0 + dst_x < zx) ? zx - dst_x : r[j].x0;
+        r[j].y0 = (r[j].y0 + dst_y < zy) ? zy - dst_y : r[j].y0;
+        r[j].x1 = (r[j].x1 + dst_x > sx) ? sx - dst_x : r[j].x1;
+        r[j].y1 = (r[j].y1 + dst_y > sy) ? sy - dst_y : r[j].y1;
+    }
+
+    // draw the rectangles
+    for (j = 0; j < i; j++) {
+        int lbrk = brk;
+        // kick out rectangles that are invalid now
+        if (r[j].x1 <= r[j].x0 || r[j].y1 <= r[j].y0)
+            continue;
+        // split up into left and right for karaoke, if needed
+        if (lbrk > r[j].x0) {
+            if (lbrk > r[j].x1) lbrk = r[j].x1;
+            img = my_draw_bitmap(bm->buffer + r[j].y0 * bm->w + r[j].x0,
+                lbrk - r[j].x0, r[j].y1 - r[j].y0,
+                bm->w, dst_x + r[j].x0, dst_y + r[j].y0, color);
+            *tail = img;
+            tail = &img->next;
+        }
+        if (lbrk < r[j].x1) {
+            if (lbrk < r[j].x0) lbrk = r[j].x0;
+            img = my_draw_bitmap(bm->buffer + r[j].y0 * bm->w + lbrk,
+                r[j].x1 - lbrk, r[j].y1 - r[j].y0,
+                bm->w, dst_x + lbrk, dst_y + r[j].y0, color2);
+            *tail = img;
+            tail = &img->next;
+        }
+    }
+
+    return tail;
 }
 
 /**
- * \brief convert bitmap glyph into ass_image_t struct(s)
+ * \brief convert bitmap glyph into ASS_Image struct(s)
  * \param bit freetype bitmap glyph, FT_PIXEL_MODE_GRAY
  * \param dst_x bitmap x coordinate in video frame
  * \param dst_y bitmap y coordinate in video frame
@@ -345,1156 +323,871 @@ static ass_image_t* my_draw_bitmap(unsigned char* bitmap, int bitmap_w, int bitm
  * \return pointer to the new list tail
  * Performs clipping. Uses my_draw_bitmap for actual bitmap convertion.
  */
-static ass_image_t** render_glyph(bitmap_t* bm, int dst_x, int dst_y, uint32_t color, uint32_t color2, int brk, ass_image_t** tail)
+static ASS_Image **
+render_glyph(ASS_Renderer *render_priv, Bitmap *bm, int dst_x, int dst_y,
+             uint32_t color, uint32_t color2, int brk, ASS_Image **tail)
 {
-       // brk is relative to dst_x
-       // color = color left of brk
-       // color2 = color right of brk
-       int b_x0, b_y0, b_x1, b_y1; // visible part of the bitmap
-       int tmp;
-       ass_image_t* img;
-
-       const int clip_x0 = render_context.clip_x0;
-       const int clip_y0 = render_context.clip_y0;
-       const int clip_x1 = render_context.clip_x1;
-       const int clip_y1 = render_context.clip_y1;
-
-       dst_x += bm->left;
-       dst_y += bm->top;
-       brk -= bm->left;
-
-       b_x0 = 0;
-       b_y0 = 0;
-       b_x1 = bm->w;
-       b_y1 = bm->h;
-
-       tmp = dst_x - clip_x0;
-       if (tmp < 0) {
-               mp_msg(MSGT_ASS, MSGL_DBG2, "clip left\n");
-               b_x0 = - tmp;
-       }
-       tmp = dst_y - clip_y0;
-       if (tmp < 0) {
-               mp_msg(MSGT_ASS, MSGL_DBG2, "clip top\n");
-               b_y0 = - tmp;
-       }
-       tmp = clip_x1 - dst_x - bm->w;
-       if (tmp < 0) {
-               mp_msg(MSGT_ASS, MSGL_DBG2, "clip right\n");
-               b_x1 = bm->w + tmp;
-       }
-       tmp = clip_y1 - dst_y - bm->h;
-       if (tmp < 0) {
-               mp_msg(MSGT_ASS, MSGL_DBG2, "clip bottom\n");
-               b_y1 = bm->h + tmp;
-       }
-
-       if ((b_y0 >= b_y1) || (b_x0 >= b_x1))
-               return tail;
-
-       if (brk > b_x0) { // draw left part
-               if (brk > b_x1) brk = b_x1;
-               img = my_draw_bitmap(bm->buffer + bm->w * b_y0 + b_x0,
-                       brk - b_x0, b_y1 - b_y0, bm->w,
-                       dst_x + b_x0, dst_y + b_y0, color);
-               *tail = img;
-               tail = &img->next;
-       }
-       if (brk < b_x1) { // draw right part
-               if (brk < b_x0) brk = b_x0;
-               img = my_draw_bitmap(bm->buffer + bm->w * b_y0 + brk,
-                       b_x1 - brk, b_y1 - b_y0, bm->w,
-                       dst_x + brk, dst_y + b_y0, color2);
-               *tail = img;
-               tail = &img->next;
-       }
-       return tail;
+    // Inverse clipping in use?
+    if (render_priv->state.clip_mode)
+        return render_glyph_i(render_priv, bm, dst_x, dst_y, color, color2,
+                              brk, tail);
+
+    // brk is relative to dst_x
+    // color = color left of brk
+    // color2 = color right of brk
+    int b_x0, b_y0, b_x1, b_y1; // visible part of the bitmap
+    int clip_x0, clip_y0, clip_x1, clip_y1;
+    int tmp;
+    ASS_Image *img;
+
+    dst_x += bm->left;
+    dst_y += bm->top;
+    brk -= bm->left;
+
+    // clipping
+    clip_x0 = FFMINMAX(render_priv->state.clip_x0, 0, render_priv->width);
+    clip_y0 = FFMINMAX(render_priv->state.clip_y0, 0, render_priv->height);
+    clip_x1 = FFMINMAX(render_priv->state.clip_x1, 0, render_priv->width);
+    clip_y1 = FFMINMAX(render_priv->state.clip_y1, 0, render_priv->height);
+    b_x0 = 0;
+    b_y0 = 0;
+    b_x1 = bm->w;
+    b_y1 = bm->h;
+
+    tmp = dst_x - clip_x0;
+    if (tmp < 0) {
+        ass_msg(render_priv->library, MSGL_DBG2, "clip left");
+        b_x0 = -tmp;
+    }
+    tmp = dst_y - clip_y0;
+    if (tmp < 0) {
+        ass_msg(render_priv->library, MSGL_DBG2, "clip top");
+        b_y0 = -tmp;
+    }
+    tmp = clip_x1 - dst_x - bm->w;
+    if (tmp < 0) {
+        ass_msg(render_priv->library, MSGL_DBG2, "clip right");
+        b_x1 = bm->w + tmp;
+    }
+    tmp = clip_y1 - dst_y - bm->h;
+    if (tmp < 0) {
+        ass_msg(render_priv->library, MSGL_DBG2, "clip bottom");
+        b_y1 = bm->h + tmp;
+    }
+
+    if ((b_y0 >= b_y1) || (b_x0 >= b_x1))
+        return tail;
+
+    if (brk > b_x0) {           // draw left part
+        if (brk > b_x1)
+            brk = b_x1;
+        img = my_draw_bitmap(bm->buffer + bm->w * b_y0 + b_x0,
+                             brk - b_x0, b_y1 - b_y0, bm->w,
+                             dst_x + b_x0, dst_y + b_y0, color);
+        *tail = img;
+        tail = &img->next;
+    }
+    if (brk < b_x1) {           // draw right part
+        if (brk < b_x0)
+            brk = b_x0;
+        img = my_draw_bitmap(bm->buffer + bm->w * b_y0 + brk,
+                             b_x1 - brk, b_y1 - b_y0, bm->w,
+                             dst_x + brk, dst_y + b_y0, color2);
+        *tail = img;
+        tail = &img->next;
+    }
+    return tail;
 }
 
 /**
- * \brief Replaces the bitmap buffer in ass_image_t with its copy.
- *
- * @param img Image to operate on.
- * @return Address of the old buffer.
+ * \brief Replace the bitmap buffer in ASS_Image with a copy
+ * \param img ASS_Image to operate on
+ * \return pointer to old bitmap buffer
  */
-static unsigned char* clone_bitmap_data(ass_image_t* img)
+static unsigned char *clone_bitmap_buffer(ASS_Image *img)
 {
-       unsigned char* old_bitmap = img->bitmap;
-       int size = img->stride * (img->h - 1) + img->w;
-       img->bitmap = malloc(size);
-       memcpy(img->bitmap, old_bitmap, size);
-       return old_bitmap;
+    unsigned char *old_bitmap = img->bitmap;
+    int size = img->stride * (img->h - 1) + img->w;
+    img->bitmap = malloc(size);
+    memcpy(img->bitmap, old_bitmap, size);
+    return old_bitmap;
 }
 
 /**
  * \brief Calculate overlapping area of two consecutive bitmaps and in case they
- * overlap, composite them together
+ * overlap, blend them together
  * Mainly useful for translucent glyphs and especially borders, to avoid the
  * luminance adding up where they overlap (which looks ugly)
  */
-static void render_overlap(ass_image_t** last_tail, ass_image_t** tail, bitmap_hash_key_t *last_hash, bitmap_hash_key_t* hash) {
-       int left, top, bottom, right;
-       int old_left, old_top, w, h, cur_left, cur_top;
-       int x, y, opos, cpos;
-       char m;
-       composite_hash_key_t hk;
-       composite_hash_val_t *hv;
-       composite_hash_key_t *nhk;
-       int ax = (*last_tail)->dst_x;
-       int ay = (*last_tail)->dst_y;
-       int aw = (*last_tail)->w;
-       int as = (*last_tail)->stride;
-       int ah = (*last_tail)->h;
-       int bx = (*tail)->dst_x;
-       int by = (*tail)->dst_y;
-       int bw = (*tail)->w;
-       int bs = (*tail)->stride;
-       int bh = (*tail)->h;
-       unsigned char* a;
-       unsigned char* b;
-
-       if ((*last_tail)->bitmap == (*tail)->bitmap)
-               return;
-
-       if ((*last_tail)->color != (*tail)->color)
-               return;
-
-       // Calculate overlap coordinates
-       left = (ax > bx) ? ax : bx;
-       top = (ay > by) ? ay : by;
-       right = ((ax+aw) < (bx+bw)) ? (ax+aw) : (bx+bw);
-       bottom = ((ay+ah) < (by+bh)) ? (ay+ah) : (by+bh);
-       if ((right <= left) || (bottom <= top))
-               return;
-       old_left = left-ax;
-       old_top = top-ay;
-       w = right-left;
-       h = bottom-top;
-       cur_left = left-bx;
-       cur_top = top-by;
-
-       // Query cache
-       memset(&hk, 0, sizeof(hk));
-       memcpy(&hk.a, last_hash, sizeof(*last_hash));
-       memcpy(&hk.b, hash, sizeof(*hash));
-       hk.aw = aw;
-       hk.ah = ah;
-       hk.bw = bw;
-       hk.bh = bh;
-       hk.ax = ax;
-       hk.ay = ay;
-       hk.bx = bx;
-       hk.by = by;
-       hv = cache_find_composite(&hk);
-       if (hv) {
-               (*last_tail)->bitmap = hv->a;
-               (*tail)->bitmap = hv->b;
-               return;
-       }
-
-       // Allocate new bitmaps and copy over data
-       a = clone_bitmap_data(*last_tail);
-       b = clone_bitmap_data(*tail);
-
-       // Composite overlapping area
-       for (y=0; y<h; y++)
-               for (x=0; x<w; x++) {
-                       opos = (old_top+y)*(as) + (old_left+x);
-                       cpos = (cur_top+y)*(bs) + (cur_left+x);
-                       m = (a[opos] > b[cpos]) ? a[opos] : b[cpos];
-                       (*last_tail)->bitmap[opos] = 0;
-                       (*tail)->bitmap[cpos] = m;
-               }
-
-       // Insert bitmaps into the cache
-       nhk = calloc(1, sizeof(*nhk));
-       memcpy(nhk, &hk, sizeof(*nhk));
-       hv = calloc(1, sizeof(*hv));
-       hv->a = (*last_tail)->bitmap;
-       hv->b = (*tail)->bitmap;
-       cache_add_composite(nhk, hv);
+static void
+render_overlap(ASS_Renderer *render_priv, ASS_Image **last_tail,
+               ASS_Image **tail)
+{
+    int left, top, bottom, right;
+    int old_left, old_top, w, h, cur_left, cur_top;
+    int x, y, opos, cpos;
+    char m;
+    CompositeHashKey hk;
+    CompositeHashValue *hv;
+    CompositeHashValue chv;
+    int ax = (*last_tail)->dst_x;
+    int ay = (*last_tail)->dst_y;
+    int aw = (*last_tail)->w;
+    int as = (*last_tail)->stride;
+    int ah = (*last_tail)->h;
+    int bx = (*tail)->dst_x;
+    int by = (*tail)->dst_y;
+    int bw = (*tail)->w;
+    int bs = (*tail)->stride;
+    int bh = (*tail)->h;
+    unsigned char *a;
+    unsigned char *b;
+
+    if ((*last_tail)->bitmap == (*tail)->bitmap)
+        return;
+
+    if ((*last_tail)->color != (*tail)->color)
+        return;
+
+    // Calculate overlap coordinates
+    left = (ax > bx) ? ax : bx;
+    top = (ay > by) ? ay : by;
+    right = ((ax + aw) < (bx + bw)) ? (ax + aw) : (bx + bw);
+    bottom = ((ay + ah) < (by + bh)) ? (ay + ah) : (by + bh);
+    if ((right <= left) || (bottom <= top))
+        return;
+    old_left = left - ax;
+    old_top = top - ay;
+    w = right - left;
+    h = bottom - top;
+    cur_left = left - bx;
+    cur_top = top - by;
+
+    // Query cache
+    memset(&hk, 0, sizeof(hk));
+    hk.a = (*last_tail)->bitmap;
+    hk.b = (*tail)->bitmap;
+    hk.aw = aw;
+    hk.ah = ah;
+    hk.bw = bw;
+    hk.bh = bh;
+    hk.ax = ax;
+    hk.ay = ay;
+    hk.bx = bx;
+    hk.by = by;
+    hk.as = as;
+    hk.bs = bs;
+    hv = cache_find_composite(render_priv->cache.composite_cache, &hk);
+    if (hv) {
+        (*last_tail)->bitmap = hv->a;
+        (*tail)->bitmap = hv->b;
+        return;
+    }
+    // Allocate new bitmaps and copy over data
+    a = clone_bitmap_buffer(*last_tail);
+    b = clone_bitmap_buffer(*tail);
+
+    // Blend overlapping area
+    for (y = 0; y < h; y++)
+        for (x = 0; x < w; x++) {
+            opos = (old_top + y) * (as) + (old_left + x);
+            cpos = (cur_top + y) * (bs) + (cur_left + x);
+            m = FFMIN(a[opos] + b[cpos], 0xff);
+            (*last_tail)->bitmap[opos] = 0;
+            (*tail)->bitmap[cpos] = m;
+        }
+
+    // Insert bitmaps into the cache
+    chv.a = (*last_tail)->bitmap;
+    chv.b = (*tail)->bitmap;
+    cache_add_composite(render_priv->cache.composite_cache, &hk, &chv);
 }
 
-/**
- * \brief Convert text_info_t struct to ass_image_t list
- * Splits glyphs in halves when needed (for \kf karaoke).
- */
-static ass_image_t* render_text(text_info_t* text_info, int dst_x, int dst_y)
+static void free_list_add(ASS_Renderer *render_priv, void *object)
 {
-       int pen_x, pen_y;
-       int i;
-       bitmap_t* bm;
-       ass_image_t* head;
-       ass_image_t** tail = &head;
-       ass_image_t** last_tail = 0;
-       ass_image_t** here_tail = 0;
-       bitmap_hash_key_t* last_hash = 0;
-
-       for (i = 0; i < text_info->length; ++i) {
-               glyph_info_t* info = text_info->glyphs + i;
-               if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm_s || (info->shadow == 0))
-                       continue;
-
-               pen_x = dst_x + info->pos.x + ROUND(info->shadow * frame_context.border_scale);
-               pen_y = dst_y + info->pos.y + ROUND(info->shadow * frame_context.border_scale);
-               bm = info->bm_s;
-
-               here_tail = tail;
-               tail = render_glyph(bm, pen_x, pen_y, info->c[3], 0, 1000000, tail);
-               if (last_tail && tail != here_tail && ((info->c[3] & 0xff) > 0))
-                       render_overlap(last_tail, here_tail, last_hash, &info->hash_key);
-               last_tail = here_tail;
-               last_hash = &info->hash_key;
-       }
-
-       last_tail = 0;
-       for (i = 0; i < text_info->length; ++i) {
-               glyph_info_t* info = text_info->glyphs + i;
-               if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm_o)
-                       continue;
-
-               pen_x = dst_x + info->pos.x;
-               pen_y = dst_y + info->pos.y;
-               bm = info->bm_o;
-
-               if ((info->effect_type == EF_KARAOKE_KO) && (info->effect_timing <= info->bbox.xMax)) {
-                       // do nothing
-               } else {
-                       here_tail = tail;
-                       tail = render_glyph(bm, pen_x, pen_y, info->c[2], 0, 1000000, tail);
-                       if (last_tail && tail != here_tail && ((info->c[2] & 0xff) > 0))
-                               render_overlap(last_tail, here_tail, last_hash, &info->hash_key);
-                       last_tail = here_tail;
-                       last_hash = &info->hash_key;
-               }
-       }
-       for (i = 0; i < text_info->length; ++i) {
-               glyph_info_t* info = text_info->glyphs + i;
-               if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm)
-                       continue;
-
-               pen_x = dst_x + info->pos.x;
-               pen_y = dst_y + info->pos.y;
-               bm = info->bm;
-
-               if ((info->effect_type == EF_KARAOKE) || (info->effect_type == EF_KARAOKE_KO)) {
-                       if (info->effect_timing > info->bbox.xMax)
-                               tail = render_glyph(bm, pen_x, pen_y, info->c[0], 0, 1000000, tail);
-                       else
-                               tail = render_glyph(bm, pen_x, pen_y, info->c[1], 0, 1000000, tail);
-               } else if (info->effect_type == EF_KARAOKE_KF) {
-                       tail = render_glyph(bm, pen_x, pen_y, info->c[0], info->c[1], info->effect_timing, tail);
-               } else
-                       tail = render_glyph(bm, pen_x, pen_y, info->c[0], 0, 1000000, tail);
-       }
-
-       *tail = 0;
-       return head;
+    if (!render_priv->free_head) {
+        render_priv->free_head = calloc(1, sizeof(FreeList));
+        render_priv->free_head->object = object;
+        render_priv->free_tail = render_priv->free_head;
+    } else {
+        FreeList *l = calloc(1, sizeof(FreeList));
+        l->object = object;
+        render_priv->free_tail->next = l;
+        render_priv->free_tail = render_priv->free_tail->next;
+    }
 }
 
 /**
- * \brief Mapping between script and screen coordinates
+ * Iterate through a list of bitmaps and blend with clip vector, if
+ * applicable. The blended bitmaps are added to a free list which is freed
+ * at the start of a new frame.
  */
-static int x2scr(double x) {
-       return x*frame_context.orig_width_nocrop / frame_context.track->PlayResX +
-               FFMAX(global_settings->left_margin, 0);
-}
-static double x2scr_pos(double x) {
-       return x*frame_context.orig_width / frame_context.track->PlayResX +
-               global_settings->left_margin;
+static void blend_vector_clip(ASS_Renderer *render_priv,
+                              ASS_Image *head)
+{
+    FT_Glyph glyph;
+    FT_BitmapGlyph clip_bm;
+    ASS_Image *cur;
+    ASS_Drawing *drawing = render_priv->state.clip_drawing;
+    int error;
+
+    if (!drawing)
+        return;
+
+    // Rasterize it
+    FT_Glyph_Copy((FT_Glyph) drawing->glyph, &glyph);
+    error = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 1);
+    if (error) {
+        ass_msg(render_priv->library, MSGL_V,
+            "Clip vector rasterization failed: %d. Skipping.", error);
+        goto blend_vector_exit;
+    }
+    clip_bm = (FT_BitmapGlyph) glyph;
+    clip_bm->top = -clip_bm->top;
+
+    assert(clip_bm->bitmap.pit