[MPlayer-cvslog] r30242 - in trunk: 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 libass/ass_drawing.c libass/ass_drawi...

greg subversion at mplayerhq.hu
Fri Jan 8 19:35:44 CET 2010


Author: greg
Date: Fri Jan  8 19:35:44 2010
New Revision: 30242

Log:
Update internal libass copy to commit 8db4a5

Added:
   trunk/libass/ass_cache_template.h
   trunk/libass/ass_drawing.c
   trunk/libass/ass_drawing.h
   trunk/libass/ass_parse.c
   trunk/libass/ass_parse.h
   trunk/libass/ass_render.h
   trunk/libass/ass_strtod.c
Modified:
   trunk/Makefile
   trunk/libass/ass.c
   trunk/libass/ass.h
   trunk/libass/ass_bitmap.c
   trunk/libass/ass_bitmap.h
   trunk/libass/ass_cache.c
   trunk/libass/ass_cache.h
   trunk/libass/ass_font.c
   trunk/libass/ass_font.h
   trunk/libass/ass_fontconfig.c
   trunk/libass/ass_fontconfig.h
   trunk/libass/ass_library.c
   trunk/libass/ass_library.h
   trunk/libass/ass_render.c
   trunk/libass/ass_types.h
   trunk/libass/ass_utils.c
   trunk/libass/ass_utils.h

Modified: trunk/Makefile
==============================================================================
--- trunk/Makefile	Fri Jan  8 19:19:10 2010	(r30241)
+++ trunk/Makefile	Fri Jan  8 19:35:44 2010	(r30242)
@@ -122,10 +122,13 @@ SRCS_COMMON-$(LIBASS)                += 
 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 \

Modified: trunk/libass/ass.c
==============================================================================
--- trunk/libass/ass.c	Fri Jan  8 19:19:10 2010	(r30241)
+++ trunk/libass/ass.c	Fri Jan  8 19:35:44 2010	(r30242)
@@ -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 at gmail.com>
  *
@@ -39,117 +37,137 @@
 #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;
+typedef enum {
+    PST_UNKNOWN = 0,
+    PST_INFO,
+    PST_STYLES,
+    PST_EVENTS,
+    PST_FONTS
+} ParserState;
 
-struct parser_priv_s {
-	parser_state_t state;
-	char* fontname;
-	char* fontdata;
-	int fontdata_size;
-	int fontdata_used;
+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;
+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);
-	}
+    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, cha
  * 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");
-	}
+    char *token;
+    char *tname;
+    char *p = str;
+    int i;
+    ASS_Event *target = event;
 
-	q = format = strdup(track->event_format);
+    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");
-	}
+    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);
-	}
+    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);
+    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;
+        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_
  * \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;
+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;
+    if (!list)
+        return;
 
-	for (fs = list; *fs; ++fs) {
-		eq = strrchr(*fs, '=');
-		if (!eq)
-			continue;
-		*eq = '\0';
-		token = eq + 1;
+    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);
+        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 = '.';
-	}
+        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* tr
  * \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;
+    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");
-	}
+    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);
+    q = format = strdup(track->style_format);
 
-	mp_msg(MSGT_ASS, MSGL_V, "[%p] Style: %s\n", track, str);
+    ass_msg(track->library, MSGL_V, "[%p] Style: %s", track, str);
 
-	sid = ass_alloc_style(track);
+    sid = ass_alloc_style(track);
 
-	style = track->styles + sid;
-	target = style;
-// fill style with some default values
-	style->ScaleX = 100.;
-	style->ScaleY = 100.;
+    style = track->styles + sid;
+    target = style;
 
-	while (1) {
-		NEXT(q, tname);
-		NEXT(p, token);
+    // fill style with some default values
+    style->ScaleX = 100.;
+    style->ScaleY = 100.;
 
-//		ALIAS(TertiaryColour,OutlineColour) // ignore TertiaryColour; it appears only in SSA, and is overridden by BackColour
+    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;
+        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;
+    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");
+}
 
-		str += 9;
-		skip_spaces(&str);
+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;
 
-		eid = ass_alloc_event(track);
-		event = track->events + eid;
+        str += 9;
+        skip_spaces(&str);
 
-		process_event_tail(track, event, str, 0);
-	} else {
-		mp_msg(MSGT_ASS, MSGL_V, "Not understood: %s  \n", str);
-	}
-	return 0;
+        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;
+    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;
+    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;
+    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;
+    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);
+    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;
-	}
+    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;
+  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;
+    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 (!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) {
-		mp_msg(MSGT_ASS, MSGL_V, "Not understood: %s  \n", str);
-		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) {
-		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;
+    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;
+    return 0;
 }
 
 /**
@@ -664,67 +738,72 @@ static int process_fonts_line(ass_track_
  * \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;
-		}
-	}
+    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);
+    // 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;
+    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* tra
  * \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
  * \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);
+    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");
-	}
+    // probably an mkv produced by ancient mkvtoolnix
+    // such files don't have [Events] and Format: headers
+    if (!track->event_format)
+        event_format_fallback(track);
 
-	process_force_style(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_tra
  * \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;
+    char *str;
+    int eid;
+    char *p;
+    char *token;
+    ASS_Event *event;
 
-	if (!track->event_format) {
-		mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_EventFormatHeaderMissing);
-		return;
-	}
+    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';
-	mp_msg(MSGT_ASS, MSGL_V, "event at %" PRId64 ", +%" PRId64 ": %s  \n", (int64_t)timecode, (int64_t)duration, str);
+    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;
+    eid = ass_alloc_event(track);
+    event = track->events + eid;
 
-	p = str;
+    p = str;
 
-	do {
-		NEXT(p, token);
-		event->ReadOrder = atoi(token);
-		if (check_duplicate_event(track, event->ReadOrder))
-			break;
+    do {
+        NEXT(p, token);
+        event->ReadOrder = atoi(token);
+        if (check_duplicate_event(track, event->ReadOrder))
+            break;
 
-		NEXT(p, token);
-		event->Layer = atoi(token);
+        NEXT(p, token);
+        event->Layer = atoi(token);
 
-		process_event_tail(track, event, p, 3);
+        process_event_tail(track, event, p, 3);
 
-		event->Start = timecode;
-		event->Duration = duration;
+        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);
+        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* trac
  * \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);
-	}
+        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;
+    {
+        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;
+        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;
-	}
+        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);
-		mp_msg(MSGT_ASS,MSGL_V,"LIBSUB: closed iconv descriptor.\n");
-	}
+    if (icdsc != (iconv_t) (-1)) {
+        (void) iconv_close(icdsc);
+        icdsc = (iconv_t) (-1);
+        ass_msg(library, MSGL_V, "Closed iconv descriptor");
+    }
 
-	return outbuf;
+    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
  * \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;
+    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;
-	}
+    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);
+    sz = ftell(fp);
+    rewind(fp);
 
-	if (sz > 10*1024*1024) {
-		mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_RefusingToLoadSubtitlesLargerThan10M, fname);
-		fclose(fp);
-		return 0;
-	}
+    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;
+    }
 
-	mp_msg(MSGT_ASS, MSGL_V, "file size: %ld\n", sz);
+    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) {
-			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);
+    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;
+    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_lib
  * \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;
+    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;
+    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
  * \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;
+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)
+        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) {}
+    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;
+    // -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;
 }
-

Modified: trunk/libass/ass.h
==============================================================================
--- trunk/libass/ass.h	Fri Jan  8 19:19:10 2010	(r30241)
+++ trunk/libass/ass.h	Fri Jan  8 19:35:44 2010	(r30242)
@@ -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 at gmail.com>
  *
@@ -24,136 +22,271 @@
 #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
+/*
+ * 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_s* next; // linked list
-} ass_image_t;
+    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
+ * \brief Finalize the renderer.
  * \param priv renderer handle
  */
-void ass_renderer_done(ass_renderer_t* priv);
+void ass_renderer_done(ASS_Renderer *priv);
 
-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 the frame size in pixels, including margins.
+ * \param priv renderer handle
+ * \param w width
+ * \param h height
+ */
+void ass_set_frame_size(ASS_Renderer *priv, int w, int h);
 
 /**
- * \brief set font lookup defaults
+ * \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
  */
-int  ass_set_fonts(ass_renderer_t* priv, const char* default_font, const char* default_family);
+void ass_set_margins(ASS_Renderer *priv, int t, int b, int l, int r);
 
 /**
- * \brief set font lookup defaults, don't use fontconfig even if it is available
+ * \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_nofc(ass_renderer_t* priv, const char* default_font, const char* default_family);
+void ass_set_use_margins(ASS_Renderer *priv, int use);
 
 /**
- * \brief render a frame, producing a list of ass_image_t
- * \param priv library
+ * \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)
+ */
+void ass_set_aspect_ratio(ASS_Renderer *priv, double dar, double sar);
+
+/**
+ * \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, 
  * \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 */

Modified: trunk/libass/ass_bitmap.c
==============================================================================
--- trunk/libass/ass_bitmap.c	Fri Jan  8 19:19:10 2010	(r30241)
+++ trunk/libass/ass_bitmap.c	Fri Jan  8 19:35:44 2010	(r30242)
@@ -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 at gmail.com>
  *
@@ -27,311 +25,513 @@
 #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;
+    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;
+    if (priv->radius == radius)
+        return 0;
+    else
+        priv->radius = radius;
 
-	priv->g_r = ceil(radius);
-	priv->g_w = 2*priv->g_r+1;
+    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) {
+        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];
-		}
+    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];
-			}
-		}
-	}
+        // 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;
+    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;
+    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(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;
-	}
+    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) {
-		mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_UnsupportedPixelMode, (int)(bit->pixel_mode));
-		FT_Done_Glyph(glyph);
-		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;
+    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;
-	}
+    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_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 bitmap_t* fix_outline_and_shadow(bitmap_t* bm_g, bitmap_t* bm_o)
+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;
+    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* 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;
+        }
+        g += bm_g->w;
+        o += bm_o->w;
+    }
+}
 
-	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;
-	}
+/**
+ * \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;
 
-	assert(bm_s);
-	return bm_s;
+    // 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 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;
+
+    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;
+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 (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;
-		}
-	}
+    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;
+    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);
+    assert(bm_g && bm_o && bm_s);
 
-	*bm_g = *bm_o = *bm_s = 0;
+    *bm_g = *bm_o = *bm_s = 0;
 
-	if (glyph)
-		*bm_g = glyph_to_bitmap_internal(glyph, bord);
-	if (!*bm_g)
-		return 1;
+    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(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 (outline_glyph) {
+        *bm_o = glyph_to_bitmap_internal(library, outline_glyph, bord);
+        if (!*bm_o) {
+            return 1;
+        }
+    }
 
-	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);
+    // 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);
+    }
 
-	assert(bm_s);
-	return 0;
-}
+    // 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;
+}

Modified: trunk/libass/ass_bitmap.h
==============================================================================
--- trunk/libass/ass_bitmap.h	Fri Jan  8 19:19:10 2010	(r30241)
+++ trunk/libass/ass_bitmap.h	Fri Jan  8 19:35:44 2010	(r30242)
@@ -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 at gmail.com>
  *
@@ -26,16 +24,18 @@
 #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 */

Modified: trunk/libass/ass_cache.c
==============================================================================
--- trunk/libass/ass_cache.c	Fri Jan  8 19:19:10 2010	(r30241)
+++ trunk/libass/ass_cache.c	Fri Jan  8 19:35:44 2010	(r30242)
@@ -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 at gmail.com>
  *
@@ -29,225 +27,209 @@
 
 #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)
-{
-	unsigned char* s = (unsigned char*)str;
-	while (*s) {
-		hval ^= (unsigned)*s++;
-		hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24);
-	}
-	return hval;
-}
-
-static unsigned hashmap_hash(void* buf, size_t len)
+static unsigned hashmap_hash(void *buf, size_t len)
 {
-	return fnv_32a_buf(buf, len, FNV1_32A_INIT);
+    return fnv_32a_buf(buf, len, FNV1_32A_INIT);
 }
 
-static int hashmap_key_compare(void* a, void* b, size_t size)
+static int hashmap_key_compare(void *a, void *b, size_t size)
 {
-	return memcmp(a, b, size) == 0;
+    return memcmp(a, b, size) == 0;
 }
 
-static void hashmap_item_dtor(void* key, size_t key_size, void* value, size_t value_size)
+static void hashmap_item_dtor(void *key, size_t key_size, void *value,
+                              size_t value_size)
 {
-	free(key);
-	free(value);
+    free(key);
+    free(value);
 }
 
-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)
+Hashmap *hashmap_init(ASS_Library *library, size_t key_size,
+                      size_t value_size, int nbuckets,
+                      HashmapItemDtor item_dtor,
+                      HashmapKeyCompare key_compare,
+                      HashmapHash hash)
 {
-	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;
+    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;
 }
 
-void hashmap_done(hashmap_t* map)
+void hashmap_done(Hashmap *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)
+void *hashmap_insert(Hashmap *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;
+    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;
+    map->count++;
+    return (*next)->value;
 }
 
-void* hashmap_find(hashmap_t* map, void* key)
+void *hashmap_find(Hashmap *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;
+    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
  * \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* 
  * \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
  * \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);
+}

Modified: trunk/libass/ass_cache.h
==============================================================================
--- trunk/libass/ass_cache.h	Fri Jan  8 19:19:10 2010	(r30241)
+++ trunk/libass/ass_cache.h	Fri Jan  8 19:35:44 2010	(r30242)
@@ -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 at gmail.com>
  *
@@ -27,99 +25,95 @@
 #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
+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);
 
-	FT_Vector advance; // subpixel shift vector
-} bitmap_hash_key_t;
+typedef struct hashmap_item {
+    void *key;
+    void *value;
+    struct hashmap_item *next;
+} HashmapItem;
+typedef HashmapItem *hashmap_item_p;
 
-typedef struct bitmap_hash_val_s {
-	bitmap_t* bm; // the actual bitmaps
-	bitmap_t* bm_o;
-	bitmap_t* bm_s;
-} bitmap_hash_val_t;
+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;
 
-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);
+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 *);
 
-// 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;
+// Create definitions for bitmap_hash_key and glyph_hash_key
+#define CREATE_STRUCT_DEFINITIONS
+#include "ass_cache_template.h"
 
-typedef struct composite_hash_val_s {
-	unsigned char* a;
-	unsigned char* b;
-} composite_hash_val_t;
+typedef struct {
+    Bitmap *bm;               // the actual bitmaps
+    Bitmap *bm_o;
+    Bitmap *bm_s;
+} BitmapHashValue;
 
-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);
+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);
 
 
-// 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 {
+    unsigned char *a;
+    unsigned char *b;
+} CompositeHashValue;
 
-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;
+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);
 
-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);
+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_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);
+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 */
+#endif                          /* LIBASS_CACHE_H */

Added: trunk/libass/ass_cache_template.h
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ trunk/libass/ass_cache_template.h	Fri Jan  8 19:35:44 2010	(r30242)
@@ -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

Added: trunk/libass/ass_drawing.c
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ trunk/libass/ass_drawing.c	Fri Jan  8 19:35:44 2010	(r30242)
@@ -0,0 +1,495 @@
+/*
+ * Copyright (C) 2009 Grigori Goronzy <greg at 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;
+}

Added: trunk/libass/ass_drawing.h
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ trunk/libass/ass_drawing.h	Fri Jan  8 19:35:44 2010	(r30242)
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2009 Grigori Goronzy <greg at 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 */

Modified: trunk/libass/ass_font.c
==============================================================================
--- trunk/libass/ass_font.c	Fri Jan  8 19:19:10 2010	(r30241)
+++ trunk/libass/ass_font.c	Fri Jan  8 19:35:44 2010	(r30242)
@@ -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 at 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"
@@ -36,210 +35,224 @@
 #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;
-		}
-	}
+    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;
-	}
+    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;
+    char *path;
+    int index;
+    FT_Face face;
+    int error;
+    int mem_idx;
 
-	if (font->n_faces == ASS_FONT_MAX_FACES)
-		return -1;
+    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;
+    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) {
-			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);
+    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);
-	return font->n_faces - 1;
+    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;
+    int error;
+    ASS_Font *fontp;
+    ASS_Font font;
 
-	fontp = ass_font_cache_find(desc);
-	if (fontp)
-		return fontp;
+    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.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.;
+    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);
+    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,
  * \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;
-		}
-	}
+    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;
+    *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;
+    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;
+    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;
-	}
+    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);
-			}
-		}
-	}
+    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
 
-	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;
-	}
+    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;
-	}
+    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 (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);
-	}
-#endif
-	error = FT_Get_Glyph(face->glyph, &glyph);
-	if (error) {
-		mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_ErrorLoadingGlyph);
-		return 0;
-	}
+    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;
+    }
 
-	return glyph;
+    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;
+    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;
+    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);
 }

Modified: trunk/libass/ass_font.h
==============================================================================
--- trunk/libass/ass_font.h	Fri Jan  8 19:19:10 2010	(r30241)
+++ trunk/libass/ass_font.h	Fri Jan  8 19:35:44 2010	(r30242)
@@ -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 at gmail.com>
  *
@@ -29,32 +27,40 @@
 #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 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 {
+    char *family;
+    unsigned bold;
+    unsigned italic;
+    int treat_family_as_pattern;
+} ASS_FontDesc;
 
-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);
+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;
 
-#endif /* LIBASS_FONT_H */
+// 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 */

Modified: trunk/libass/ass_fontconfig.c
==============================================================================
--- trunk/libass/ass_fontconfig.c	Fri Jan  8 19:19:10 2010	(r30241)
+++ trunk/libass/ass_fontconfig.c	Fri Jan  8 19:35:44 2010	(r30242)
@@ -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 at 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"
@@ -42,26 +40,17 @@
 #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;
+    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;
+    *index = 0;
 
-	if (treat_family_as_pattern)
-		pat = FcNameParse((const FcChar8*)family);
-	else
-		pat = FcPatternCreate();
+    if (treat_family_as_pattern)
+        pat = FcNameParse((const FcChar8 *) family);
+    else
+        pat = FcPatternCreate();
 
-	if (!pat)
-		goto error;
+    if (!pat)
+        goto error;
 
-	if (!treat_family_as_pattern) {
-		FcPatternAddString(pat, FC_FAMILY, (const FcChar8*)family);
+    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);
+        // 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);
+    FcDefaultSubstitute(pat);
 
-	rc = FcConfigSubstitute(priv->config, pat, FcMatchPattern);
-	if (!rc)
-		goto error;
+    rc = FcConfigSubstitute(priv->config, pat, FcMatchPattern);
+    if (!rc)
+        goto error;
 
-	fset = FcFontSort(priv->config, pat, FcTrue, NULL, &result);
-	if (!fset)
-		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];
+    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;
-	}
+        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 (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
+    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;
+    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 = 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_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_FAMILY, 0, &r_family);
+    if (result != FcResultMatch)
+        r_family = NULL;
 
-	result = FcPatternGetString(rpat, FC_FULLNAME, 0, &r_fullname);
-	if (result != FcResultMatch)
-		r_fullname = 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);
+    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 = 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_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 = 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;
+    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" : "");
+    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;
+  error:
+    if (pat)
+        FcPatternDestroy(pat);
+    if (rpat)
+        FcPatternDestroy(rpat);
+    if (fset)
+        FcFontSetDestroy(fset);
+    return retval;
 }
 
 /**
@@ -236,77 +231,51 @@ static char* _select_font(fc_instance_t*
  * \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* 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;
-}
-
-#if (FC_VERSION < 20402)
-static char* validate_fname(char* name)
+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* 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;
+    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;
 }
-#endif
 
 /**
  * \brief Process memory font.
@@ -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);
+    int rc;
+    const char *name = library->fontdata[idx].name;
+    const char *data = library->fontdata[idx].data;
+    int data_size = library->fontdata[idx].size;
 
-#else // (FC_VERSION >= 20402)
-	FT_Face face;
-	FcPattern* pattern;
-	FcFontSet* fset;
-	FcBool res;
-	int face_index, num_faces = 1;
+    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;
+    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) {
-			mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FunctionCallFailed, "FcFreeTypeQueryFace");
-			FT_Done_Face(face);
-			return;
-		}
+        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) {
-			mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FunctionCallFailed, "FcConfigGetFonts");
-			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) {
-			mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FunctionCallFailed, "FcFontSetAdd");
-			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);
-	}
-#endif
+        FT_Done_Face(face);
+    }
 }
 
 /**
@@ -406,113 +343,119 @@ static void process_fontdata(fc_instance
  * \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);
+    int rc;
+    FCInstance *priv = calloc(1, sizeof(FCInstance));
+    const char *dir = library->fonts_dir;
+    int i;
 
-	priv->config = FcConfigGetCurrent();
-	if (!priv->config) {
-		mp_msg(MSGT_ASS, MSGL_FATAL, MSGTR_LIBASS_FcInitLoadConfigAndFontsFailed);
-		goto exit;
-	}
+    if (!fc) {
+        ass_msg(library, MSGL_WARN,
+               "Fontconfig disabled, only default font will be used.");
+        goto exit;
+    }
 
-	for (i = 0; i < library->num_fontdata; ++i)
-		process_fontdata(priv, library, ftlibrary, i);
+    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 (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;
-					}
+    if (!rc || !priv->config) {
+        ass_msg(library, MSGL_FATAL,
+                "No valid fontconfig configuration found!");
+        FcConfigDestroy(priv->config);
+        goto exit;
+    }
 
-					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;
-					}
+    for (i = 0; i < library->num_fontdata; ++i)
+        process_fontdata(priv, library, ftlibrary, i);
 
-					rc = FcDirSave(fcs, fss, (const FcChar8 *)dir);
-					if (!rc) {
-						mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FcDirSave);
-						goto ErrorFontCache;
-					}
-				ErrorFontCache:
-					;
-				}
-			}
+    if (dir) {
+        ass_msg(library, MSGL_INFO, "Updating font cache");
 
-		rc = FcConfigAppFontAddDir(priv->config, (const FcChar8*)dir);
-		if (!rc) {
-			mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FcConfigAppFontAddDirFailed);
-		}
-	}
+        rc = FcConfigAppFontAddDir(priv->config, (const FcChar8 *) dir);
+        if (!rc) {
+            ass_msg(library, MSGL_WARN, "%s failed", "FcConfigAppFontAddDir");
+        }
+    }
 
-	priv->family_default = family ? strdup(family) : NULL;
+    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);
+}
 
-char* fontconfig_select(fc_instance_t* priv, const char* family, int treat_family_as_pattern,
-			unsigned bold, unsigned italic, int* index, uint32_t code)
+#else                           /* CONFIG_FONTCONFIG */
+
+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);
+}

Modified: trunk/libass/ass_fontconfig.h
==============================================================================
--- trunk/libass/ass_fontconfig.h	Fri Jan  8 19:19:10 2010	(r30241)
+++ trunk/libass/ass_fontconfig.h	Fri Jan  8 19:35:44 2010	(r30242)
@@ -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 at gmail.com>
  *
@@ -25,6 +23,7 @@
 
 #include <stdint.h>
 #include "ass_types.h"
+#include "ass.h"
 #include <ft2build.h>
 #include FT_FREETYPE_H
 
@@ -32,10 +31,17 @@
 #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 */

Modified: trunk/libass/ass_library.c
==============================================================================
--- trunk/libass/ass_library.c	Fri Jan  8 19:19:10 2010	(r30241)
+++ trunk/libass/ass_library.c	Fri Jan  8 19:35:44 2010	(r30242)
@@ -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 at gmail.com>
  *
@@ -24,92 +22,126 @@
 #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;
+    char **p;
+    char **q;
+    int cnt;
 
-	if (priv->style_overrides) {
-		for (p = priv->style_overrides; *p; ++p)
-			free(*p);
-		free(priv->style_overrides);
-	}
+    if (priv->style_overrides) {
+        for (p = priv->style_overrides; *p; ++p)
+            free(*p);
+        free(priv->style_overrides);
+    }
 
-	if (!list) return;
+    if (!list)
+        return;
 
-	for (p = list, cnt = 0; *p; ++p, ++cnt) {}
+    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;
+    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;
+    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)
+{
+    if (msg_cb) {
+        priv->msg_callback = msg_cb;
+        priv->msg_callback_data = data;
+    }
 }

Modified: trunk/libass/ass_library.h
==============================================================================
--- trunk/libass/ass_library.h	Fri Jan  8 19:19:10 2010	(r30241)
+++ trunk/libass/ass_library.h	Fri Jan  8 19:35:44 2010	(r30242)
@@ -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 at gmail.com>
  *
@@ -23,19 +21,23 @@
 #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 */

Added: trunk/libass/ass_parse.c
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ trunk/libass/ass_parse.c	Fri Jan  8 19:35:44 2010	(r30242)
@@ -0,0 +1,926 @@
+/*
+ * Copyright (C) 2009 Grigori Goronzy <greg at 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;
+}

Added: trunk/libass/ass_parse.h
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ trunk/libass/ass_parse.h	Fri Jan  8 19:35:44 2010	(r30242)
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2009 Grigori Goronzy <greg at 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 */

Modified: trunk/libass/ass_render.c
==============================================================================
--- trunk/libass/ass_render.c	Fri Jan  8 19:19:10 2010	(r30241)
+++ trunk/libass/ass_render.c	Fri Jan  8 19:35:44 2010	(r30242)
@@ -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 at 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"
@@ -40,301 +36,283 @@
 #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 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;
+#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
 
-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;
+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);
+        }
+    }
+}
 
-// 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_Renderer *ass_renderer_init(ASS_Library *library)
+{
+    int error;
+    FT_Library ft;
+    ASS_Renderer *priv = 0;
+    int vmajor, vminor, vpatch;
 
-	ass_font_t* font;
-	char* font_path;
-	double font_size;
+    error = FT_Init_FreeType(&ft);
+    if (error) {
+        ass_msg(library, MSGL_FATAL, "%s failed", "FT_Init_FreeType");
+        goto ass_init_exit;
+    }
 
-	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
+    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);
 
-	effect_t effect_type;
-	int effect_timing;
-	int effect_skip_timing;
+    priv = calloc(1, sizeof(ASS_Renderer));
+    if (!priv) {
+        FT_Done_FreeType(ft);
+        goto ass_init_exit;
+    }
 
-	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;
+    priv->synth_priv = ass_synth_init(BLUR_MAX_RADIUS);
 
-	// face properties
-	char* family;
-	unsigned bold;
-	unsigned italic;
-	int treat_family_as_pattern;
+    priv->library = library;
+    priv->ftlibrary = ft;
+    // images_root and related stuff is zero-filled in calloc
 
-} render_context_t;
+    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;
 
-// 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;
+    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));
 
-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;
+  ass_init_exit:
+    if (priv)
+        ass_msg(library, MSGL_INFO, "Init");
+    else
+        ass_msg(library, MSGL_ERR, "Init failed");
 
-struct render_priv_s {
-	int top, height;
-	int render_id;
-};
+    return priv;
+}
 
-static void ass_lazy_track_init(void)
+void ass_set_cache_limits(ASS_Renderer *render_priv, int glyph_max,
+                          int bitmap_max)
 {
-	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);
-		}
-	}
+    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;
 }
 
-ass_renderer_t* ass_renderer_init(ass_library_t* library)
+static void free_list_clear(ASS_Renderer *render_priv)
 {
-	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));
+    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;
+    }
+}
 
-	error = FT_Init_FreeType( &ft );
-	if ( error ) {
-		mp_msg(MSGT_ASS, MSGL_FATAL, MSGTR_LIBASS_FT_Init_FreeTypeFailed);
-		goto ass_init_exit;
-	}
+static void ass_free_images(ASS_Image *img);
 
-	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);
+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);
 
-	priv = calloc(1, sizeof(ass_renderer_t));
-	if (!priv) {
-		FT_Done_FreeType(ft);
-		goto ass_init_exit;
-	}
+    ass_free_images(render_priv->images_root);
+    ass_free_images(render_priv->prev_images_root);
 
-	priv->synth_priv = ass_synth_init(BLUR_MAX_RADIUS);
+    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);
 
-	priv->library = library;
-	priv->ftlibrary = ft;
-	// images_root and related stuff is zero-filled in calloc
+    free(render_priv->settings.default_font);
+    free(render_priv->settings.default_family);
 
-	ass_font_cache_init();
-	ass_bitmap_cache_init();
-	ass_composite_cache_init();
-	ass_glyph_cache_init();
+    free_list_clear(render_priv);
+    free(render_priv);
+}
 
-	text_info.glyphs = calloc(MAX_GLYPHS, sizeof(glyph_info_t));
+/**
+ * \brief Create a new ASS_Image
+ * Parameters are the same as ASS_Image fields.
+ */
+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 *img = calloc(1, sizeof(ASS_Image));
 
-ass_init_exit:
-	if (priv) mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_Init);
-	else mp_msg(MSGT_ASS, MSGL_ERR, MSGTR_LIBASS_InitFailed);
+    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 priv;
+    return img;
 }
 
-void ass_renderer_done(ass_renderer_t* 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);
-}
+static double x2scr_pos(ASS_Renderer *render_priv, double x);
+static double y2scr_pos(ASS_Renderer *render_priv, double y);
 
-/**
- * \brief Create a new ass_image_t
- * Parameters are the same as ass_image_t fields.
+/*
+ * \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_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 **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)
 {
-	ass_image_t* img = calloc(1, sizeof(ass_image_t));
+    int i, j, x0, y0, x1, y1, cx0, cy0, cx1, cy1, sx, sy, zx, zy;
+    Rect r[4];
+    ASS_Image *img;
 
-	assert(dst_x >= 0);
-	assert(dst_y >= 0);
-	assert(dst_x + bitmap_w <= frame_context.width);
-	assert(dst_y + bitmap_h <= frame_context.height);
+    dst_x += bm->left;
+    dst_y += bm->top;
 
-	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;
+    // 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);
 
-	return img;
+    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(unsig
  * \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;
+    // 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);
 
-	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;
+    // 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;
+    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;
+    // 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) {
-		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;
-	}
+    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 ((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;
+    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;
+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)->bitmap == (*tail)->bitmap)
+        return;
 
-	if ((*last_tail)->color != (*tail)->color)
-		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;
+    // 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;
-	}
+    // 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);
 
-	// Allocate new bitmaps and copy over data
-	a = clone_bitmap_data(*last_tail);
-	b = clone_bitmap_data(*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;
+        }
 
-	// 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
+    chv.a = (*last_tail)->bitmap;
+    chv.b = (*tail)->bitmap;
+    cache_add_composite(render_priv->cache.composite_cache, &hk, &chv);
+}
 
-	// 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 free_list_add(ASS_Renderer *render_priv, void *object)
+{
+    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 Convert text_info_t struct to ass_image_t list
- * Splits glyphs in halves when needed (for \kf karaoke).
+ * 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 ass_image_t* render_text(text_info_t* text_info, int dst_x, int dst_y)
+static void blend_vector_clip(ASS_Renderer *render_priv,
+                              ASS_Image *head)
 {
-	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;
+    FT_Glyph glyph;
+    FT_BitmapGlyph clip_bm;
+    ASS_Image *cur;
+    ASS_Drawing *drawing = render_priv->state.clip_drawing;
+    int error;
 
-	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;
+    if (!drawing)
+        return;
 
-		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;
+    // 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;
 
-		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;
-	}
+    assert(clip_bm->bitmap.pitch >= 0);
 
-	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;
+    // Iterate through bitmaps and blend/clip them
+    for (cur = head; cur; cur = cur->next) {
+        int left, top, right, bottom, apos, bpos, y, x, w, h;
+        int ax, ay, aw, ah, as;
+        int bx, by, bw, bh, bs;
+        int aleft, atop, bleft, btop;
+        unsigned char *abuffer, *bbuffer, *nbuffer;
 
-		pen_x = dst_x + info->pos.x;
-		pen_y = dst_y + info->pos.y;
-		bm = info->bm_o;
+        abuffer = cur->bitmap;
+        bbuffer = clip_bm->bitmap.buffer;
+        ax = cur->dst_x;
+        ay = cur->dst_y;
+        aw = cur->w;
+        ah = cur->h;
+        as = cur->stride;
+        bx = clip_bm->left;
+        by = clip_bm->top;
+        bw = clip_bm->bitmap.width;
+        bh = clip_bm->bitmap.rows;
+        bs = clip_bm->bitmap.pitch;
 
-		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;
+        // 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);
+        aleft = left - ax;
+        atop = top - ay;
+        w = right - left;
+        h = bottom - top;
+        bleft = left - bx;
+        btop = top - by;
 
-		pen_x = dst_x + info->pos.x;
-		pen_y = dst_y + info->pos.y;
-		bm = info->bm;
+        if (render_priv->state.clip_drawing_mode) {
+            // Inverse clip
+            if (ax + aw < bx || ay + ah < by || ax > bx + bw ||
+                ay > by + bh) {
+                continue;
+            }
 
-		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);
-	}
+            // Allocate new buffer and add to free list
+            nbuffer = malloc(as * ah);
+            free_list_add(render_priv, nbuffer);
 
-	*tail = 0;
-	return head;
-}
+            // Blend together
+            memcpy(nbuffer, abuffer, as * (ah - 1) + aw);
+            for (y = 0; y < h; y++)
+                for (x = 0; x < w; x++) {
+                    apos = (atop + y) * as + aleft + x;
+                    bpos = (btop + y) * bs + bleft + x;
+                    nbuffer[apos] = FFMAX(0, abuffer[apos] - bbuffer[bpos]);
+                }
+        } else {
+            // Regular clip
+            if (ax + aw < bx || ay + ah < by || ax > bx + bw ||
+                ay > by + bh) {
+                cur->w = cur->h = 0;
+                continue;
+            }
 
-/**
- * \brief Mapping between script and screen coordinates
- */
-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;
+            // Allocate new buffer and add to free list
+            nbuffer = calloc(as, ah);
+            free_list_add(render_priv, nbuffer);
+
+            // Blend together
+            for (y = 0; y < h; y++)
+                for (x = 0; x < w; x++) {
+                    apos = (atop + y) * as + aleft + x;
+                    bpos = (btop + y) * bs + bleft + x;
+                    nbuffer[apos] = (abuffer[apos] * bbuffer[bpos] + 255) >> 8;
+                }
+        }
+        cur->bitmap = nbuffer;
+    }
+
+    // Free clip vector and its bitmap, we don't need it anymore
+    FT_Done_Glyph(glyph);
+blend_vector_exit:
+    ass_drawing_free(render_priv->state.clip_drawing);
+    render_priv->state.clip_drawing = 0;
 }
 
 /**
- * \brief Mapping between script and screen coordinates
+ * \brief Convert TextInfo struct to ASS_Image list
+ * Splits glyphs in halves when needed (for \kf karaoke).
  */
-static double y2scr(double y) {
-	return y * frame_context.orig_height_nocrop / frame_context.track->PlayResY +
-		FFMAX(global_settings->top_margin, 0);
-}
-static double y2scr_pos(double y) {
-	return y * frame_context.orig_height / frame_context.track->PlayResY +
-		global_settings->top_margin;
-}
+static ASS_Image *render_text(ASS_Renderer *render_priv, int dst_x,
+                                int dst_y)
+{
+    int pen_x, pen_y;
+    int i;
+    Bitmap *bm;
+    ASS_Image *head;
+    ASS_Image **tail = &head;
+    ASS_Image **last_tail = 0;
+    ASS_Image **here_tail = 0;
+    TextInfo *text_info = &render_priv->text_info;
 
-// the same for toptitles
-static int y2scr_top(double y) {
-	if (global_settings->use_margins)
-		return y * frame_context.orig_height_nocrop / frame_context.track->PlayResY;
-	else
-		return y * frame_context.orig_height_nocrop / frame_context.track->PlayResY +
-			FFMAX(global_settings->top_margin, 0);
-}
-// the same for subtitles
-static int y2scr_sub(double y) {
-	if (global_settings->use_margins)
-		return y * frame_context.orig_height_nocrop / frame_context.track->PlayResY +
-			FFMAX(global_settings->top_margin, 0) +
-			FFMAX(global_settings->bottom_margin, 0);
-	else
-		return y * frame_context.orig_height_nocrop / frame_context.track->PlayResY +
-			FFMAX(global_settings->top_margin, 0);
-}
+    for (i = 0; i < text_info->length; ++i) {
+        GlyphInfo *info = text_info->glyphs + i;
+        if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm_s
+            || (info->shadow_x == 0 && info->shadow_y == 0) || info->skip)
+            continue;
 
-static void compute_string_bbox( text_info_t* info, FT_BBox *abbox ) {
-	FT_BBox bbox;
-	int i;
+        pen_x =
+            dst_x + (info->pos.x >> 6) +
+            (int) (info->shadow_x * render_priv->border_scale);
+        pen_y =
+            dst_y + (info->pos.y >> 6) +
+            (int) (info->shadow_y * render_priv->border_scale);
+        bm = info->bm_s;
 
-	if (text_info.length > 0) {
-		bbox.xMin = 32000;
-		bbox.xMax = -32000;
-		bbox.yMin = - d6_to_int(text_info.lines[0].asc) + text_info.glyphs[0].pos.y;
-		bbox.yMax = d6_to_int(text_info.height - text_info.lines[0].asc) + text_info.glyphs[0].pos.y;
+        here_tail = tail;
+        tail =
+            render_glyph(render_priv, bm, pen_x, pen_y, info->c[3], 0,
+                         1000000, tail);
+        if (last_tail && tail != here_tail && ((info->c[3] & 0xff) > 0))
+            render_overlap(render_priv, last_tail, here_tail);
 
-		for (i = 0; i < text_info.length; ++i) {
-			int s = text_info.glyphs[i].pos.x;
-			int e = s + d6_to_int(text_info.glyphs[i].advance.x);
-			bbox.xMin = FFMIN(bbox.xMin, s);
-			bbox.xMax = FFMAX(bbox.xMax, e);
-		}
-	} else
-		bbox.xMin = bbox.xMax = bbox.yMin = bbox.yMax = 0;
+        last_tail = here_tail;
+    }
 
-	/* return string bbox */
-	*abbox = bbox;
-}
+    last_tail = 0;
+    for (i = 0; i < text_info->length; ++i) {
+        GlyphInfo *info = text_info->glyphs + i;
+        if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm_o
+            || info->skip)
+            continue;
 
+        pen_x = dst_x + (info->pos.x >> 6);
+        pen_y = dst_y + (info->pos.y >> 6);
+        bm = info->bm_o;
 
-/**
- * \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;
-}
+        if ((info->effect_type == EF_KARAOKE_KO)
+            && (info->effect_timing <= (info->bbox.xMax >> 6))) {
+            // do nothing
+        } else {
+            here_tail = tail;
+            tail =
+                render_glyph(render_priv, bm, pen_x, pen_y, info->c[2],
+                             0, 1000000, tail);
+            if (last_tail && tail != here_tail && ((info->c[2] & 0xff) > 0))
+                render_overlap(render_priv, last_tail, here_tail);
 
-static void change_font_size(double sz)
-{
-	double size = sz * frame_context.font_scale;
+            last_tail = here_tail;
+        }
+    }
 
-	if (size < 1)
-		size = 1;
-	else if (size > frame_context.height * 2)
-		size = frame_context.height * 2;
+    for (i = 0; i < text_info->length; ++i) {
+        GlyphInfo *info = text_info->glyphs + i;
+        if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm
+            || info->skip)
+            continue;
 
-	ass_font_set_size(render_context.font, size);
+        pen_x = dst_x + (info->pos.x >> 6);
+        pen_y = dst_y + (info->pos.y >> 6);
+        bm = info->bm;
 
-	render_context.font_size = sz;
+        if ((info->effect_type == EF_KARAOKE)
+            || (info->effect_type == EF_KARAOKE_KO)) {
+            if (info->effect_timing > (info->bbox.xMax >> 6))
+                tail =
+                    render_glyph(render_priv, bm, pen_x, pen_y,
+                                 info->c[0], 0, 1000000, tail);
+            else
+                tail =
+                    render_glyph(render_priv, bm, pen_x, pen_y,
+                                 info->c[1], 0, 1000000, tail);
+        } else if (info->effect_type == EF_KARAOKE_KF) {
+            tail =
+                render_glyph(render_priv, bm, pen_x, pen_y, info->c[0],
+                             info->c[1], info->effect_timing, tail);
+        } else
+            tail =
+                render_glyph(render_priv, bm, pen_x, pen_y, info->c[0],
+                             0, 1000000, tail);
+    }
+
+    *tail = 0;
+    blend_vector_clip(render_priv, head);
+
+    return head;
 }
 
 /**
- * \brief Change current font, using setting from render_context.
+ * \brief Mapping between script and screen coordinates
  */
-static void update_font(void)
+static double x2scr(ASS_Renderer *render_priv, double x)
 {
-	unsigned val;
-	ass_renderer_t* priv = frame_context.ass_priv;
-	ass_font_desc_t desc;
-	desc.family = strdup(render_context.family);
-	desc.treat_family_as_pattern = render_context.treat_family_as_pattern;
-
-	val = render_context.bold;
-	// 0 = normal, 1 = bold, >1 = exact weight
-	if (val == 0) val = 80; // normal
-	else if (val == 1) val = 200; // bold
-	desc.bold = val;
-
-	val = render_context.italic;
-	if (val == 0) val = 0; // normal
-	else if (val == 1) val = 110; //italic
-	desc.italic = val;
-
-	render_context.font = ass_font_new(priv->library, priv->ftlibrary, priv->fontconfig_priv, &desc);
-	free(desc.family);
-
-	if (render_context.font)
-		change_font_size(render_context.font_size);
+    return x * render_priv->orig_width_nocrop /
+        render_priv->track->PlayResX +
+        FFMAX(render_priv->settings.left_margin, 0);
+}
+static double x2scr_pos(ASS_Renderer *render_priv, double x)
+{
+    return x * render_priv->orig_width / render_priv->track->PlayResX +
+        render_priv->settings.left_margin;
 }
 
 /**
- * \brief Change border width
- * negative value resets border to style value
+ * \brief Mapping between script and screen coordinates
  */
-static void change_border(double border)
+static double y2scr(ASS_Renderer *render_priv, double y)
 {
-	int b;
-	if (!render_context.font) return;
-
-	if (border < 0) {
-		if (render_context.style->BorderStyle == 1)
-			border = render_context.style->Outline;
-		else
-			border = 1.;
-	}
-	render_context.border = border;
-
-	b = 64 * border * frame_context.border_scale;
-	if (b > 0) {
-		if (!render_context.stroker) {
-			int error;
-#if (FREETYPE_MAJOR > 2) || ((FREETYPE_MAJOR == 2) && (FREETYPE_MINOR > 1))
-			error = FT_Stroker_New( ass_renderer->ftlibrary, &render_context.stroker );
-#else // < 2.2
-			error = FT_Stroker_New( render_context.font->faces[0]->memory, &render_context.stroker );
-#endif
-			if (error) {
-				mp_msg(MSGT_ASS, MSGL_V, "failed to get stroker\n");
-				render_context.stroker = 0;
-			}
-		}
-		if (render_context.stroker)
-			FT_Stroker_Set( render_context.stroker, b,
-					FT_STROKER_LINECAP_ROUND,
-					FT_STROKER_LINEJOIN_ROUND,
-					0 );
-	} else {
-		FT_Stroker_Done(render_context.stroker);
-		render_context.stroker = 0;
-	}
+    return y * render_priv->orig_height_nocrop /
+        render_priv->track->PlayResY +
+        FFMAX(render_priv->settings.top_margin, 0);
+}
+static double y2scr_pos(ASS_Renderer *render_priv, double y)
+{
+    return y * render_priv->orig_height / render_priv->track->PlayResY +
+        render_priv->settings.top_margin;
 }
 
-#define _r(c)  ((c)>>24)
-#define _g(c)  (((c)>>16)&0xFF)
-#define _b(c)  (((c)>>8)&0xFF)
-#define _a(c)  ((c)&0xFF)
-
-/**
- * \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)
+// the same for toptitles
+static double y2scr_top(ASS_Renderer *render_priv, double y)
 {
-	(*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);
+    if (render_priv->settings.use_margins)
+        return y * render_priv->orig_height_nocrop /
+            render_priv->track->PlayResY;
+    else
+        return y * render_priv->orig_height_nocrop /
+            render_priv->track->PlayResY +
+            FFMAX(render_priv->settings.top_margin, 0);
 }
 
-// like change_color, but for alpha component only
-static void change_alpha(uint32_t* var, uint32_t new, double pwr)
+// the same for subtitles
+static double y2scr_sub(ASS_Renderer *render_priv, double y)
 {
-	*var = (_r(*var) << 24) + (_g(*var) << 16) + (_b(*var) << 8) + (_a(*var) * (1 - pwr) + _a(new) * pwr);
+    if (render_priv->settings.use_margins)
+        return y * render_priv->orig_height_nocrop /
+            render_priv->track->PlayResY +
+            FFMAX(render_priv->settings.top_margin,
+                  0) + FFMAX(render_priv->settings.bottom_margin, 0);
+    else
+        return y * render_priv->orig_height_nocrop /
+            render_priv->track->PlayResY +
+            FFMAX(render_priv->settings.top_margin, 0);
 }
 
-/**
- * \brief Multiply two alpha values
- * \param a first value
- * \param b second value
- * \return result of multiplication
- * Parameters and result are limited by 0xFF.
- */
-static uint32_t mult_alpha(uint32_t a, uint32_t b)
+static void compute_string_bbox(TextInfo *info, DBBox *bbox)
 {
-	return 0xFF - (0xFF - a) * (0xFF - b) / 0xFF;
+    int i;
+
+    if (info->length > 0) {
+        bbox->xMin = 32000;
+        bbox->xMax = -32000;
+        bbox->yMin = -1 * info->lines[0].asc + d6_to_double(info->glyphs[0].pos.y);
+        bbox->yMax = info->height - info->lines[0].asc +
+                     d6_to_double(info->glyphs[0].pos.y);
+
+        for (i = 0; i < info->length; ++i) {
+            if (info->glyphs[i].skip) continue;
+            double s = d6_to_double(info->glyphs[i].pos.x);
+            double e = s + d6_to_double(info->glyphs[i].advance.x);
+            bbox->xMin = FFMIN(bbox->xMin, s);
+            bbox->xMax = FFMAX(bbox->xMax, e);
+        }
+    } else
+        bbox->xMin = bbox->xMax = bbox->yMin = bbox->yMax = 0.;
 }
 
 /**
- * \brief Calculate alpha value by piecewise linear function
- * Used for \fad, \fade implementation.
+ * \brief partially reset render_context to style values
+ * Works like {\r}: resets some style overrides
  */
-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)
+void reset_render_context(ASS_Renderer *render_priv)
 {
-	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;
-	}
+    render_priv->state.c[0] = render_priv->state.style->PrimaryColour;
+    render_priv->state.c[1] = render_priv->state.style->SecondaryColour;
+    render_priv->state.c[2] = render_priv->state.style->OutlineColour;
+    render_priv->state.c[3] = render_priv->state.style->BackColour;
+    render_priv->state.flags =
+        (render_priv->state.style->Underline ? DECO_UNDERLINE : 0) |
+        (render_priv->state.style->StrikeOut ? DECO_STRIKETHROUGH : 0);
+    render_priv->state.font_size = render_priv->state.style->FontSize;
 
-	return a;
-}
+    free(render_priv->state.family);
+    render_priv->state.family = NULL;
+    render_priv->state.family = strdup(render_priv->state.style->FontName);
+    render_priv->state.treat_family_as_pattern =
+        render_priv->state.style->treat_fontname_as_pattern;
+    render_priv->state.bold = render_priv->state.style->Bold;
+    render_priv->state.italic = render_priv->state.style->Italic;
+    update_font(render_priv);
 
-static void reset_render_context(void);
+    change_border(render_priv, -1., -1.);
+    render_priv->state.scale_x = render_priv->state.style->ScaleX;
+    render_priv->state.scale_y = render_priv->state.style->ScaleY;
+    render_priv->state.hspacing = render_priv->state.style->Spacing;
+    render_priv->state.be = 0;
+    render_priv->state.blur = 0.0;
+    render_priv->state.shadow_x = render_priv->state.style->Shadow;
+    render_priv->state.shadow_y = render_priv->state.style->Shadow;
+    render_priv->state.frx = render_priv->state.fry = 0.;
+    render_priv->state.frz = M_PI * render_priv->state.style->Angle / 180.;
+    render_priv->state.fax = render_priv->state.fay = 0.;
+    render_priv->state.wrap_style = render_priv->track->WrapStyle;
+
+    // FIXME: does not reset unsupported attributes.
+}
 
 /**
- * \brief Parse style override tag.
- * \param p string to parse
- * \param pwr multiplier for some tag effects (comes from \t tags)
+ * \brief Start new event. Reset render_priv->state.
  */
-static char* parse_tag(char* p, double pwr) {
-#define skip_to(x) while ((*p != (x)) && (*p != '}') && (*p != 0)) { ++p;}
-#define skip(x) if (*p == (x)) ++p; else { return p; }
-
-	skip_to('\\');
-	skip('\\');
-	if ((*p == '}') || (*p == 0))
-		return p;
+static void
+init_render_context(ASS_Renderer *render_priv, ASS_Event *event)
+{
+    render_priv->state.event = event;
+    render_priv->state.style = render_priv->track->styles + event->Style;
 
-	// New tags introduced in vsfilter 2.39
-	if (mystrcmp(&p, "xbord")) {
-		double val;
-		if (mystrtod(&p, &val))
-			mp_msg(MSGT_ASS, MSGL_V, "stub: \\xbord%.2f\n", val);
-	} else if (mystrcmp(&p, "ybord")) {
-		double val;
-		if (mystrtod(&p, &val))
-			mp_msg(MSGT_ASS, MSGL_V, "stub: \\ybord%.2f\n", val);
-	} else if (mystrcmp(&p, "xshad")) {
-		int val;
-		if (mystrtoi(&p, &val))
-			mp_msg(MSGT_ASS, MSGL_V, "stub: \\xshad%d\n", val);
-	} else if (mystrcmp(&p, "yshad")) {
-		int val;
-		if (mystrtoi(&p, &val))
-			mp_msg(MSGT_ASS, MSGL_V, "stub: \\yshad%d\n", val);
-	} else if (mystrcmp(&p, "fax")) {
-		int val;
-		if (mystrtoi(&p, &val))
-			mp_msg(MSGT_ASS, MSGL_V, "stub: \\fax%d\n", val);
-	} else if (mystrcmp(&p, "fay")) {
-		int val;
-		if (mystrtoi(&p, &val))
-			mp_msg(MSGT_ASS, MSGL_V, "stub: \\fay%d\n", val);
-	} else if (mystrcmp(&p, "iclip")) {
-		int x0, y0, x1, y1;
-		int res = 1;
-		skip('(');
-		res &= mystrtoi(&p, &x0);
-		skip(',');
-		res &= mystrtoi(&p, &y0);
-		skip(',');
-		res &= mystrtoi(&p, &x1);
-		skip(',');
-		res &= mystrtoi(&p, &y1);
-		skip(')');
-		mp_msg(MSGT_ASS, MSGL_V, "stub: \\iclip(%d,%d,%d,%d)\n", x0, y0, x1, y1);
-	} else if (mystrcmp(&p, "blur")) {
-		double val;
-		if (mystrtod(&p, &val)) {
-			val = (val < 0) ? 0 : val;
-			val = (val > BLUR_MAX_RADIUS) ? BLUR_MAX_RADIUS : val;
-			render_context.blur = val;
-		} else
-			render_context.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_context.scale_x = render_context.scale_x * ( 1 - pwr) + val * pwr;
-			} else
-				render_context.scale_x = render_context.style->ScaleX;
-		} else if (tp == 'y') {
-			if (mystrtod(&p, &val)) {
-				val /= 100;
-				render_context.scale_y = render_context.scale_y * ( 1 - pwr) + val * pwr;
-			} else
-				render_context.scale_y = render_context.style->ScaleY;
-		}
-	} else if (mystrcmp(&p, "fsp")) {
-		double val;
-		if (mystrtod(&p, &val))
-			render_context.hspacing = render_context.hspacing * ( 1 - pwr ) + val * pwr;
-		else
-			render_context.hspacing = render_context.style->Spacing;
-	} else if (mystrcmp(&p, "fs")) {
-		double val;
-		if (mystrtod(&p, &val))
-			val = render_context.font_size * ( 1 - pwr ) + val * pwr;
-		else
-			val = render_context.style->FontSize;
-		if (render_context.font)
-			change_font_size(val);
-	} else if (mystrcmp(&p, "bord")) {
-		double val;
-		if (mystrtod(&p, &val))
-			val = render_context.border * ( 1 - pwr ) + val * pwr;
-		else
-			val = -1.; // reset to default
-		change_border(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);
-			mp_msg(MSGT_ASS, 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_context.event->Duration;
-			mp_msg(MSGT_ASS, MSGL_DBG2, "movement: (%f, %f) -> (%f, %f)\n", x1, y1, x2, y2);
-		}
-		skip(')');
-		delta_t = t2 - t1;
-		t = frame_context.time - render_context.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_context.evt_type != EVENT_POSITIONED) {
-			render_context.pos_x = x;
-			render_context.pos_y = y;
-			render_context.detect_collisions = 0;
-			render_context.evt_type = EVENT_POSITIONED;
-		}
-	} else if (mystrcmp(&p, "frx")) {
-		double val;
-		if (mystrtod(&p, &val)) {
-			val *= M_PI / 180;
-			render_context.frx = val * pwr + render_context.frx * (1-pwr);
-		} else
-			render_context.frx = 0.;
-	} else if (mystrcmp(&p, "fry")) {
-		double val;
-		if (mystrtod(&p, &val)) {
-			val *= M_PI / 180;
-			render_context.fry = val * pwr + render_context.fry * (1-pwr);
-		} else
-			render_context.fry = 0.;
-	} else if (mystrcmp(&p, "frz") || mystrcmp(&p, "fr")) {
-		double val;
-		if (mystrtod(&p, &val)) {
-			val *= M_PI / 180;
-			render_context.frz = val * pwr + render_context.frz * (1-pwr);
-		} else
-			render_context.frz = M_PI * render_context.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_context.style->FontName);
-		if (render_context.family)
-			free(render_context.family);
-		render_context.family = family;
-		update_font();
-	} else if (mystrcmp(&p, "alpha")) {
-		uint32_t val;
-		int i;
-		if (strtocolor(&p, &val)) {
-			unsigned char a = val >> 24;
-			for (i = 0; i < 4; ++i)
-				change_alpha(&render_context.c[i], a, pwr);
-		} else {
-			change_alpha(&render_context.c[0], render_context.style->PrimaryColour, pwr);
-			change_alpha(&render_context.c[1], render_context.style->SecondaryColour, pwr);
-			change_alpha(&render_context.c[2], render_context.style->OutlineColour, pwr);
-			change_alpha(&render_context.c[3], render_context.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
-			mp_msg(MSGT_ASS, MSGL_DBG2, "an %d\n", val);
-			if (v != 0) v = 3 - v;
-			val = ((val - 1) % 3) + 1; // horizontal alignment
-			val += v*4;
-			mp_msg(MSGT_ASS, MSGL_DBG2, "align %d\n", val);
-			render_context.alignment = val;
-		} else
-			render_context.alignment = render_context.style->Alignment;
-	} else if (mystrcmp(&p, "a")) {
-		int val;
-		if (mystrtoi(&p, &val) && val)
-			render_context.alignment = val;
-		else
-			render_context.alignment = render_context.style->Alignment;
-	} else if (mystrcmp(&p, "pos")) {
-		double v1, v2;
-		skip('(');
-		mystrtod(&p, &v1);
-		skip(',');
-		mystrtod(&p, &v2);
-		skip(')');
-		mp_msg(MSGT_ASS, MSGL_DBG2, "pos(%f, %f)\n", v1, v2);
-		if (render_context.evt_type != EVENT_POSITIONED) {
-			render_context.evt_type = EVENT_POSITIONED;
-			render_context.detect_collisions = 0;
-			render_context.pos_x = v1;
-			render_context.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_context.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_context.fade = interpolate_alpha(frame_context.time - render_context.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(')');
-		mp_msg(MSGT_ASS, MSGL_DBG2, "org(%d, %d)\n", v1, v2);
-		//				render_context.evt_type = EVENT_POSITIONED;
-		if (!render_context.have_origin) {
-			render_context.org_x = v1;
-			render_context.org_y = v2;
-			render_context.have_origin = 1;
-			render_context.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]; v3 = v[2];
-		} else if (cnt == 2) {
-			v1 = v[0]; v2 = v[1]; v3 = 1.;
-		} else if (cnt == 1) {
-			v1 = 0; v2 = render_context.event->Duration; v3 = v[0];
-		} else { // cnt == 0
-			v1 = 0; v2 = render_context.event->Duration; v3 = 1.;
-		}
-		render_context.detect_collisions = 0;
-		t1 = v1;
-		t2 = v2;
-		delta_t = v2 - v1;
-		if (v3 < 0.)
-			v3 = 0.;
-		t = frame_context.time - render_context.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(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")) {
-		int x0, y0, x1, y1;
-		int res = 1;
-		skip('(');
-		res &= mystrtoi(&p, &x0);
-		skip(',');
-		res &= mystrtoi(&p, &y0);
-		skip(',');
-		res &= mystrtoi(&p, &x1);
-		skip(',');
-		res &= mystrtoi(&p, &y1);
-		skip(')');
-		if (res) {
-			render_context.clip_x0 = render_context.clip_x0 * (1-pwr) + x0 * pwr;
-			render_context.clip_x1 = render_context.clip_x1 * (1-pwr) + x1 * pwr;
-			render_context.clip_y0 = render_context.clip_y0 * (1-pwr) + y0 * pwr;
-			render_context.clip_y1 = render_context.clip_y1 * (1-pwr) + y1 * pwr;
-		} else {
-			render_context.clip_x0 = 0;
-			render_context.clip_y0 = 0;
-			render_context.clip_x1 = frame_context.track->PlayResX;
-			render_context.clip_y1 = frame_context.track->PlayResY;
-		}
-	} else if (mystrcmp(&p, "c")) {
-		uint32_t val;
-		if (!strtocolor(&p, &val))
-			val = render_context.style->PrimaryColour;
-		mp_msg(MSGT_ASS, MSGL_DBG2, "color: %X\n", val);
-		change_color(&render_context.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;
-		assert((n >= '1') && (n <= '4'));
-		if (!strtocolor(&p, &val))
-			switch(n) {
-				case '1': val = render_context.style->PrimaryColour; break;
-				case '2': val = render_context.style->SecondaryColour; break;
-				case '3': val = render_context.style->OutlineColour; break;
-				case '4': val = render_context.style->BackColour; break;
-				default : val = 0; break; // impossible due to assert; avoid compilation warning
-			}
-		switch (cmd) {
-			case 'c': change_color(render_context.c + cidx, val, pwr); break;
-			case 'a': change_alpha(render_context.c + cidx, val >> 24, pwr); break;
-			default: mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_BadCommand, n, cmd); break;
-		}
-		mp_msg(MSGT_ASS, MSGL_DBG2, "single c/a at %f: %c%c = %X   \n", pwr, n, cmd, render_context.c[cidx]);
-	} else if (mystrcmp(&p, "r")) {
-		reset_render_context();
-	} 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_context.be = val;
-		} else
-			render_context.be = 0;
-	} else if (mystrcmp(&p, "b")) {
-		int b;
-		if (mystrtoi(&p, &b)) {
-			if (pwr >= .5)
-				render_context.bold = b;
-		} else
-			render_context.bold = render_context.style->Bold;
-		update_font();
-	} else if (mystrcmp(&p, "i")) {
-		int i;
-		if (mystrtoi(&p, &i)) {
-			if (pwr >= .5)
-				render_context.italic = i;
-		} else
-			render_context.italic = render_context.style->Italic;
-		update_font();
-	} else if (mystrcmp(&p, "kf") || mystrcmp(&p, "K")) {
-		int val = 0;
-		mystrtoi(&p, &val);
-		render_context.effect_type = EF_KARAOKE_KF;
-		if (render_context.effect_timing)
-			render_context.effect_skip_timing += render_context.effect_timing;
-		render_context.effect_timing = val * 10;
-	} else if (mystrcmp(&p, "ko")) {
-		int val = 0;
-		mystrtoi(&p, &val);
-		render_context.effect_type = EF_KARAOKE_KO;
-		if (render_context.effect_timing)
-			render_context.effect_skip_timing += render_context.effect_timing;
-		render_context.effect_timing = val * 10;
-	} else if (mystrcmp(&p, "k")) {
-		int val = 0;
-		mystrtoi(&p, &val);
-		render_context.effect_type = EF_KARAOKE;
-		if (render_context.effect_timing)
-			render_context.effect_skip_timing += render_context.effect_timing;
-		render_context.effect_timing = val * 10;
-	} else if (mystrcmp(&p, "shad")) {
-		int val;
-		if (mystrtoi(&p, &val))
-			render_context.shadow = val;
-		else
-			render_context.shadow = render_context.style->Shadow;
-	} else if (mystrcmp(&p, "pbo")) {
-		int val = 0;
-		mystrtoi(&p, &val); // ignored
-	} else if (mystrcmp(&p, "p")) {
-		int val;
-		if (!mystrtoi(&p, &val))
-			val = 0;
-		render_context.drawing_mode = !!val;
-	}
+    reset_render_context(render_priv);
 
-	return p;
+    render_priv->state.evt_type = EVENT_NORMAL;
+    render_priv->state.alignment = render_priv->state.style->Alignment;
+    render_priv->state.pos_x = 0;
+    render_priv->state.pos_y = 0;
+    render_priv->state.org_x = 0;
+    render_priv->state.org_y = 0;
+    render_priv->state.have_origin = 0;
+    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;
+    render_priv->state.clip_mode = 0;
+    render_priv->state.detect_collisions = 1;
+    render_priv->state.fade = 0;
+    render_priv->state.drawing_mode = 0;
+    render_priv->state.effect_type = EF_NONE;
+    render_priv->state.effect_timing = 0;
+    render_priv->state.effect_skip_timing = 0;
+    render_priv->state.drawing =
+        ass_drawing_new(render_priv->fontconfig_priv,
+                        render_priv->state.font,
+                        render_priv->settings.hinting,
+                        render_priv->ftlibrary);
 
-#undef skip
-#undef skip_to
+    apply_transition_effects(render_priv, event);
 }
 
-/**
- * \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
- */
-static unsigned get_next_char(char** str)
+static void free_render_context(ASS_Renderer *render_priv)
 {
-	char* p = *str;
-	unsigned chr;
-	if (*p == '{') { // '\0' goes here
-		p++;
-		while (1) {
-			p = parse_tag(p, 1.);
-			if (*p == '}') { // end of tag
-				p++;
-				if (*p == '{') {
-					p++;
-					continue;
-				} else
-					break;
-			} else if (*p != '\\')
-				mp_msg(MSGT_ASS, MSGL_V, "Unable to parse: \"%s\" \n", p);
-			if (*p == 0)
-				break;
-		}
-	}
-	if (*p == '\t') {
-		++p;
-		*str = p;
-		return ' ';
-	}
-	if (*p == '\\') {
-		if ((*(p+1) == 'N') || ((*(p+1) == 'n') && (frame_context.track->WrapStyle == 2))) {
-			p += 2;
-			*str = p;
-			return '\n';
-		} else if ((*(p+1) == 'n') || (*(p+1) == 'h')) {
-			p += 2;
-			*str = p;
-			return ' ';
-		}
-	}
-	chr = utf8_get_char((const char **)&p);
-	*str = p;
-	return chr;
+    free(render_priv->state.family);
+    ass_drawing_free(render_priv->state.drawing);
+
+    render_priv->state.family = NULL;
+    render_priv->state.drawing = NULL;
 }
 
-static void apply_transition_effects(ass_event_t* event)
+// Calculate the cbox of a series of points
+static void
+get_contour_cbox(FT_BBox *box, FT_Vector *points, int start, int end)
 {
-	int v[4];
-	int cnt;
-	char* p = event->Effect;
+    box->xMin = box->yMin = INT_MAX;
+    box->xMax = box->yMax = INT_MIN;
+    int i;
 
-	if (!p || !*p) return;
+    for (i = start; i < end; i++) {
+        box->xMin = (points[i].x < box->xMin) ? points[i].x : box->xMin;
+        box->xMax = (points[i].x > box->xMax) ? points[i].x : box->xMax;
+        box->yMin = (points[i].y < box->yMin) ? points[i].y : box->yMin;
+        box->yMax = (points[i].y > box->yMax) ? points[i].y : box->yMax;
+    }
+}
 
-	cnt = 0;
-	while (cnt < 4 && (p = strchr(p, ';'))) {
-		v[cnt++] = atoi(++p);
-	}
+/**
+ * \brief Fix-up stroker result for huge borders by removing the contours from
+ * the outline that are harmful.
+*/
+static void fix_freetype_stroker(FT_OutlineGlyph glyph, int border_x,
+                                 int border_y)
+{
+    int nc = glyph->outline.n_contours;
+    int begin, stop;
+    char modified = 0;
+    char *valid_cont;
+    int start = 0;
+    int end = -1;
+    FT_BBox *boxes = calloc(nc, sizeof(FT_BBox));
+    int i, j;
 
-	if (strncmp(event->Effect, "Banner;", 7) == 0) {
-		int delay;
-		if (cnt < 1) {
-			mp_msg(MSGT_ASS, MSGL_V, "Error parsing effect: %s \n", event->Effect);
-			return;
-		}
-		if (cnt >= 2 && v[1] == 0) // right-to-left
-			render_context.scroll_direction = SCROLL_RL;
-		else // left-to-right
-			render_context.scroll_direction = SCROLL_LR;
+    // Create a list of cboxes of the contours
+    for (i = 0; i < nc; i++) {
+        start = end + 1;
+        end = glyph->outline.contours[i];
+        get_contour_cbox(&boxes[i], glyph->outline.points, start, end);
+    }
 
-		delay = v[0];
-		if (delay == 0) delay = 1; // ?
-		render_context.scroll_shift = (frame_context.time - render_context.event->Start) / delay;
-		render_context.evt_type = EVENT_HSCROLL;
-		return;
-	}
+    // if a) contour's cbox is contained in another contours cbox
+    //    b) contour's height or width is smaller than the border*2
+    // the contour can be safely removed.
+    valid_cont = calloc(1, nc);
+    for (i = 0; i < nc; i++) {
+        valid_cont[i] = 1;
+        for (j = 0; j < nc; j++) {
+            if (i == j)
+                continue;
+            if (boxes[i].xMin >= boxes[j].xMin &&
+                boxes[i].xMax <= boxes[j].xMax &&
+                boxes[i].yMin >= boxes[j].yMin &&
+                boxes[i].yMax <= boxes[j].yMax) {
+                int width = boxes[i].xMax - boxes[i].xMin;
+                int height = boxes[i].yMax - boxes[i].yMin;
+                if (width < border_x * 2 || height < border_y * 2) {
+                    valid_cont[i] = 0;
+                    modified = 1;
+                    break;
+                }
+            }
+        }
+    }
 
-	if (strncmp(event->Effect, "Scroll up;", 10) == 0) {
-		render_context.scroll_direction = SCROLL_BT;
-	} else if (strncmp(event->Effect, "Scroll down;", 12) == 0) {
-		render_context.scroll_direction = SCROLL_TB;
-	} else {
-		mp_msg(MSGT_ASS, MSGL_V, "Unknown transition effect: %s \n", event->Effect);
-		return;
-	}
-	// parse scroll up/down parameters
-	{
-		int delay;
-		int y0, y1;
-		if (cnt < 3) {
-			mp_msg(MSGT_ASS, MSGL_V, "Error parsing effect: %s \n", event->Effect);
-			return;
-		}
-		delay = v[2];
-		if (delay == 0) delay = 1; // ?
-		render_context.scroll_shift = (frame_context.time - render_context.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 = frame_context.track->PlayResY; // y0=y1=0 means fullscreen scrolling
-		render_context.clip_y0 = y0;
-		render_context.clip_y1 = y1;
-		render_context.evt_type = EVENT_VSCROLL;
-		render_context.detect_collisions = 0;
-	}
+    // Zero-out contours that can be removed; much simpler than copying
+    if (modified) {
+        for (i = 0; i < nc; i++) {
+            if (valid_cont[i])
+                continue;
+            begin = (i == 0) ? 0 : glyph->outline.contours[i - 1] + 1;
+            stop = glyph->outline.contours[i];
+            for (j = begin; j <= stop; j++) {
+                glyph->outline.points[j].x = 0;
+                glyph->outline.points[j].y = 0;
+                glyph->outline.tags[j] = 0;
+            }
+        }
+    }
 
+    free(boxes);
+    free(valid_cont);
 }
 
-/**
- * \brief partially reset render_context to style values
- * Works like {\r}: resets some style overrides
+/*
+ * Replace the outline of a glyph by a contour which makes up a simple
+ * opaque rectangle.
  */
-static void reset_render_context(void)
+static void draw_opaque_box(ASS_Renderer *render_priv, uint32_t ch,
+                            FT_Glyph glyph, int sx, int sy)
 {
-	render_context.c[0] = render_context.style->PrimaryColour;
-	render_context.c[1] = render_context.style->SecondaryColour;
-	render_context.c[2] = render_context.style->OutlineColour;
-	render_context.c[3] = render_context.style->BackColour;
-	render_context.font_size = render_context.style->FontSize;
+    int asc = 0, desc = 0;
+    int i;
+    int adv = d16_to_d6(glyph->advance.x);
+    double scale_y = render_priv->state.scale_y;
+    double scale_x = render_priv->state.scale_x
+                     * render_priv->font_scale_x;
+    FT_OutlineGlyph og = (FT_OutlineGlyph) glyph;
+    FT_Outline *ol;
 
-	if (render_context.family)
-		free(render_context.family);
-	render_context.family = strdup(render_context.style->FontName);
-	render_context.treat_family_as_pattern = render_context.style->treat_fontname_as_pattern;
-	render_context.bold = render_context.style->Bold;
-	render_context.italic = render_context.style->Italic;
-	update_font();
+    // to avoid gaps
+    sx = FFMAX(64, sx);
+    sy = FFMAX(64, sy);
 
-	change_border(-1.);
-	render_context.scale_x = render_context.style->ScaleX;
-	render_context.scale_y = render_context.style->ScaleY;
-	render_context.hspacing = render_context.style->Spacing;
-	render_context.be = 0;
-	render_context.blur = 0.0;
-	render_context.shadow = render_context.style->Shadow;
-	render_context.frx = render_context.fry = 0.;
-	render_context.frz = M_PI * render_context.style->Angle / 180.;
+    if (ch == -1) {
+        asc = render_priv->state.drawing->asc;
+        desc = render_priv->state.drawing->desc;
+    } else {
+        ass_font_get_asc_desc(render_priv->state.font, ch, &asc, &desc);
+        asc  *= scale_y;
+        desc *= scale_y;
+    }
 
-	// FIXME: does not reset unsupported attributes.
+    // Emulate the WTFish behavior of VSFilter, i.e. double-scale
+    // the sizes of the opaque box.
+    adv += double_to_d6(render_priv->state.hspacing * render_priv->font_scale
+                        * scale_x);
+    adv *= scale_x;
+    sx *= scale_x;
+    sy *= scale_y;
+    desc *= scale_y;
+    desc += asc * (scale_y - 1.0);
+
+    FT_Vector points[4] = {
+        { .x = -sx,         .y = asc + sy },
+        { .x = adv + sx,    .y = asc + sy },
+        { .x = adv + sx,    .y = -desc - sy },
+        { .x = -sx,         .y = -desc - sy },
+    };
+
+    FT_Outline_Done(render_priv->ftlibrary, &og->outline);
+    FT_Outline_New(render_priv->ftlibrary, 4, 1, &og->outline);
+
+    ol = &og->outline;
+    ol->n_points = ol->n_contours = 0;
+    for (i = 0; i < 4; i++) {
+        ol->points[ol->n_points] = points[i];
+        ol->tags[ol->n_points++] = 1;
+    }
+    ol->contours[ol->n_contours++] = ol->n_points - 1;
 }
 
-/**
- * \brief Start new event. Reset render_context.
+/*
+ * Stroke an outline glyph in x/y direction.  Applies various fixups to get
+ * around limitations of the FreeType stroker.
  */
-static void init_render_context(ass_event_t* event)
+static void stroke_outline_glyph(ASS_Renderer *render_priv,
+                                 FT_OutlineGlyph *glyph, int sx, int sy)
 {
-	render_context.event = event;
-	render_context.style = frame_context.track->styles + event->Style;
+    if (sx <= 0 && sy <= 0)
+        return;
 
-	reset_render_context();
+    fix_freetype_stroker(*glyph, sx, sy);
 
-	render_context.evt_type = EVENT_NORMAL;
-	render_context.alignment = render_context.style->Alignment;
-	render_context.pos_x = 0;
-	render_context.pos_y = 0;
-	render_context.org_x = 0;
-	render_context.org_y = 0;
-	render_context.have_origin = 0;
-	render_context.clip_x0 = 0;
-	render_context.clip_y0 = 0;
-	render_context.clip_x1 = frame_context.track->PlayResX;
-	render_context.clip_y1 = frame_context.track->PlayResY;
-	render_context.detect_collisions = 1;
-	render_context.fade = 0;
-	render_context.drawing_mode = 0;
-	render_context.effect_type = EF_NONE;
-	render_context.effect_timing = 0;
-	render_context.effect_skip_timing = 0;
+    // Borders are equal; use the regular stroker
+    if (sx == sy && render_priv->state.stroker) {
+        int error;
+        error = FT_Glyph_StrokeBorder((FT_Glyph *) glyph,
+                                      render_priv->state.stroker, 0, 1);
+        if (error)
+            ass_msg(render_priv->library, MSGL_WARN,
+                    "FT_Glyph_Stroke error: %d", error);
 
-	apply_transition_effects(event);
-}
+    // "Stroke" with the outline emboldener in two passes.
+    // The outlines look uglier, but the emboldening never adds any points
+    } else {
+        int i;
+        FT_Outline *ol = &(*glyph)->outline;
+        FT_Outline nol;
+        FT_Outline_New(render_priv->ftlibrary, ol->n_points,
+                       ol->n_contours, &nol);
+        FT_Outline_Copy(ol, &nol);
 
-static void free_render_context(void)
-{
+        FT_Outline_Embolden(ol, sx * 2);
+        FT_Outline_Translate(ol, -sx, -sx);
+        FT_Outline_Embolden(&nol, sy * 2);
+        FT_Outline_Translate(&nol, -sy, -sy);
+
+        for (i = 0; i < ol->n_points; i++)
+            ol->points[i].y = nol.points[i].y;
+
+        FT_Outline_Done(render_priv->ftlibrary, &nol);
+    }
 }
 
 /**
  * \brief Get normal and outline (border) glyphs
  * \param symbol ucs4 char
  * \param info out: struct filled with extracted data
- * \param advance subpixel shift vector used for cache lookup
  * Tries to get both glyphs from cache.
  * If they can't be found, gets a glyph from font face, generates outline with FT_Stroker,
  * and add them to cache.
  * The glyphs are returned in info->glyph and info->outline_glyph
  */
-static void get_outline_glyph(int symbol, glyph_info_t* info, FT_Vector* advance)
+static void
+get_outline_glyph(ASS_Renderer *render_priv, int symbol, GlyphInfo *info,
+                  ASS_Drawing *drawing)
 {
-	int error;
-	glyph_hash_val_t* val;
-	glyph_hash_key_t key;
-	memset(&key, 0, sizeof(key));
-	key.font = render_context.font;
-	key.size = render_context.font_size;
-	key.ch = symbol;
-	key.scale_x = (render_context.scale_x * 0xFFFF);
-	key.scale_y = (render_context.scale_y * 0xFFFF);
-	key.advance = *advance;
-	key.bold = render_context.bold;
-	key.italic = render_context.italic;
-	key.outline = render_context.border * 0xFFFF;
+    GlyphHashValue *val;
+    GlyphHashKey key;
+    memset(&key, 0, sizeof(key));
 
-	memset(info, 0, sizeof(glyph_info_t));
+    if (drawing->hash) {
+        key.scale_x = double_to_d16(render_priv->state.scale_x);
+        key.scale_y = double_to_d16(render_priv->state.scale_y);
+        key.outline.x = render_priv->state.border_x * 0xFFFF;
+        key.outline.y = render_priv->state.border_y * 0xFFFF;
+        key.border_style = render_priv->state.style->BorderStyle;
+        key.drawing_hash = drawing->hash;
+    } else {
+        key.font = render_priv->state.font;
+        key.size = render_priv->state.font_size;
+        key.ch = symbol;
+        key.bold = render_priv->state.bold;
+        key.italic = render_priv->state.italic;
+        key.scale_x = double_to_d16(render_priv->state.scale_x);
+        key.scale_y = double_to_d16(render_priv->state.scale_y);
+        key.outline.x = render_priv->state.border_x * 0xFFFF;
+        key.outline.y = render_priv->state.border_y * 0xFFFF;
+        key.flags = render_priv->state.flags;
+        key.border_style = render_priv->state.style->BorderStyle;
+    }
+    memset(info, 0, sizeof(GlyphInfo));
 
-	val = cache_find_glyph(&key);
-	if (val) {
-		FT_Glyph_Copy(val->glyph, &info->glyph);
-		if (val->outline_glyph)
-			FT_Glyph_Copy(val->outline_glyph, &info->outline_glyph);
-		info->bbox = val->bbox_scaled;
-		info->advance.x = val->advance.x;
-		info->advance.y = val->advance.y;
-	} else {
-		glyph_hash_val_t v;
-		info->glyph = ass_font_get_glyph(frame_context.ass_priv->fontconfig_priv, render_context.font, symbol, global_settings->hinting);
-		if (!info->glyph)
-			return;
-		info->advance.x = d16_to_d6(info->glyph->advance.x);
-		info->advance.y = d16_to_d6(info->glyph->advance.y);
-		FT_Glyph_Get_CBox( info->glyph, FT_GLYPH_BBOX_PIXELS, &info->bbox);
+    val = cache_find_glyph(render_priv->cache.glyph_cache, &key);
+    if (val) {
+        FT_Glyph_Copy(val->glyph, &info->glyph);
+        if (val->outline_glyph)
+            FT_Glyph_Copy(val->outline_glyph, &info->outline_glyph);
+        info->bbox = val->bbox_scaled;
+        info->advance.x = val->advance.x;
+        info->advance.y = val->advance.y;
+        if (drawing->hash) {
+            drawing->asc = val->asc;
+            drawing->desc = val->desc;
+        }
+    } else {
+        GlyphHashValue v;
+        if (drawing->hash) {
+            if(!ass_drawing_parse(drawing, 0))
+                return;
+            FT_Glyph_Copy((FT_Glyph) drawing->glyph, &info->glyph);
+        } else {
+            info->glyph =
+                ass_font_get_glyph(render_priv->fontconfig_priv,
+                                   render_priv->state.font, symbol,
+                                   render_priv->settings.hinting,
+                                   render_priv->state.flags);
+        }
+        if (!info->glyph)
+            return;
+        info->advance.x = d16_to_d6(info->glyph->advance.x);
+        info->advance.y = d16_to_d6(info->glyph->advance.y);
+        FT_Glyph_Get_CBox(info->glyph, FT_GLYPH_BBOX_SUBPIXELS, &info->bbox);
 
-		if (render_context.stroker) {
-			info->outline_glyph = info->glyph;
-			error = FT_Glyph_StrokeBorder( &(info->outline_glyph), render_context.stroker, 0 , 0 ); // don't destroy original
-			if (error) {
-				mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FT_Glyph_Stroke_Error, error);
-			}
-		}
+        if (render_priv->state.style->BorderStyle == 3 &&
+            (render_priv->state.border_x > 0||
+             render_priv->state.border_y > 0)) {
+            FT_Glyph_Copy(info->glyph, &info->outline_glyph);
+            draw_opaque_box(render_priv, symbol, info->outline_glyph,
+                            double_to_d6(render_priv->state.border_x *
+                                         render_priv->border_scale),
+                            double_to_d6(render_priv->state.border_y *
+                                         render_priv->border_scale));
+        } else if (render_priv->state.border_x > 0 ||
+                   render_priv->state.border_y > 0) {
 
-		memset(&v, 0, sizeof(v));
-		FT_Glyph_Copy(info->glyph, &v.glyph);
-		if (info->outline_glyph)
-			FT_Glyph_Copy(info->outline_glyph, &v.outline_glyph);
-		v.advance = info->advance;
-		v.bbox_scaled = info->bbox;
-		cache_add_glyph(&key, &v);
-	}
+            FT_Glyph_Copy(info->glyph, &info->outline_glyph);
+            stroke_outline_glyph(render_priv,
+                                 (FT_OutlineGlyph *) &info->outline_glyph,
+                                 double_to_d6(render_priv->state.border_x *
+                                              render_priv->border_scale),
+                                 double_to_d6(render_priv->state.border_y *
+                                              render_priv->border_scale));
+        }
+
+        memset(&v, 0, sizeof(v));
+        FT_Glyph_Copy(info->glyph, &v.glyph);
+        if (info->outline_glyph)
+            FT_Glyph_Copy(info->outline_glyph, &v.outline_glyph);
+        v.advance = info->advance;
+        v.bbox_scaled = info->bbox;
+        if (drawing->hash) {
+            v.asc = drawing->asc;
+            v.desc = drawing->desc;
+        }
+        cache_add_glyph(render_priv->cache.glyph_cache, &key, &v);
+    }
 }
 
-static void transform_3d(FT_Vector shift, FT_Glyph* glyph, FT_Glyph* glyph2, double frx, double fry, double frz);
+static void transform_3d(FT_Vector shift, FT_Glyph *glyph,
+                         FT_Glyph *glyph2, double frx, double fry,
+                         double frz, double fax, double fay, double scale,
+                         int yshift);
 
 /**
  * \brief Get bitmaps for a glyph
@@ -1504,50 +1197,74 @@ static void transform_3d(FT_Vector shift
  * After that, bitmaps are added to the cache.
  * They are returned in info->bm (glyph), info->bm_o (outline) and info->bm_s (shadow).
  */
-static void get_bitmap_glyph(glyph_info_t* info)
+static void
+get_bitmap_glyph(ASS_Renderer *render_priv, GlyphInfo *info)
 {
-	bitmap_hash_val_t* val;
-	bitmap_hash_key_t* key = &info->hash_key;
+    BitmapHashValue *val;
+    BitmapHashKey *key = &info->hash_key;
 
-	val = cache_find_bitmap(key);
-/* 	val = 0; */
+    val = cache_find_bitmap(render_priv->cache.bitmap_cache, key);
 
-	if (val) {
-		info->bm = val->bm;
-		info->bm_o = val->bm_o;
-		info->bm_s = val->bm_s;
-	} else {
-		FT_Vector shift;
-		bitmap_hash_val_t hash_val;
-		int error;
-		info->bm = info->bm_o = info->bm_s = 0;
-		if (info->glyph && info->symbol != '\n' && info->symbol != 0) {
-			// calculating rotation shift vector (from rotation origin to the glyph basepoint)
-			shift.x = int_to_d6(info->hash_key.shift_x);
-			shift.y = int_to_d6(info->hash_key.shift_y);
-			// apply rotation
-			transform_3d(shift, &info->glyph, &info->outline_glyph, info->frx, info->fry, info->frz);
+    if (val) {
+        info->bm = val->bm;
+        info->bm_o = val->bm_o;
+        info->bm_s = val->bm_s;
+    } else {
+        FT_Vector shift;
+        BitmapHashValue hash_val;
+        int error;
+        double fax_scaled, fay_scaled;
+        info->bm = info->bm_o = info->bm_s = 0;
+        if (info->glyph && info->symbol != '\n' && info->symbol != 0
+            && !info->skip) {
+            // calculating rotation shift vector (from rotation origin to the glyph basepoint)
+            shift.x = info->hash_key.shift_x;
+            shift.y = info->hash_key.shift_y;
+            fax_scaled = info->fax * render_priv->font_scale_x *
+                         render_priv->state.scale_x;
+            fay_scaled = info->fay * render_priv->state.scale_y;
+            // apply rotation
+            transform_3d(shift, &info->glyph, &info->outline_glyph,
+                         info->frx, info->fry, info->frz, fax_scaled,
+                         fay_scaled, render_priv->font_scale, info->asc);
 
-			// render glyph
-			error = glyph_to_bitmap(ass_renderer->synth_priv,
-					info->glyph, info->outline_glyph,
-					&info->bm, &info->bm_o,
-					&info->bm_s, info->be, info->blur * frame_context.border_scale);
-			if (error)
-				info->symbol = 0;
+            // subpixel shift
+            if (info->glyph)
+                FT_Outline_Translate(
+                    &((FT_OutlineGlyph) info->glyph)->outline,
+                    info->hash_key.advance.x,
+                    -info->hash_key.advance.y);
+            if (info->outline_glyph)
+                FT_Outline_Translate(
+                    &((FT_OutlineGlyph) info->outline_glyph)->outline,
+                    info->hash_key.advance.x,
+                    -info->hash_key.advance.y);
 
-			// add bitmaps to cache
-			hash_val.bm_o = info->bm_o;
-			hash_val.bm = info->bm;
-			hash_val.bm_s = info->bm_s;
-			cache_add_bitmap(&(info->hash_key), &hash_val);
-		}
-	}
-	// deallocate glyphs
-	if (info->glyph)
-		FT_Done_Glyph(info->glyph);
-	if (info->outline_glyph)
-		FT_Done_Glyph(info->outline_glyph);
+            // render glyph
+            error = glyph_to_bitmap(render_priv->library,
+                                    render_priv->synth_priv,
+                                    info->glyph, info->outline_glyph,
+                                    &info->bm, &info->bm_o,
+                                    &info->bm_s, info->be,
+                                    info->blur * render_priv->border_scale,
+                                    info->hash_key.shadow_offset,
+                                    info->hash_key.border_style);
+            if (error)
+                info->symbol = 0;
+
+            // add bitmaps to cache
+            hash_val.bm_o = info->bm_o;
+            hash_val.bm = info->bm;
+            hash_val.bm_s = info->bm_s;
+            cache_add_bitmap(render_priv->cache.bitmap_cache,
+                             &(info->hash_key), &hash_val);
+        }
+    }
+    // deallocate glyphs
+    if (info->glyph)
+        FT_Done_Glyph(info->glyph);
+    if (info->outline_glyph)
+        FT_Done_Glyph(info->outline_glyph);
 }
 
 /**
@@ -1558,31 +1275,100 @@ static void get_bitmap_glyph(glyph_info_
  *   lines[].asc
  *   lines[].desc
  */
-static void measure_text(void)
+static void measure_text(ASS_Renderer *render_priv)
 {
-	int cur_line = 0, max_asc = 0, max_desc = 0;
-	int i;
-	text_info.height = 0;
-	for (i = 0; i < text_info.length + 1; ++i) {
-		if ((i == text_info.length) || text_info.glyphs[i].linebreak) {
-			text_info.lines[cur_line].asc = max_asc;
-			text_info.lines[cur_line].desc = max_desc;
-			text_info.height += max_asc + max_desc;
-			cur_line ++;
-			max_asc = max_desc = 0;
-		}
-		if (i < text_info.length) {
-			glyph_info_t* cur = text_info.glyphs + i;
-			if (cur->asc > max_asc)
-				max_asc = cur->asc;
-			if (cur->desc > max_desc)
-				max_desc = cur->desc;
-		}
-	}
-	text_info.height += (text_info.n_lines - 1) * double_to_d6(global_settings->line_spacing);
+    TextInfo *text_info = &render_priv->text_info;
+    int cur_line = 0;
+    double max_asc = 0., max_desc = 0.;
+    GlyphInfo *last = NULL;
+    int i;
+    int empty_line = 1;
+    text_info->height = 0.;
+    for (i = 0; i < text_info->length + 1; ++i) {
+        if ((i == text_info->length) || text_info->glyphs[i].linebreak) {
+            if (empty_line && cur_line > 0 && last && i < text_info->length) {
+                max_asc = d6_to_double(last->asc) / 2.0;
+                max_desc = d6_to_double(last->desc) / 2.0;
+            }
+            text_info->lines[cur_line].asc = max_asc;
+            text_info->lines[cur_line].desc = max_desc;
+            text_info->height += max_asc + max_desc;
+            cur_line++;
+            max_asc = max_desc = 0.;
+            empty_line = 1;
+        } else
+            empty_line = 0;
+        if (i < text_info->length) {
+            GlyphInfo *cur = text_info->glyphs + i;
+            if (d6_to_double(cur->asc) > max_asc)
+                max_asc = d6_to_double(cur->asc);
+            if (d6_to_double(cur->desc) > max_desc)
+                max_desc = d6_to_double(cur->desc);
+            if (cur->symbol != '\n' && cur->symbol != 0)
+                last = cur;
+        }
+    }
+    text_info->height +=
+        (text_info->n_lines -
+         1) * render_priv->settings.line_spacing;
 }
 
 /**
+ * Mark extra whitespace for later removal.
+ */
+#define IS_WHITESPACE(x) ((x->symbol == ' ' || x->symbol == '\n') \
+                          && !x->linebreak)
+static void trim_whitespace(ASS_Renderer *render_priv)
+{
+    int i, j;
+    GlyphInfo *cur;
+    TextInfo *ti = &render_priv->text_info;
+
+    // Mark trailing spaces
+    i = ti->length - 1;
+    cur = ti->glyphs + i;
+    while (i && IS_WHITESPACE(cur)) {
+        cur->skip++;
+        cur = ti->glyphs + --i;
+    }
+
+    // Mark leading whitespace
+    i = 0;
+    cur = ti->glyphs;
+    while (i < ti->length && IS_WHITESPACE(cur)) {
+        cur->skip++;
+        cur = ti->glyphs + ++i;
+    }
+
+    // Mark all extraneous whitespace inbetween
+    for (i = 0; i < ti->length; ++i) {
+        cur = ti->glyphs + i;
+        if (cur->linebreak) {
+            // Mark whitespace before
+            j = i - 1;
+            cur = ti->glyphs + j;
+            while (j && IS_WHITESPACE(cur)) {
+                cur->skip++;
+                cur = ti->glyphs + --j;
+            }
+            // A break itself can contain a whitespace, too
+            cur = ti->glyphs + i;
+            if (cur->symbol == ' ')
+                cur->skip++;
+            // Mark whitespace after
+            j = i + 1;
+            cur = ti->glyphs + j;
+            while (j < ti->length && IS_WHITESPACE(cur)) {
+                cur->skip++;
+                cur = ti->glyphs + ++j;
+            }
+            i = j - 1;
+        }
+    }
+}
+#undef IS_WHITESPACE
+
+/**
  * \brief rearrange text between lines
  * \param max_text_width maximal text line width in pixels
  * The algo is similar to the one in libvo/sub.c:
@@ -1590,132 +1376,167 @@ static void measure_text(void)
  * 2. Try moving words from the end of a line to the beginning of the next one while it reduces
  * the difference in lengths between this two lines.
  * The result may not be optimal, but usually is good enough.
+ *
+ * FIXME: implement style 0 and 3 correctly, add support for style 1
  */
-static void wrap_lines_smart(int max_text_width)
+static void
+wrap_lines_smart(ASS_Renderer *render_priv, double max_text_width)
 {
-	int i, j;
-	glyph_info_t *cur, *s1, *e1, *s2, *s3, *w;
-	int last_space;
-	int break_type;
-	int exit;
-	int pen_shift_x;
-	int pen_shift_y;
-	int cur_line;
+    int i;
+    GlyphInfo *cur, *s1, *e1, *s2, *s3, *w;
+    int last_space;
+    int break_type;
+    int exit;
+    double pen_shift_x;
+    double pen_shift_y;
+    int cur_line;
+    TextInfo *text_info = &render_priv->text_info;
 
-	last_space = -1;
-	text_info.n_lines = 1;
-	break_type = 0;
-	s1 = text_info.glyphs; // current line start
-	for (i = 0; i < text_info.length; ++i) {
-		int break_at, s_offset, len;
-		cur = text_info.glyphs + i;
-		break_at = -1;
-		s_offset = s1->bbox.xMin + s1->pos.x;
-		len = (cur->bbox.xMax + cur->pos.x) - s_offset;
+    last_space = -1;
+    text_info->n_lines = 1;
+    break_type = 0;
+    s1 = text_info->glyphs;     // current line start
+    for (i = 0; i < text_info->length; ++i) {
+        int break_at;
+        double s_offset, len;
+        cur = text_info->glyphs + i;
+        break_at = -1;
+        s_offset = d6_to_double(s1->bbox.xMin + s1->pos.x);
+        len = d6_to_double(cur->bbox.xMax + cur->pos.x) - s_offset;
 
-		if (cur->symbol == '\n') {
-			break_type = 2;
-			break_at = i;
-			mp_msg(MSGT_ASS, MSGL_DBG2, "forced line break at %d\n", break_at);
-		}
+        if (cur->symbol == '\n') {
+            break_type = 2;
+            break_at = i;
+            ass_msg(render_priv->library, MSGL_DBG2,
+                    "forced line break at %d", break_at);
+        }
 
-		if ((len >= max_text_width) && (frame_context.track->WrapStyle != 2)) {
-			break_type = 1;
-			break_at = last_space;
-			if (break_at == -1)
-				break_at = i - 1;
-			if (break_at == -1)
-				break_at = 0;
-			mp_msg(MSGT_ASS, MSGL_DBG2, "overfill at %d\n", i);
-			mp_msg(MSGT_ASS, MSGL_DBG2, "line break at %d\n", break_at);
-		}
+        if ((len >= max_text_width)
+            && (render_priv->state.wrap_style != 2)) {
+            break_type = 1;
+            break_at = last_space;
+            if (break_at == -1)
+                break_at = i - 1;
+            if (break_at == -1)
+                break_at = 0;
+            ass_msg(render_priv->library, MSGL_DBG2, "overfill at %d", i);
+            ass_msg(render_priv->library, MSGL_DBG2, "line break at %d",
+                    break_at);
+        }
 
-		if (break_at != -1) {
-			// need to use one more line
-			// marking break_at+1 as start of a new line
-			int lead = break_at + 1; // the first symbol of the new line
-			if (text_info.n_lines >= MAX_LINES) {
-				// to many lines !
-				// no more linebreaks
-				for (j = lead; j < text_info.length; ++j)
-					text_info.glyphs[j].linebreak = 0;
-				break;
-			}
-			if (lead < text_info.length)
-				text_info.glyphs[lead].linebreak = break_type;
-			last_space = -1;
-			s1 = text_info.glyphs + lead;
-			s_offset = s1->bbox.xMin + s1->pos.x;
-			text_info.n_lines ++;
-		}
+        if (break_at != -1) {
+            // need to use one more line
+            // marking break_at+1 as start of a new line
+            int lead = break_at + 1;    // the first symbol of the new line
+            if (text_info->n_lines >= text_info->max_lines) {
+                // Raise maximum number of lines
+                text_info->max_lines *= 2;
+                text_info->lines = realloc(text_info->lines,
+                                           sizeof(LineInfo) *
+                                           text_info->max_lines);
+            }
+            if (lead < text_info->length)
+                text_info->glyphs[lead].linebreak = break_type;
+            last_space = -1;
+            s1 = text_info->glyphs + lead;
+            s_offset = d6_to_double(s1->bbox.xMin + s1->pos.x);
+            text_info->n_lines++;
+        }
 
-		if (cur->symbol == ' ')
-			last_space = i;
+        if (cur->symbol == ' ')
+            last_space = i;
 
-		// make sure the hard linebreak is not forgotten when
-		// there was a new soft linebreak just inserted
-		if (cur->symbol == '\n' && break_type == 1)
-			i--;
-	}
+        // make sure the hard linebreak is not forgotten when
+        // there was a new soft linebreak just inserted
+        if (cur->symbol == '\n' && break_type == 1)
+            i--;
+    }
 #define DIFF(x,y) (((x) < (y)) ? (y - x) : (x - y))
-	exit = 0;
-	while (!exit) {
-		exit = 1;
-		w = s3 = text_info.glyphs;
-		s1 = s2 = 0;
-		for (i = 0; i <= text_info.length; ++i) {
-			cur = text_info.glyphs + i;
-			if ((i == text_info.length) || cur->linebreak) {
-				s1 = s2;
-				s2 = s3;
-				s3 = cur;
-				if (s1 && (s2->linebreak == 1)) { // have at least 2 lines, and linebreak is 'soft'
-					int l1, l2, l1_new, l2_new;
+    exit = 0;
+    while (!exit && render_priv->state.wrap_style != 1) {
+        exit = 1;
+        w = s3 = text_info->glyphs;
+        s1 = s2 = 0;
+        for (i = 0; i <= text_info->length; ++i) {
+            cur = text_info->glyphs + i;
+            if ((i == text_info->length) || cur->linebreak) {
+                s1 = s2;
+                s2 = s3;
+                s3 = cur;
+                if (s1 && (s2->linebreak == 1)) {       // have at least 2 lines, and linebreak is 'soft'
+                    double l1, l2, l1_new, l2_new;
 
-					w = s2;
-					do { --w; } while ((w > s1) && (w->symbol == ' '));
-					while ((w > s1) && (w->symbol != ' ')) { --w; }
-					e1 = w;
-					while ((e1 > s1) && (e1->symbol == ' ')) { --e1; }
-					if (w->symbol == ' ') ++w;
+                    w = s2;
+                    do {
+                        --w;
+                    } while ((w > s1) && (w->symbol == ' '));
+                    while ((w > s1) && (w->symbol != ' ')) {
+                        --w;
+                    }
+                    e1 = w;
+                    while ((e1 > s1) && (e1->symbol == ' ')) {
+                        --e1;
+                    }
+                    if (w->symbol == ' ')
+                        ++w;
 
-					l1 = ((s2-1)->bbox.xMax + (s2-1)->pos.x) - (s1->bbox.xMin + s1->pos.x);
-					l2 = ((s3-1)->bbox.xMax + (s3-1)->pos.x) - (s2->bbox.xMin + s2->pos.x);
-					l1_new = (e1->bbox.xMax + e1->pos.x) - (s1->bbox.xMin + s1->pos.x);
-					l2_new = ((s3-1)->bbox.xMax + (s3-1)->pos.x) - (w->bbox.xMin + w->pos.x);
+                    l1 = d6_to_double(((s2 - 1)->bbox.xMax + (s2 - 1)->pos.x) -
+                        (s1->bbox.xMin + s1->pos.x));
+                    l2 = d6_to_double(((s3 - 1)->bbox.xMax + (s3 - 1)->pos.x) -
+                        (s2->bbox.xMin + s2->pos.x));
+                    l1_new = d6_to_double(
+                        (e1->bbox.xMax + e1->pos.x) -
+                        (s1->bbox.xMin + s1->pos.x));
+                    l2_new = d6_to_double(
+                        ((s3 - 1)->bbox.xMax + (s3 - 1)->pos.x) -
+                        (w->bbox.xMin + w->pos.x));
 
-					if (DIFF(l1_new, l2_new) < DIFF(l1, l2)) {
-						w->linebreak = 1;
-						s2->linebreak = 0;
-						exit = 0;
-					}
-				}
-			}
-			if (i == text_info.length)
-				break;
-		}
+                    if (DIFF(l1_new, l2_new) < DIFF(l1, l2)) {
+                        w->linebreak = 1;
+                        s2->linebreak = 0;
+                        exit = 0;
+                    }
+                }
+            }
+            if (i == text_info->length)
+                break;
+        }
 
-	}
-	assert(text_info.n_lines >= 1);
+    }
+    assert(text_info->n_lines >= 1);
 #undef DIFF
 
-	measure_text();
+    measure_text(render_priv);
+    trim_whitespace(render_priv);
 
-	pen_shift_x = 0;
-	pen_shift_y = 0;
-	cur_line = 1;
-	for (i = 0; i < text_info.length; ++i) {
-		cur = text_info.glyphs + i;
-		if (cur->linebreak) {
-			int height = text_info.lines[cur_line - 1].desc + text_info.lines[cur_line].asc;
-			cur_line ++;
-			pen_shift_x = - cur->pos.x;
-			pen_shift_y += d6_to_int(height + double_to_d6(global_settings->line_spacing));
-			mp_msg(MSGT_ASS, MSGL_DBG2, "shifting from %d to %d by (%d, %d)\n", i, text_info.length - 1, pen_shift_x, pen_shift_y);
-		}
-		cur->pos.x += pen_shift_x;
-		cur->pos.y += pen_shift_y;
-	}
+    pen_shift_x = 0.;
+    pen_shift_y = 0.;
+    cur_line = 1;
+
+    i = 0;
+    cur = text_info->glyphs + i;
+    while (i < text_info->length && cur->skip)
+        cur = text_info->glyphs + ++i;
+    pen_shift_x = d6_to_double(-cur->pos.x);
+
+    for (i = 0; i < text_info->length; ++i) {
+        cur = text_info->glyphs + i;
+        if (cur->linebreak) {
+            while (i < text_info->length && cur->skip && cur->symbol != '\n')
+                cur = text_info->glyphs + ++i;
+            double height =
+                text_info->lines[cur_line - 1].desc +
+                text_info->lines[cur_line].asc;
+            cur_line++;
+            pen_shift_x = d6_to_double(-cur->pos.x);
+            pen_shift_y += height + render_priv->settings.line_spacing;
+            ass_msg(render_priv->library, MSGL_DBG2,
+                   "shifting from %d to %d by (%f, %f)", i,
+                   text_info->length - 1, pen_shift_x, pen_shift_y);
+        }
+        cur->pos.x += double_to_d6(pen_shift_x);
+        cur->pos.y += double_to_d6(pen_shift_y);
+    }
 }
 
 /**
@@ -1729,60 +1550,63 @@ static void wrap_lines_smart(int max_tex
  * 2. sets effect_timing for all glyphs to x coordinate of the border line between the left and right karaoke parts
  * (left part is filled with PrimaryColour, right one - with SecondaryColour).
  */
-static void process_karaoke_effects(void)
+static void process_karaoke_effects(ASS_Renderer *render_priv)
 {
-	glyph_info_t *cur, *cur2;
-	glyph_info_t *s1, *e1; // start and end of the current word
-	glyph_info_t *s2; // start of the next word
-	int i;
-	int timing; // current timing
-	int tm_start, tm_end; // timings at start and end of the current word
-	int tm_current;
-	double dt;
-	int x;
-	int x_start, x_end;
+    GlyphInfo *cur, *cur2;
+    GlyphInfo *s1, *e1;      // start and end of the current word
+    GlyphInfo *s2;           // start of the next word
+    int i;
+    int timing;                 // current timing
+    int tm_start, tm_end;       // timings at start and end of the current word
+    int tm_current;
+    double dt;
+    int x;
+    int x_start, x_end;
 
-	tm_current = frame_context.time - render_context.event->Start;
-	timing = 0;
-	s1 = s2 = 0;
-	for (i = 0; i <= text_info.length; ++i) {
-		cur = text_info.glyphs + i;
-		if ((i == text_info.length) || (cur->effect_type != EF_NONE)) {
-			s1 = s2;
-			s2 = cur;
-			if (s1) {
-				e1 = s2 - 1;
-				tm_start = timing + s1->effect_skip_timing;
-				tm_end = tm_start + s1->effect_timing;
-				timing = tm_end;
-				x_start = 1000000;
-				x_end = -1000000;
-				for (cur2 = s1; cur2 <= e1; ++cur2) {
-					x_start = FFMIN(x_start, cur2->bbox.xMin + cur2->pos.x);
-					x_end = FFMAX(x_end, cur2->bbox.xMax + cur2->pos.x);
-				}
+    tm_current = render_priv->time - render_priv->state.event->Start;
+    timing = 0;
+    s1 = s2 = 0;
+    for (i = 0; i <= render_priv->text_info.length; ++i) {
+        cur = render_priv->text_info.glyphs + i;
+        if ((i == render_priv->text_info.length)
+            || (cur->effect_type != EF_NONE)) {
+            s1 = s2;
+            s2 = cur;
+            if (s1) {
+                e1 = s2 - 1;
+                tm_start = timing + s1->effect_skip_timing;
+                tm_end = tm_start + s1->effect_timing;
+                timing = tm_end;
+                x_start = 1000000;
+                x_end = -1000000;
+                for (cur2 = s1; cur2 <= e1; ++cur2) {
+                    x_start = FFMIN(x_start, d6_to_int(cur2->bbox.xMin + cur2->pos.x));
+                    x_end = FFMAX(x_end, d6_to_int(cur2->bbox.xMax + cur2->pos.x));
+                }
 
-				dt = (tm_current - tm_start);
-				if ((s1->effect_type == EF_KARAOKE) || (s1->effect_type == EF_KARAOKE_KO)) {
-					if (dt > 0)
-						x = x_end + 1;
-					else
-						x = x_start;
-				} else if (s1->effect_type == EF_KARAOKE_KF) {
-					dt /= (tm_end - tm_start);
-					x = x_start + (x_end - x_start) * dt;
-				} else {
-					mp_msg(MSGT_ASS, MSGL_ERR, MSGTR_LIBASS_UnknownEffectType_InternalError);
-					continue;
-				}
+                dt = (tm_current - tm_start);
+                if ((s1->effect_type == EF_KARAOKE)
+                    || (s1->effect_type == EF_KARAOKE_KO)) {
+                    if (dt > 0)
+                        x = x_end + 1;
+                    else
+                        x = x_start;
+                } else if (s1->effect_type == EF_KARAOKE_KF) {
+                    dt /= (tm_end - tm_start);
+                    x = x_start + (x_end - x_start) * dt;
+                } else {
+                    ass_msg(render_priv->library, MSGL_ERR,
+                            "Unknown effect type");
+                    continue;
+                }
 
-				for (cur2 = s1; cur2 <= e1; ++cur2) {
-					cur2->effect_type = s1->effect_type;
-					cur2->effect_timing = x - cur2->pos.x;
-				}
-			}
-		}
-	}
+                for (cur2 = s1; cur2 <= e1; ++cur2) {
+                    cur2->effect_type = s1->effect_type;
+                    cur2->effect_timing = x - d6_to_int(cur2->pos.x);
+                }
+            }
+        }
+    }
 }
 
 /**
@@ -1791,34 +1615,34 @@ static void process_karaoke_effects(void
  * \param alignment alignment
  * \param bx, by out: base point coordinates
  */
-static void get_base_point(FT_BBox bbox, int alignment, int* bx, int* by)
+static void get_base_point(DBBox *bbox, int alignment, double *bx, double *by)
 {
-	const int halign = alignment & 3;
-	const int valign = alignment & 12;
-	if (bx)
-		switch(halign) {
-		case HALIGN_LEFT:
-			*bx = bbox.xMin;
-			break;
-		case HALIGN_CENTER:
-			*bx = (bbox.xMax + bbox.xMin) / 2;
-			break;
-		case HALIGN_RIGHT:
-			*bx = bbox.xMax;
-			break;
-		}
-	if (by)
-		switch(valign) {
-		case VALIGN_TOP:
-			*by = bbox.yMin;
-			break;
-		case VALIGN_CENTER:
-			*by = (bbox.yMax + bbox.yMin) / 2;
-			break;
-		case VALIGN_SUB:
-			*by = bbox.yMax;
-			break;
-		}
+    const int halign = alignment & 3;
+    const int valign = alignment & 12;
+    if (bx)
+        switch (halign) {
+        case HALIGN_LEFT:
+            *bx = bbox->xMin;
+            break;
+        case HALIGN_CENTER:
+            *bx = (bbox->xMax + bbox->xMin) / 2.0;
+            break;
+        case HALIGN_RIGHT:
+            *bx = bbox->xMax;
+            break;
+        }
+    if (by)
+        switch (valign) {
+        case VALIGN_TOP:
+            *by = bbox->yMin;
+            break;
+        case VALIGN_CENTER:
+            *by = (bbox->yMax + bbox->yMin) / 2.0;
+            break;
+        case VALIGN_SUB:
+            *by = bbox->yMax;
+            break;
+        }
 }
 
 /**
@@ -1826,42 +1650,47 @@ static void get_base_point(FT_BBox bbox,
  * Applies rotations given by frx, fry and frz and projects the points back
  * onto the screen plane.
  */
-static void transform_3d_points(FT_Vector shift, FT_Glyph glyph, double frx, double fry, double frz) {
-	double sx = sin(frx);
-	double sy = sin(fry);
-	double sz = sin(frz);
-	double cx = cos(frx);
-	double cy = cos(fry);
-	double cz = cos(frz);
-	FT_Outline *outline = &((FT_OutlineGlyph) glyph)->outline;
-	FT_Vector* p = outline->points;
-	double x, y, z, xx, yy, zz;
-	int i;
+static void
+transform_3d_points(FT_Vector shift, FT_Glyph glyph, double frx, double fry,
+                    double frz, double fax, double fay, double scale,
+                    int yshift)
+{
+    double sx = sin(frx);
+    double sy = sin(fry);
+    double sz = sin(frz);
+    double cx = cos(frx);
+    double cy = cos(fry);
+    double cz = cos(frz);
+    FT_Outline *outline = &((FT_OutlineGlyph) glyph)->outline;
+    FT_Vector *p = outline->points;
+    double x, y, z, xx, yy, zz;
+    int i, dist;
 
-	for (i=0; i<outline->n_points; i++) {
-		x = p[i].x + shift.x;
-		y = p[i].y + shift.y;
-		z = 0.;
+    dist = 20000 * scale;
+    for (i = 0; i < outline->n_points; i++) {
+        x = (double) p[i].x + shift.x + (fax * (yshift - p[i].y));
+        y = (double) p[i].y + shift.y + (-fay * p[i].x);
+        z = 0.;
 
-		xx = x*cz + y*sz;
-		yy = -(x*sz - y*cz);
-		zz = z;
+        xx = x * cz + y * sz;
+        yy = -(x * sz - y * cz);
+        zz = z;
 
-		x = xx;
-		y = yy*cx + zz*sx;
-		z = yy*sx - zz*cx;
+        x = xx;
+        y = yy * cx + zz * sx;
+        z = yy * sx - zz * cx;
 
-		xx = x*cy + z*sy;
-		yy = y;
-		zz = x*sy - z*cy;
+        xx = x * cy + z * sy;
+        yy = y;
+        zz = x * sy - z * cy;
 
-		zz = FFMAX(zz, -19000);
+        zz = FFMAX(zz, 1000 - dist);
 
-		x = (xx * 20000) / (zz + 20000);
-		y = (yy * 20000) / (zz + 20000);
-		p[i].x = x - shift.x + 0.5;
-		p[i].y = y - shift.y + 0.5;
-	}
+        x = (xx * dist) / (zz + dist);
+        y = (yy * dist) / (zz + dist);
+        p[i].x = x - shift.x + 0.5;
+        p[i].y = y - shift.y + 0.5;
+    }
 }
 
 /**
@@ -1874,17 +1703,22 @@ static void transform_3d_points(FT_Vecto
  * \param frz z-axis rotation angle
  * Rotates both glyphs by frx, fry and frz. Shift vector is added before rotation and subtracted after it.
  */
-static void transform_3d(FT_Vector shift, FT_Glyph* glyph, FT_Glyph* glyph2, double frx, double fry, double frz)
+static void
+transform_3d(FT_Vector shift, FT_Glyph *glyph, FT_Glyph *glyph2,
+             double frx, double fry, double frz, double fax, double fay,
+             double scale, int yshift)
 {
-	frx = - frx;
-	frz = - frz;
-	if (frx != 0. || fry != 0. || frz != 0.) {
-		if (glyph && *glyph)
-			transform_3d_points(shift, *glyph, frx, fry, frz);
+    frx = -frx;
+    frz = -frz;
+    if (frx != 0. || fry != 0. || frz != 0. || fax != 0. || fay != 0.) {
+        if (glyph && *glyph)
+            transform_3d_points(shift, *glyph, frx, fry, frz,
+                                fax, fay, scale, yshift);
 
-		if (glyph2 && *glyph2)
-			transform_3d_points(shift, *glyph2, frx, fry, frz);
-	}
+        if (glyph2 && *glyph2)
+            transform_3d_points(shift, *glyph2, frx, fry, frz,
+                                fax, fay, scale, yshift);
+    }
 }
 
 
@@ -1892,622 +1726,836 @@ static void transform_3d(FT_Vector shift
  * \brief Main ass rendering function, glues everything together
  * \param event event to render
  * \param event_images struct containing resulting images, will also be initialized
- * Process event, appending resulting ass_image_t's to images_root.
+ * Process event, appending resulting ASS_Image's to images_root.
  */
-static int ass_render_event(ass_event_t* event, event_images_t* event_images)
+static int
+ass_render_event(ASS_Renderer *render_priv, ASS_Event *event,
+                 EventImages *event_images)
 {
-	char* p;
-	FT_UInt previous;
-	FT_UInt num_glyphs;
-	FT_Vector pen;
-	unsigned code;
-	FT_BBox bbox;
-	int i, j;
-	FT_Vector shift;
-	int MarginL, MarginR, MarginV;
-	int last_break;
-	int alignment, halign, valign;
-	int device_x = 0, device_y = 0;
-
-	if (event->Style >= frame_context.track->n_styles) {
-		mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_NoStyleFound);
-		return 1;
-	}
-	if (!event->Text) {
-		mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_EmptyEvent);
-		return 1;
-	}
-
-	init_render_context(event);
+    char *p;
+    FT_UInt previous;
+    FT_UInt num_glyphs;
+    FT_Vector pen;
+    unsigned code;
+    DBBox bbox;
+    int i, j;
+    int MarginL, MarginR, MarginV;
+    int last_break;
+    int alignment, halign, valign;
+    int kern = render_priv->track->Kerning;
+    double device_x = 0;
+    double device_y = 0;
+    TextInfo *text_info = &render_priv->text_info;
+    ASS_Drawing *drawing;
 
-	text_info.length = 0;
-	pen.x = 0;
-	pen.y = 0;
-	previous = 0;
-	num_glyphs = 0;
-	p = event->Text;
-	// Event parsing.
-	while (1) {
-		// get next char, executing style override
-		// this affects render_context
-		do {
-			code = get_next_char(&p);
-		} while (code && render_context.drawing_mode); // skip everything in drawing mode
+    if (event->Style >= render_priv->track->n_styles) {
+        ass_msg(render_priv->library, MSGL_WARN, "No style found");
+        return 1;
+    }
+    if (!event->Text) {
+        ass_msg(render_priv->library, MSGL_WARN, "Empty event");
+        return 1;
+    }
 
-		// face could have been changed in get_next_char
-		if (!render_context.font) {
-			free_render_context();
-			return 1;
-		}
+    init_render_context(render_priv, event);
 
-		if (code == 0)
-			break;
+    drawing = render_priv->state.drawing;
+    text_info->length = 0;
+    pen.x = 0;
+    pen.y = 0;
+    previous = 0;
+    num_glyphs = 0;
+    p = event->Text;
+    // Event parsing.
+    while (1) {
+        // get next char, executing style override
+        // this affects render_context
+        do {
+            code = get_next_char(render_priv, &p);
+            if (render_priv->state.drawing_mode && code)
+                ass_drawing_add_char(drawing, (char) code);
+        } while (code && render_priv->state.drawing_mode);      // skip everything in drawing mode
 
-		if (text_info.length >= MAX_GLYPHS) {
-			mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_MAX_GLYPHS_Reached,
-					(int)(event - frame_context.track->events), event->Start, event->Duration, event->Text);
-			break;
-		}
+        // Parse drawing
+        if (drawing->i) {
+            drawing->scale_x = render_priv->state.scale_x *
+                                     render_priv->font_scale_x *
+                                     render_priv->font_scale;
+            drawing->scale_y = render_priv->state.scale_y *
+                                     render_priv->font_scale;
+            ass_drawing_hash(drawing);
+            p--;
+            code = -1;
+        }
 
-		if ( previous && code ) {
-			FT_Vector delta;
-			delta = ass_font_get_kerning(render_context.font, previous, code);
-			pen.x += delta.x * render_context.scale_x;
-			pen.y += delta.y * render_context.scale_y;
-		}
+        // face could have been changed in get_next_char
+        if (!render_priv->state.font) {
+            free_render_context(render_priv);
+            return 1;
+        }
 
-		shift.x = pen.x & SUBPIXEL_MASK;
-		shift.y = pen.y & SUBPIXEL_MASK;
+        if (code == 0)
+            break;
 
-		if (render_context.evt_type == EVENT_POSITIONED) {
-			shift.x += double_to_d6(x2scr_pos(render_context.pos_x)) & SUBPIXEL_MASK;
-			shift.y -= double_to_d6(y2scr_pos(render_context.pos_y)) & SUBPIXEL_MASK;
-		}
+        if (text_info->length >= text_info->max_glyphs) {
+            // Raise maximum number of glyphs
+            text_info->max_glyphs *= 2;
+            text_info->glyphs =
+                realloc(text_info->glyphs,
+                        sizeof(GlyphInfo) * text_info->max_glyphs);
+        }
 
-		ass_font_set_transform(render_context.font,
-				       render_context.scale_x * frame_context.font_scale_x,
-				       render_context.scale_y,
-				       &shift );
+        // Add kerning to pen
+        if (kern && previous && code && !drawing->hash) {
+            FT_Vector delta;
+            delta =
+                ass_font_get_kerning(render_priv->state.font, previous,
+                                     code);
+            pen.x += delta.x * render_priv->state.scale_x
+                     * render_priv->font_scale_x;
+            pen.y += delta.y * render_priv->state.scale_y
+                     * render_priv->font_scale_x;
+        }
 
-		get_outline_glyph(code, text_info.glyphs + text_info.length, &shift);
+        ass_font_set_transform(render_priv->state.font,
+                               render_priv->state.scale_x *
+                               render_priv->font_scale_x,
+                               render_priv->state.scale_y, NULL);
 
-		text_info.glyphs[text_info.length].pos.x = pen.x >> 6;
-		text_info.glyphs[text_info.length].pos.y = pen.y >> 6;
+        get_outline_glyph(render_priv, code,
+                          text_info->glyphs + text_info->length, drawing);
 
-		pen.x += text_info.glyphs[text_info.length].advance.x;
-		pen.x += double_to_d6(render_context.hspacing);
-		pen.y += text_info.glyphs[text_info.length].advance.y;
+        // Add additional space after italic to non-italic style changes
+        if (text_info->length &&
+            text_info->glyphs[text_info->length - 1].hash_key.italic &&
+            !render_priv->state.italic) {
+            int back = text_info->length - 1;
+            GlyphInfo *og = &text_info->glyphs[back];
+            while (back && og->bbox.xMax - og->bbox.xMin == 0
+                   && og->hash_key.italic)
+                og = &text_info->glyphs[--back];
+            if (og->bbox.xMax > og->advance.x) {
+                // The FreeType oblique slants by 6/16
+                pen.x += og->bbox.yMax * 0.375;
+            }
+        }
 
-		previous = code;
+        text_info->glyphs[text_info->length].pos.x = pen.x;
+        text_info->glyphs[text_info->length].pos.y = pen.y;
 
-		text_info.glyphs[text_info.length].symbol = code;
-		text_info.glyphs[text_info.length].linebreak = 0;
-		for (i = 0; i < 4; ++i) {
-			uint32_t clr = render_context.c[i];
-			change_alpha(&clr, mult_alpha(_a(clr), render_context.fade), 1.);
-			text_info.glyphs[text_info.length].c[i] = clr;
-		}
-		text_info.glyphs[text_info.length].effect_type = render_context.effect_type;
-		text_info.glyphs[text_info.length].effect_timing = render_context.effect_timing;
-		text_info.glyphs[text_info.length].effect_skip_timing = render_context.effect_skip_timing;
-		text_info.glyphs[text_info.length].be = render_context.be;
-		text_info.glyphs[text_info.length].blur = render_context.blur;
-		text_info.glyphs[text_info.length].shadow = render_context.shadow;
-		text_info.glyphs[text_info.length].frx = render_context.frx;
-		text_info.glyphs[text_info.length].fry = render_context.fry;
-		text_info.glyphs[text_info.length].frz = render_context.frz;
-		ass_font_get_asc_desc(render_context.font, code,
-				      &text_info.glyphs[text_info.length].asc,
-				      &text_info.glyphs[text_info.length].desc);
-		text_info.glyphs[text_info.length].asc *= render_context.scale_y;
-		text_info.glyphs[text_info.length].desc *= render_context.scale_y;
+        pen.x += text_info->glyphs[text_info->length].advance.x;
+        pen.x += double_to_d6(render_priv->state.hspacing *
+                              render_priv->font_scale
+                              * render_priv->state.scale_x);
+        pen.y += text_info->glyphs[text_info->length].advance.y;
+        pen.y += (render_priv->state.fay * render_priv->state.scale_y) *
+                 text_info->glyphs[text_info->length].advance.x;
 
-		// fill bitmap_hash_key
-		text_info.glyphs[text_info.length].hash_key.font = render_context.font;
-		text_info.glyphs[text_info.length].hash_key.size = render_context.font_size;
-		text_info.glyphs[text_info.length].hash_key.outline = render_context.border * 0xFFFF;
-		text_info.glyphs[text_info.length].hash_key.scale_x = render_context.scale_x * 0xFFFF;
-		text_info.glyphs[text_info.length].hash_key.scale_y = render_context.scale_y * 0xFFFF;
-		text_info.glyphs[text_info.length].hash_key.frx = render_context.frx * 0xFFFF;
-		text_info.glyphs[text_info.length].hash_key.fry = render_context.fry * 0xFFFF;
-		text_info.glyphs[text_info.length].hash_key.frz = render_context.frz * 0xFFFF;
-		text_info.glyphs[text_info.length].hash_key.bold = render_context.bold;
-		text_info.glyphs[text_info.length].hash_key.italic = render_context.italic;
-		text_info.glyphs[text_info.length].hash_key.ch = code;
-		text_info.glyphs[text_info.length].hash_key.advance = shift;
-		text_info.glyphs[text_info.length].hash_key.be = render_context.be;
-		text_info.glyphs[text_info.length].hash_key.blur = render_context.blur;
+        previous = code;
 
-		text_info.length++;
+        text_info->glyphs[text_info->length].symbol = code;
+        text_info->glyphs[text_info->length].linebreak = 0;
+        for (i = 0; i < 4; ++i) {
+            uint32_t clr = render_priv->state.c[i];
+            change_alpha(&clr,
+                         mult_alpha(_a(clr), render_priv->state.fade), 1.);
+            text_info->glyphs[text_info->length].c[i] = clr;
+        }
+        text_info->glyphs[text_info->length].effect_type =
+            render_priv->state.effect_type;
+        text_info->glyphs[text_info->length].effect_timing =
+            render_priv->state.effect_timing;
+        text_info->glyphs[text_info->length].effect_skip_timing =
+            render_priv->state.effect_skip_timing;
+        text_info->glyphs[text_info->length].be = render_priv->state.be;
+        text_info->glyphs[text_info->length].blur = render_priv->state.blur;
+        text_info->glyphs[text_info->length].shadow_x =
+            render_priv->state.shadow_x;
+        text_info->glyphs[text_info->length].shadow_y =
+            render_priv->state.shadow_y;
+        text_info->glyphs[text_info->length].frx = render_priv->state.frx;
+        text_info->glyphs[text_info->length].fry = render_priv->state.fry;
+        text_info->glyphs[text_info->length].frz = render_priv->state.frz;
+        text_info->glyphs[text_info->length].fax = render_priv->state.fax;
+        text_info->glyphs[text_info->length].fay = render_priv->state.fay;
+        if (drawing->hash) {
+            text_info->glyphs[text_info->length].asc = drawing->asc;
+            text_info->glyphs[text_info->length].desc = drawing->desc;
+        } else {
+            ass_font_get_asc_desc(render_priv->state.font, code,
+                                  &text_info->glyphs[text_info->length].asc,
+                                  &text_info->glyphs[text_info->length].desc);
 
-		render_context.effect_type = EF_NONE;
-		render_context.effect_timing = 0;
-		render_context.effect_skip_timing = 0;
-	}
+            text_info->glyphs[text_info->length].asc *=
+                render_priv->state.scale_y;
+            text_info->glyphs[text_info->length].desc *=
+                render_priv->state.scale_y;
+        }
 
-	if (text_info.length == 0) {
-		// no valid symbols in the event; this can be smth like {comment}
-		free_render_context();
-		return 1;
-	}
+        // fill bitmap_hash_key
+        if (!drawing->hash) {
+            text_info->glyphs[text_info->length].hash_key.font =
+                render_priv->state.font;
+            text_info->glyphs[text_info->length].hash_key.size =
+                render_priv->state.font_size;
+            text_info->glyphs[text_info->length].hash_key.bold =
+                render_priv->state.bold;
+            text_info->glyphs[text_info->length].hash_key.italic =
+                render_priv->state.italic;
+        } else
+            text_info->glyphs[text_info->length].hash_key.drawing_hash =
+                drawing->hash;
+        text_info->glyphs[text_info->length].hash_key.ch = code;
+        text_info->glyphs[text_info->length].hash_key.outline.x =
+            double_to_d16(render_priv->state.border_x);
+        text_info->glyphs[text_info->length].hash_key.outline.y =
+            double_to_d16(render_priv->state.border_y);
+        text_info->glyphs[text_info->length].hash_key.scale_x =
+            double_to_d16(render_priv->state.scale_x);
+        text_info->glyphs[text_info->length].hash_key.scale_y =
+            double_to_d16(render_priv->state.scale_y);
+        text_info->glyphs[text_info->length].hash_key.frx =
+            rot_key(render_priv->state.frx);
+        text_info->glyphs[text_info->length].hash_key.fry =
+            rot_key(render_priv->state.fry);
+        text_info->glyphs[text_info->length].hash_key.frz =
+            rot_key(render_priv->state.frz);
+        text_info->glyphs[text_info->length].hash_key.fax =
+            double_to_d16(render_priv->state.fax);
+        text_info->glyphs[text_info->length].hash_key.fay =
+            double_to_d16(render_priv->state.fay);
+        text_info->glyphs[text_info->length].hash_key.advance.x = pen.x;
+        text_info->glyphs[text_info->length].hash_key.advance.y = pen.y;
+        text_info->glyphs[text_info->length].hash_key.be =
+            render_priv->state.be;
+        text_info->glyphs[text_info->length].hash_key.blur =
+            render_priv->state.blur;
+        text_info->glyphs[text_info->length].hash_key.border_style =
+            render_priv->state.style->BorderStyle;
+        text_info->glyphs[text_info->length].hash_key.shadow_offset.x =
+            double_to_d6(
+                render_priv->state.shadow_x * render_priv->border_scale -
+                (int) (render_priv->state.shadow_x *
+                render_priv->border_scale));
+        text_info->glyphs[text_info->length].hash_key.shadow_offset.y =
+            double_to_d6(
+                render_priv->state.shadow_y * render_priv->border_scale -
+                (int) (render_priv->state.shadow_y *
+                render_priv->border_scale));
+        text_info->glyphs[text_info->length].hash_key.flags =
+            render_priv->state.flags;
 
-	// depends on glyph x coordinates being monotonous, so it should be done before line wrap
-	process_karaoke_effects();
+        text_info->length++;
 
-	// alignments
-	alignment = render_context.alignment;
-	halign = alignment & 3;
-	valign = alignment & 12;
+        render_priv->state.effect_type = EF_NONE;
+        render_priv->state.effect_timing = 0;
+        render_priv->state.effect_skip_timing = 0;
 
-	MarginL = (event->MarginL) ? event->MarginL : render_context.style->MarginL;
-	MarginR = (event->MarginR) ? event->MarginR : render_context.style->MarginR;
-	MarginV = (event->MarginV) ? event->MarginV : render_context.style->MarginV;
+        if (drawing->hash) {
+            ass_drawing_free(drawing);
+            drawing = render_priv->state.drawing =
+                ass_drawing_new(render_priv->fontconfig_priv,
+                    render_priv->state.font,
+                    render_priv->settings.hinting,
+                    render_priv->ftlibrary);
+        }
+    }
 
-	if (render_context.evt_type != EVENT_HSCROLL) {
-		int max_text_width;
 
-		// calculate max length of a line
-		max_text_width = x2scr(frame_context.track->PlayResX - MarginR) - x2scr(MarginL);
+    if (text_info->length == 0) {
+        // no valid symbols in the event; this can be smth like {comment}
+        free_render_context(render_priv);
+        return 1;
+    }
+    // depends on glyph x coordinates being monotonous, so it should be done before line wrap
+    process_karaoke_effects(render_priv);
 
-		// rearrange text in several lines
-		wrap_lines_smart(max_text_width);
+    // alignments
+    alignment = render_priv->state.alignment;
+    halign = alignment & 3;
+    valign = alignment & 12;
 
-		// align text
-		last_break = -1;
-		for (i = 1; i < text_info.length + 1; ++i) { // (text_info.length + 1) is the end of the last line
-			if ((i == text_info.length) || text_info.glyphs[i].linebreak) {
-				int width, shift = 0;
-				glyph_info_t* first_glyph = text_info.glyphs + last_break + 1;
-				glyph_info_t* last_glyph = text_info.glyphs + i - 1;
+    MarginL =
+        (event->MarginL) ? event->MarginL : render_priv->state.style->
+        MarginL;
+    MarginR =
+        (event->MarginR) ? event->MarginR : render_priv->state.style->
+        MarginR;
+    MarginV =
+        (event->MarginV) ? event->MarginV : render_priv->state.style->
+        MarginV;
 
-				while ((last_glyph > first_glyph) && ((last_glyph->symbol == '\n') || (last_glyph->symbol == 0)))
-					last_glyph --;
+    if (render_priv->state.evt_type != EVENT_HSCROLL) {
+        double max_text_width;
 
-				width = last_glyph->pos.x + d6_to_int(last_glyph->advance.x) - first_glyph->pos.x;
-				if (halign == HALIGN_LEFT) { // left aligned, no action
-					shift = 0;
-				} else if (halign == HALIGN_RIGHT) { // right aligned
-					shift = max_text_width - width;
-				} else if (halign == HALIGN_CENTER) { // centered
-					shift = (max_text_width - width) / 2;
-				}
-				for (j = last_break + 1; j < i; ++j) {
-					text_info.glyphs[j].pos.x += shift;
-				}
-				last_break = i - 1;
-			}
-		}
-	} else { // render_context.evt_type == EVENT_HSCROLL
-		measure_text();
-	}
+        // calculate max length of a line
+        max_text_width =
+            x2scr(render_priv,
+                  render_priv->track->PlayResX - MarginR) -
+            x2scr(render_priv, MarginL);
 
-	// determing text bounding box
-	compute_string_bbox(&text_info, &bbox);
+        // rearrange text in several lines
+        wrap_lines_smart(render_priv, max_text_width);
 
-	// determine device coordinates for text
+        // align text
+        last_break = -1;
+        for (i = 1; i < text_info->length + 1; ++i) {   // (text_info->length + 1) is the end of the last line
+            if ((i == text_info->length)
+                || text_info->glyphs[i].linebreak) {
+                double width, shift = 0;
+                GlyphInfo *first_glyph =
+                    text_info->glyphs + last_break + 1;
+                GlyphInfo *last_glyph = text_info->glyphs + i - 1;
 
-	// x coordinate for everything except positioned events
-	if (render_context.evt_type == EVENT_NORMAL ||
-	    render_context.evt_type == EVENT_VSCROLL) {
-		device_x = x2scr(MarginL);
-	} else if (render_context.evt_type == EVENT_HSCROLL) {
-		if (render_context.scroll_direction == SCROLL_RL)
-			device_x = x2scr(frame_context.track->PlayResX - render_context.scroll_shift);
-		else if (render_context.scroll_direction == SCROLL_LR)
-			device_x = x2scr(render_context.scroll_shift) - (bbox.xMax - bbox.xMin);
-	}
+                while (first_glyph < last_glyph && first_glyph->skip)
+                    first_glyph++;
 
-	// y coordinate for everything except positioned events
-	if (render_context.evt_type == EVENT_NORMAL ||
-	    render_context.evt_type == EVENT_HSCROLL) {
-		if (valign == VALIGN_TOP) { // toptitle
-			device_y = y2scr_top(MarginV) + d6_to_int(text_info.lines[0].asc);
-		} else if (valign == VALIGN_CENTER) { // midtitle
-			int scr_y = y2scr(frame_context.track->PlayResY / 2);
-			device_y = scr_y - (bbox.yMax - bbox.yMin) / 2;
-		} else { // subtitle
-			int scr_y;
-			if (valign != VALIGN_SUB)
-				mp_msg(MSGT_ASS, MSGL_V, "Invalid valign, supposing 0 (subtitle)\n");
-			scr_y = y2scr_sub(frame_context.track->PlayResY - MarginV);
-			device_y = scr_y;
-			device_y -= d6_to_int(text_info.height);
-			device_y += d6_to_int(text_info.lines[0].asc);
-		}
-	} else if (render_context.evt_type == EVENT_VSCROLL) {
-		if (render_context.scroll_direction == SCROLL_TB)
-			device_y = y2scr(render_context.clip_y0 + render_context.scroll_shift) - (bbox.yMax - bbox.yMin);
-		else if (render_context.scroll_direction == SCROLL_BT)
-			device_y = y2scr(render_context.clip_y1 - render_context.scroll_shift);
-	}
+                while ((last_glyph > first_glyph)
+                       && ((last_glyph->symbol == '\n')
+                           || (last_glyph->symbol == 0)
+                           || (last_glyph->skip)))
+                    last_glyph--;
 
-	// positioned events are totally different
-	if (render_context.evt_type == EVENT_POSITIONED) {
-		int base_x = 0;
-		int base_y = 0;
-		mp_msg(MSGT_ASS, MSGL_DBG2, "positioned event at %f, %f\n", render_context.pos_x, render_context.pos_y);
-		get_base_point(bbox, alignment, &base_x, &base_y);
-		device_x = x2scr_pos(render_context.pos_x) - base_x;
-		device_y = y2scr_pos(render_context.pos_y) - base_y;
-	}
+                width = d6_to_double(
+                    last_glyph->pos.x + last_glyph->advance.x -
+                    first_glyph->pos.x);
+                if (halign == HALIGN_LEFT) {    // left aligned, no action
+                    shift = 0;
+                } else if (halign == HALIGN_RIGHT) {    // right aligned
+                    shift = max_text_width - width;
+                } else if (halign == HALIGN_CENTER) {   // centered
+                    shift = (max_text_width - width) / 2.0;
+                }
+                for (j = last_break + 1; j < i; ++j) {
+                    text_info->glyphs[j].pos.x += double_to_d6(shift);
+                }
+                last_break = i - 1;
+            }
+        }
+    } else {                    // render_priv->state.evt_type == EVENT_HSCROLL
+        measure_text(render_priv);
+    }
 
-	// fix clip coordinates (they depend on alignment)
-	if (render_context.evt_type == EVENT_NORMAL ||
-	    render_context.evt_type == EVENT_HSCROLL ||
-	    render_context.evt_type == EVENT_VSCROLL) {
-		render_context.clip_x0 = x2scr(render_context.clip_x0);
-		render_context.clip_x1 = x2scr(render_context.clip_x1);
-		if (valign == VALIGN_TOP) {
-			render_context.clip_y0 = y2scr_top(render_context.clip_y0);
-			render_context.clip_y1 = y2scr_top(render_context.clip_y1);
-		} else if (valign == VALIGN_CENTER) {
-			render_context.clip_y0 = y2scr(render_context.clip_y0);
-			render_context.clip_y1 = y2scr(render_context.clip_y1);
-		} else if (valign == VALIGN_SUB) {
-			render_context.clip_y0 = y2scr_sub(render_context.clip_y0);
-			render_context.clip_y1 = y2scr_sub(render_context.clip_y1);
-		}
-	} else if (render_context.evt_type == EVENT_POSITIONED) {
-		render_context.clip_x0 = x2scr_pos(render_context.clip_x0);
-		render_context.clip_x1 = x2scr_pos(render_context.clip_x1);
-		render_context.clip_y0 = y2scr_pos(render_context.clip_y0);
-		render_context.clip_y1 = y2scr_pos(render_context.clip_y1);
-	}
+    // determing text bounding box
+    compute_string_bbox(text_info, &bbox);
 
-	render_context.clip_x0 = FFMIN(FFMAX(render_context.clip_x0, 0), frame_context.width);
-	render_context.clip_x1 = FFMIN(FFMAX(render_context.clip_x1, 0), frame_context.width);
-	render_context.clip_y0 = FFMIN(FFMAX(render_context.clip_y0, 0), frame_context.height);
-	render_context.clip_y1 = FFMIN(FFMAX(render_context.clip_y1, 0), frame_context.height);
+    // determine device coordinates for text
 
-	// calculate rotation parameters
-	{
-		FT_Vector center;
+    // x coordinate for everything except positioned events
+    if (render_priv->state.evt_type == EVENT_NORMAL ||
+        render_priv->state.evt_type == EVENT_VSCROLL) {
+        device_x = x2scr(render_priv, MarginL);
+    } else if (render_priv->state.evt_type == EVENT_HSCROLL) {
+        if (render_priv->state.scroll_direction == SCROLL_RL)
+            device_x =
+                x2scr(render_priv,
+                      render_priv->track->PlayResX -
+                      render_priv->state.scroll_shift);
+        else if (render_priv->state.scroll_direction == SCROLL_LR)
+            device_x =
+                x2scr(render_priv,
+                      render_priv->state.scroll_shift) - (bbox.xMax -
+                                                          bbox.xMin);
+    }
+    // y coordinate for everything except positioned events
+    if (render_priv->state.evt_type == EVENT_NORMAL ||
+        render_priv->state.evt_type == EVENT_HSCROLL) {
+        if (valign == VALIGN_TOP) {     // toptitle
+            device_y =
+                y2scr_top(render_priv,
+                          MarginV) + text_info->lines[0].asc;
+        } else if (valign == VALIGN_CENTER) {   // midtitle
+            double scr_y =
+                y2scr(render_priv, render_priv->track->PlayResY / 2.0);
+            device_y = scr_y - (bbox.yMax + bbox.yMin) / 2.0;
+        } else {                // subtitle
+            double scr_y;
+            if (valign != VALIGN_SUB)
+                ass_msg(render_priv->library, MSGL_V,
+                       "Invalid valign, supposing 0 (subtitle)");
+            scr_y =
+                y2scr_sub(render_priv,
+                          render_priv->track->PlayResY - MarginV);
+            device_y = scr_y;
+            device_y -= text_info->height;
+            device_y += text_info->lines[0].asc;
+        }
+    } else if (render_priv->state.evt_type == EVENT_VSCROLL) {
+        if (render_priv->state.scroll_direction == SCROLL_TB)
+            device_y =
+                y2scr(render_priv,
+                      render_priv->state.clip_y0 +
+                      render_priv->state.scroll_shift) - (bbox.yMax -
+                                                          bbox.yMin);
+        else if (render_priv->state.scroll_direction == SCROLL_BT)
+            device_y =
+                y2scr(render_priv,
+                      render_priv->state.clip_y1 -
+                      render_priv->state.scroll_shift);
+    }
+    // positioned events are totally different
+    if (render_priv->state.evt_type == EVENT_POSITIONED) {
+        double base_x = 0;
+        double base_y = 0;
+        ass_msg(render_priv->library, MSGL_DBG2, "positioned event at %f, %f",
+               render_priv->state.pos_x, render_priv->state.pos_y);
+        get_base_point(&bbox, alignment, &base_x, &base_y);
+        device_x =
+            x2scr_pos(render_priv, render_priv->state.pos_x) - base_x;
+        device_y =
+            y2scr_pos(render_priv, render_priv->state.pos_y) - base_y;
+    }
+    // fix clip coordinates (they depend on alignment)
+    if (render_priv->state.evt_type == EVENT_NORMAL ||
+        render_priv->state.evt_type == EVENT_HSCROLL ||
+        render_priv->state.evt_type == EVENT_VSCROLL) {
+        render_priv->state.clip_x0 =
+            x2scr(render_priv, render_priv->state.clip_x0);
+        render_priv->state.clip_x1 =
+            x2scr(render_priv, render_priv->state.clip_x1);
+        if (valign == VALIGN_TOP) {
+            render_priv->state.clip_y0 =
+                y2scr_top(render_priv, render_priv->state.clip_y0);
+            render_priv->state.clip_y1 =
+                y2scr_top(render_priv, render_priv->state.clip_y1);
+        } else if (valign == VALIGN_CENTER) {
+            render_priv->state.clip_y0 =
+                y2scr(render_priv, render_priv->state.clip_y0);
+            render_priv->state.clip_y1 =
+                y2scr(render_priv, render_priv->state.clip_y1);
+        } else if (valign == VALIGN_SUB) {
+            render_priv->state.clip_y0 =
+                y2scr_sub(render_priv, render_priv->state.clip_y0);
+            render_priv->state.clip_y1 =
+                y2scr_sub(render_priv, render_priv->state.clip_y1);
+        }
+    } else if (render_priv->state.evt_type == EVENT_POSITIONED) {
+        render_priv->state.clip_x0 =
+            x2scr_pos(render_priv, render_priv->state.clip_x0);
+        render_priv->state.clip_x1 =
+            x2scr_pos(render_priv, render_priv->state.clip_x1);
+        render_priv->state.clip_y0 =
+            y2scr_pos(render_priv, render_priv->state.clip_y0);
+        render_priv->state.clip_y1 =
+            y2scr_pos(render_priv, render_priv->state.clip_y1);
+    }
+    // calculate rotation parameters
+    {
+        DVector center;
 
-		if (render_context.have_origin) {
-			center.x = x2scr(render_context.org_x);
-			center.y = y2scr(render_context.org_y);
-		} else {
-			int bx = 0, by = 0;
-			get_base_point(bbox, alignment, &bx, &by);
-			center.x = device_x + bx;
-			center.y = device_y + by;
-		}
+        if (render_priv->state.have_origin) {
+            center.x = x2scr(render_priv, render_priv->state.org_x);
+            center.y = y2scr(render_priv, render_priv->state.org_y);
+        } else {
+            double bx = 0., by = 0.;
+            get_base_point(&bbox, alignment, &bx, &by);
+            center.x = device_x + bx;
+            center.y = device_y + by;
+        }
 
-		for (i = 0; i < text_info.length; ++i) {
-			glyph_info_t* info = text_info.glyphs + i;
+        for (i = 0; i < text_info->length; ++i) {
+            GlyphInfo *info = text_info->glyphs + i;
 
-			if (info->hash_key.frx || info->hash_key.fry || info->hash_key.frz) {
-				info->hash_key.shift_x = info->pos.x + device_x - center.x;
-				info->hash_key.shift_y = - (info->pos.y + device_y - center.y);
-			} else {
-				info->hash_key.shift_x = 0;
-				info->hash_key.shift_y = 0;
-			}
-		}
-	}
+            if (info->hash_key.frx || info->hash_key.fry
+                || info->hash_key.frz || info->hash_key.fax
+                || info->hash_key.fay) {
+                info->hash_key.shift_x = info->pos.x + double_to_d6(device_x - center.x);
+                info->hash_key.shift_y =
+                    -(info->pos.y + double_to_d6(device_y - center.y));
+            } else {
+                info->hash_key.shift_x = 0;
+                info->hash_key.shift_y = 0;
+            }
+        }
+    }
 
-	// convert glyphs to bitmaps
-	for (i = 0; i < text_info.length; ++i)
-		get_bitmap_glyph(text_info.glyphs + i);
+    // convert glyphs to bitmaps
+    for (i = 0; i < text_info->length; ++i) {
+        GlyphInfo *g = text_info->glyphs + i;
+        g->hash_key.advance.x =
+            double_to_d6(device_x - (int) device_x +
+            d6_to_double(g->pos.x & SUBPIXEL_MASK)) & ~SUBPIXEL_ACCURACY;
+        g->hash_key.advance.y =
+            double_to_d6(device_y - (int) device_y +
+            d6_to_double(g->pos.y & SUBPIXEL_MASK)) & ~SUBPIXEL_ACCURACY;
+        get_bitmap_glyph(render_priv, text_info->glyphs + i);
+    }
 
-	memset(event_images, 0, sizeof(*event_images));
-	event_images->top = device_y - d6_to_int(text_info.lines[0].asc);
-	event_images->height = d6_to_int(text_info.height);
-	event_images->detect_collisions = render_context.detect_collisions;
-	event_images->shift_direction = (valign == VALIGN_TOP) ? 1 : -1;
-	event_images->event = event;
-	event_images->imgs = render_text(&text_info, device_x, device_y);
+    memset(event_images, 0, sizeof(*event_images));
+    event_images->top = device_y - text_info->lines[0].asc;
+    event_images->height = text_info->height;
+    event_images->left = device_x + bbox.xMin + 0.5;
+    event_images->width = bbox.xMax - bbox.xMin + 0.5;
+    event_images->detect_collisions = render_priv->state.detect_collisions;
+    event_images->shift_direction = (valign == VALIGN_TOP) ? 1 : -1;
+    event_images->event = event;
+    event_images->imgs = render_text(render_priv, (int) device_x, (int) device_y);
 
-	free_render_context();
+    free_render_context(render_priv);
 
-	return 0;
+    return 0;
 }
 
 /**
  * \brief deallocate image list
  * \param img list pointer
  */
-void ass_free_images(ass_image_t* img)
+static void ass_free_images(ASS_Image *img)
 {
-	while (img) {
-		ass_image_t* next = img->next;
-		free(img);
-		img = next;
-	}
+    while (img) {
+        ASS_Image *next = img->next;
+        free(img);
+        img = next;
+    }
 }
 
-static void ass_reconfigure(ass_renderer_t* priv)
+static void ass_reconfigure(ASS_Renderer *priv)
 {
-	priv->render_id = ++last_render_id;
-	ass_glyph_cache_reset();
-	ass_bitmap_cache_reset();
-	ass_composite_cache_reset();
-	ass_free_images(priv->prev_images_root);
-	priv->prev_images_root = 0;
+    priv->render_id++;
+    priv->cache.glyph_cache =
+        ass_glyph_cache_reset(priv->cache.glyph_cache);
+    priv->cache.bitmap_cache =
+        ass_bitmap_cache_reset(priv->cache.bitmap_cache);
+    priv->cache.composite_cache =
+        ass_composite_cache_reset(priv->cache.composite_cache);
+    ass_free_images(priv->prev_images_root);
+    priv->prev_images_root = 0;
 }
 
-void ass_set_frame_size(ass_renderer_t* priv, int w, int h)
+void ass_set_frame_size(ASS_Renderer *priv, int w, int h)
 {
-	if (priv->settings.frame_width != w || priv->settings.frame_height != h) {
-		priv->settings.frame_width = w;
-		priv->settings.frame_height = h;
-		if (priv->settings.aspect == 0.)
-			priv->settings.aspect = ((double)w) / h;
-		ass_reconfigure(priv);
-	}
+    if (priv->settings.frame_width != w || priv->settings.frame_height != h) {
+        priv->settings.frame_width = w;
+        priv->settings.frame_height = h;
+        if (priv->settings.aspect == 0.) {
+            priv->settings.aspect = ((double) w) / h;
+            priv->settings.storage_aspect = ((double) w) / h;
+        }
+        ass_reconfigure(priv);
+    }
 }
 
-void ass_set_margins(ass_renderer_t* priv, int t, int b, int l, int r)
+void ass_set_margins(ASS_Renderer *priv, int t, int b, int l, int r)
 {
-	if (priv->settings.left_margin != l ||
-	    priv->settings.right_margin != r ||
-	    priv->settings.top_margin != t ||
-	    priv->settings.bottom_margin != b) {
-		priv->settings.left_margin = l;
-		priv->settings.right_margin = r;
-		priv->settings.top_margin = t;
-		priv->settings.bottom_margin = b;
-		ass_reconfigure(priv);
-	}
+    if (priv->settings.left_margin != l ||
+        priv->settings.right_margin != r ||
+        priv->settings.top_margin != t
+        || priv->settings.bottom_margin != b) {
+        priv->settings.left_margin = l;
+        priv->settings.right_margin = r;
+        priv->settings.top_margin = t;
+        priv->settings.bottom_margin = b;
+        ass_reconfigure(priv);
+    }
 }
 
-void ass_set_use_margins(ass_renderer_t* priv, int use)
+void ass_set_use_margins(ASS_Renderer *priv, int use)
 {
-	priv->settings.use_margins = use;
+    priv->settings.use_margins = use;
 }
 
-void ass_set_aspect_ratio(ass_renderer_t* priv, double ar)
+void ass_set_aspect_ratio(ASS_Renderer *priv, double dar, double sar)
 {
-	if (priv->settings.aspect != ar) {
-		priv->settings.aspect = ar;
-		ass_reconfigure(priv);
-	}
+    if (priv->settings.aspect != dar || priv->settings.storage_aspect != sar) {
+        priv->settings.aspect = dar;
+        priv->settings.storage_aspect = sar;
+        ass_reconfigure(priv);
+    }
 }
 
-void ass_set_font_scale(ass_renderer_t* priv, double font_scale)
+void ass_set_font_scale(ASS_Renderer *priv, double font_scale)
 {
-	if (priv->settings.font_size_coeff != font_scale) {
-		priv->settings.font_size_coeff = font_scale;
-		ass_reconfigure(priv);
-	}
+    if (priv->settings.font_size_coeff != font_scale) {
+        priv->settings.font_size_coeff = font_scale;
+        ass_reconfigure(priv);
+    }
 }
 
-void ass_set_hinting(ass_renderer_t* priv, ass_hinting_t ht)
+void ass_set_hinting(ASS_Renderer *priv, ASS_Hinting ht)
 {
-	if (priv->settings.hinting != ht) {
-		priv->settings.hinting = ht;
-		ass_reconfigure(priv);
-	}
+    if (priv->settings.hinting != ht) {
+        priv->settings.hinting = ht;
+        ass_reconfigure(priv);
+    }
 }
 
-void ass_set_line_spacing(ass_renderer_t* priv, double line_spacing)
+void ass_set_line_spacing(ASS_Renderer *priv, double line_spacing)
 {
-	priv->settings.line_spacing = line_spacing;
+    priv->settings.line_spacing = line_spacing;
 }
 
-static int ass_set_fonts_(ass_renderer_t* priv, const char* default_font, const char* default_family, int fc)
+void ass_set_fonts(ASS_Renderer *priv, const char *default_font,
+                   const char *default_family, int fc, const char *config,
+                   int update)
 {
-	if (priv->settings.default_font)
-		free(priv->settings.default_font);
-	if (priv->settings.default_family)
-		free(priv->settings.default_family);
-
-	priv->settings.default_font = default_font ? strdup(default_font) : 0;
-	priv->settings.default_family = default_family ? strdup(default_family) : 0;
-
-	if (priv->fontconfig_priv)
-		fontconfig_done(priv->fontconfig_priv);
-	priv->fontconfig_priv = fontconfig_init(priv->library, priv->ftlibrary, default_family, default_font, fc);
-
-	return !!priv->fontconfig_priv;
-}
+    free(priv->settings.default_font);
+    free(priv->settings.default_family);
+    priv->settings.default_font = default_font ? strdup(default_font) : 0;
+    priv->settings.default_family =
+        default_family ? strdup(default_family) : 0;
 
-int ass_set_fonts(ass_renderer_t* priv, const char* default_font, const char* default_family)
-{
-	return ass_set_fonts_(priv, default_font, default_family, 1);
+    if (priv->fontconfig_priv)
+        fontconfig_done(priv->fontconfig_priv);
+    priv->fontconfig_priv =
+        fontconfig_init(priv->library, priv->ftlibrary, default_family,
+                        default_font, fc, config, update);
 }
 
-int ass_set_fonts_nofc(ass_renderer_t* priv, const char* default_font, const char* default_family)
+int ass_fonts_update(ASS_Renderer *render_priv)
 {
-	return ass_set_fonts_(priv, default_font, default_family, 0);
+    return fontconfig_update(render_priv->fontconfig_priv);
 }
 
 /**
  * \brief Start a new frame
  */
-static int ass_start_frame(ass_renderer_t *priv, ass_track_t* track, long long now)
+static int
+ass_start_frame(ASS_Renderer *render_priv, ASS_Track *track,
+                long long now)
 {
-	ass_renderer = priv;
-	global_settings = &priv->settings;
+    ASS_Settings *settings_priv = &render_priv->settings;
+    CacheStore *cache = &render_priv->cache;
 
-	if (!priv->settings.frame_width && !priv->settings.frame_height)
-		return 1; // library not initialized
+    if (!render_priv->settings.frame_width
+        && !render_priv->settings.frame_height)
+        return 1;               // library not initialized
 
-	if (track->n_events == 0)
-		return 1; // nothing to do
+    if (render_priv->library != track->library)
+        return 1;
 
-	frame_context.ass_priv = priv;
-	frame_context.width = global_settings->frame_width;
-	frame_context.height = global_settings->frame_height;
-	frame_context.orig_width = global_settings->frame_width - global_settings->left_margin - global_settings->right_margin;
-	frame_context.orig_height = global_settings->frame_height - global_settings->top_margin - global_settings->bottom_margin;
-	frame_context.orig_width_nocrop = global_settings->frame_width -
-		FFMAX(global_settings->left_margin, 0) -
-		FFMAX(global_settings->right_margin, 0);
-	frame_context.orig_height_nocrop = global_settings->frame_height -
-		FFMAX(global_settings->top_margin, 0) -
-		FFMAX(global_settings->bottom_margin, 0);
-	frame_context.track = track;
-	frame_context.time = now;
+    free_list_clear(render_priv);
 
-	ass_lazy_track_init();
+    if (track->n_events == 0)
+        return 1;               // nothing to do
 
-	frame_context.font_scale = global_settings->font_size_coeff *
-	                           frame_context.orig_height / frame_context.track->PlayResY;
-	if (frame_context.track->ScaledBorderAndShadow)
-		frame_context.border_scale = ((double)frame_context.orig_height) / frame_context.track->PlayResY;
-	else
-		frame_context.border_scale = 1.;
+    render_priv->width = settings_priv->frame_width;
+    render_priv->height = settings_priv->frame_height;
+    render_priv->orig_width =
+        settings_priv->frame_width - settings_priv->left_margin -
+        settings_priv->right_margin;
+    render_priv->orig_height =
+        settings_priv->frame_height - settings_priv->top_margin -
+        settings_priv->bottom_margin;
+    render_priv->orig_width_nocrop =
+        settings_priv->frame_width - FFMAX(settings_priv->left_margin,
+                                           0) -
+        FFMAX(settings_priv->right_margin, 0);
+    render_priv->orig_height_nocrop =
+        settings_priv->frame_height - FFMAX(settings_priv->top_margin,
+                                            0) -
+        FFMAX(settings_priv->bottom_margin, 0);
+    render_priv->track = track;
+    render_priv->time = now;
 
-	frame_context.font_scale_x = 1.;
+    ass_lazy_track_init(render_priv);
 
-	priv->prev_images_root = priv->images_root;
-	priv->images_root = 0;
+    render_priv->font_scale = settings_priv->font_size_coeff *
+        render_priv->orig_height / render_priv->track->PlayResY;
+    if (render_priv->track->ScaledBorderAndShadow)
+        render_priv->border_scale =
+            ((double) render_priv->orig_height) /
+            render_priv->track->PlayResY;
+    else
+        render_priv->border_scale = 1.;
 
-	return 0;
+    // PAR correction
+    render_priv->font_scale_x = render_priv->settings.aspect /
+                                render_priv->settings.storage_aspect;
+
+    render_priv->prev_images_root = render_priv->images_root;
+    render_priv->images_root = 0;
+
+    if (cache->bitmap_cache->cache_size > cache->bitmap_max_size) {
+        ass_msg(render_priv->library, MSGL_V,
+                "Hitting hard bitmap cache limit (was: %ld bytes), "
+                "resetting.", (long) cache->bitmap_cache->cache_size);
+        cache->bitmap_cache = ass_bitmap_cache_reset(cache->bitmap_cache);
+        cache->composite_cache = ass_composite_cache_reset(
+            cache->composite_cache);
+        ass_free_images(render_priv->prev_images_root);
+        render_priv->prev_images_root = 0;
+    }
+
+    if (cache->glyph_cache->count > cache->glyph_max) {
+        ass_msg(render_priv->library, MSGL_V,
+            "Hitting hard glyph cache limit (was: %ld glyphs), resetting.",
+            (long) cache->glyph_cache->count);
+        cache->glyph_cache = ass_glyph_cache_reset(cache->glyph_cache);
+    }
+
+    return 0;
 }
 
-static int cmp_event_layer(const void* p1, const void* p2)
+static int cmp_event_layer(const void *p1, const void *p2)
 {
-	ass_event_t* e1 = ((event_images_t*)p1)->event;
-	ass_event_t* e2 = ((event_images_t*)p2)->event;
-	if (e1->Layer < e2->Layer)
-		return -1;
-	if (e1->Layer > e2->Layer)
-		return 1;
-	if (e1->ReadOrder < e2->ReadOrder)
-		return -1;
-	if (e1->ReadOrder > e2->ReadOrder)
-		return 1;
-	return 0;
+    ASS_Event *e1 = ((EventImages *) p1)->event;
+    ASS_Event *e2 = ((EventImages *) p2)->event;
+    if (e1->Layer < e2->Layer)
+        return -1;
+    if (e1->Layer > e2->Layer)
+        return 1;
+    if (e1->ReadOrder < e2->ReadOrder)
+        return -1;
+    if (e1->ReadOrder > e2->ReadOrder)
+        return 1;
+    return 0;
 }
 
-#define MAX_EVENTS 100
-
-static render_priv_t* get_render_priv(ass_event_t* event)
+static ASS_RenderPriv *get_render_priv(ASS_Renderer *render_priv,
+                                       ASS_Event *event)
 {
-	if (!event->render_priv)
-		event->render_priv = calloc(1, sizeof(render_priv_t));
-	// FIXME: check render_id
-	if (ass_renderer->render_id != event->render_priv->render_id) {
-		memset(event->render_priv, 0, sizeof(render_priv_t));
-		event->render_priv->render_id = ass_renderer->render_id;
-	}
-	return event->render_priv;
-}
+    if (!event->render_priv)
+        event->render_priv = calloc(1, sizeof(ASS_RenderPriv));
+    if (render_priv->render_id != event->render_priv->render_id) {
+        memset(event->render_priv, 0, sizeof(ASS_RenderPriv));
+        event->render_priv->render_id = render_priv->render_id;
+    }
 
-typedef struct segment_s {
-	int a, b; // top and height
-} segment_t;
+    return event->render_priv;
+}
 
-static int overlap(segment_t* s1, segment_t* s2)
+static int overlap(Segment *s1, Segment *s2)
 {
-	if (s1->a >= s2->b || s2->a >= s1->b)
-		return 0;
-	return 1;
+    if (s1->a >= s2->b || s2->a >= s1->b ||
+        s1->ha >= s2->hb || s2->ha >= s1->hb)
+        return 0;
+    return 1;
 }
 
-static int cmp_segment(const void* p1, const void* p2)
+static int cmp_segment(const void *p1, const void *p2)
 {
-	return ((segment_t*)p1)->a - ((segment_t*)p2)->a;
+    return ((Segment *) p1)->a - ((Segment *) p2)->a;
 }
 
-static void shift_event(event_images_t* ei, int shift)
+static void
+shift_event(ASS_Renderer *render_priv, EventImages *ei, int shift)
 {
-	ass_image_t* cur = ei->imgs;
-	while (cur) {
-		cur->dst_y += shift;
-		// clip top and bottom
-		if (cur->dst_y < 0) {
-			int clip = - cur->dst_y;
-			cur->h -= clip;
-			cur->bitmap += clip * cur->stride;
-			cur->dst_y = 0;
-		}
-		if (cur->dst_y + cur->h >= frame_context.height) {
-			int clip = cur->dst_y + cur->h - frame_context.height;
-			cur->h -= clip;
-		}
-		if (cur->h <= 0) {
-			cur->h = 0;
-			cur->dst_y = 0;
-		}
-		cur = cur->next;
-	}
-	ei->top += shift;
+    ASS_Image *cur = ei->imgs;
+    while (cur) {
+        cur->dst_y += shift;
+        // clip top and bottom
+        if (cur->dst_y < 0) {
+            int clip = -cur->dst_y;
+            cur->h -= clip;
+            cur->bitmap += clip * cur->stride;
+            cur->dst_y = 0;
+        }
+        if (cur->dst_y + cur->h >= render_priv->height) {
+            int clip = cur->dst_y + cur->h - render_priv->height;
+            cur->h -= clip;
+        }
+        if (cur->h <= 0) {
+            cur->h = 0;
+            cur->dst_y = 0;
+        }
+        cur = cur->next;
+    }
+    ei->top += shift;
 }
 
 // dir: 1 - move down
 //      -1 - move up
-static int fit_segment(segment_t* s, segment_t* fixed, int* cnt, int dir)
+static int fit_segment(Segment *s, Segment *fixed, int *cnt, int dir)
 {
-	int i;
-	int shift = 0;
+    int i;
+    int shift = 0;
 
-	if (dir == 1) // move down
-		for (i = 0; i < *cnt; ++i) {
-			if (s->b + shift <= fixed[i].a || s->a + shift >= fixed[i].b)
-				continue;
-			shift = fixed[i].b - s->a;
-		}
-	else // dir == -1, move up
-		for (i = *cnt-1; i >= 0; --i) {
-			if (s->b + shift <= fixed[i].a || s->a + shift >= fixed[i].b)
-				continue;
-			shift = fixed[i].a - s->b;
-		}
+    if (dir == 1)               // move down
+        for (i = 0; i < *cnt; ++i) {
+            if (s->b + shift <= fixed[i].a || s->a + shift >= fixed[i].b ||
+                s->hb <= fixed[i].ha || s->ha >= fixed[i].hb)
+                continue;
+            shift = fixed[i].b - s->a;
+    } else                      // dir == -1, move up
+        for (i = *cnt - 1; i >= 0; --i) {
+            if (s->b + shift <= fixed[i].a || s->a + shift >= fixed[i].b ||
+                s->hb <= fixed[i].ha || s->ha >= fixed[i].hb)
+                continue;
+            shift = fixed[i].a - s->b;
+        }
 
-	fixed[*cnt].a = s->a + shift;
-	fixed[*cnt].b = s->b + shift;
-	(*cnt)++;
-	qsort(fixed, *cnt, sizeof(segment_t), cmp_segment);
+    fixed[*cnt].a = s->a + shift;
+    fixed[*cnt].b = s->b + shift;
+    fixed[*cnt].ha = s->ha;
+    fixed[*cnt].hb = s->hb;
+    (*cnt)++;
+    qsort(fixed, *cnt, sizeof(Segment), cmp_segment);
 
-	return shift;
+    return shift;
 }
 
-static void fix_collisions(event_images_t* imgs, int cnt)
+static void
+fix_collisions(ASS_Renderer *render_priv, EventImages *imgs, int cnt)
 {
-	segment_t used[MAX_EVENTS];
-	int cnt_used = 0;
-	int i, j;
+    Segment *used = malloc(cnt * sizeof(*used));
+    int cnt_used = 0;
+    int i, j;
 
-	// fill used[] with fixed events
-	for (i = 0; i < cnt; ++i) {
-		render_priv_t* priv;
-		if (!imgs[i].detect_collisions) continue;
-		priv = get_render_priv(imgs[i].event);
-		if (priv->height > 0) { // it's a fixed event
-			segment_t s;
-			s.a = priv->top;
-			s.b = priv->top + priv->height;
-			if (priv->height != imgs[i].height) { // no, it's not
-				mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_EventHeightHasChanged);
-				priv->top = 0;
-				priv->height = 0;
-			}
-			for (j = 0; j < cnt_used; ++j)
-				if (overlap(&s, used + j)) { // no, it's not
-					priv->top = 0;
-					priv->height = 0;
-				}
-			if (priv->height > 0) { // still a fixed event
-				used[cnt_used].a = priv->top;
-				used[cnt_used].b = priv->top + priv->height;
-				cnt_used ++;
-				shift_event(imgs + i, priv->top - imgs[i].top);
-			}
-		}
-	}
-	qsort(used, cnt_used, sizeof(segment_t), cmp_segment);
+    // fill used[] with fixed events
+    for (i = 0; i < cnt; ++i) {
+        ASS_RenderPriv *priv;
+        if (!imgs[i].detect_collisions)
+            continue;
+        priv = get_render_priv(render_priv, imgs[i].event);
+        if (priv->height > 0) { // it's a fixed event
+            Segment s;
+            s.a = priv->top;
+            s.b = priv->top + priv->height;
+            s.ha = priv->left;
+            s.hb = priv->left + priv->width;
+            if (priv->height != imgs[i].height) {       // no, it's not
+                ass_msg(render_priv->library, MSGL_WARN,
+                        "Warning! Event height has changed");
+                priv->top = 0;
+                priv->height = 0;
+                priv->left = 0;
+                priv->width = 0;
+            }
+            for (j = 0; j < cnt_used; ++j)
+                if (overlap(&s, used + j)) {    // no, it's not
+                    priv->top = 0;
+                    priv->height = 0;
+                    priv->left = 0;
+                    priv->width = 0;
+                }
+            if (priv->height > 0) {     // still a fixed event
+                used[cnt_used].a = priv->top;
+                used[cnt_used].b = priv->top + priv->height;
+                used[cnt_used].ha = priv->left;
+                used[cnt_used].hb = priv->left + priv->width;
+                cnt_used++;
+                shift_event(render_priv, imgs + i, priv->top - imgs[i].top);
+            }
+        }
+    }
+    qsort(used, cnt_used, sizeof(Segment), cmp_segment);
 
-	// try to fit other events in free spaces
-	for (i = 0; i < cnt; ++i) {
-		render_priv_t* priv;
-		if (!imgs[i].detect_collisions) continue;
-		priv = get_render_priv(imgs[i].event);
-		if (priv->height == 0) { // not a fixed event
-			int shift;
-			segment_t s;
-			s.a = imgs[i].top;
-			s.b = imgs[i].top + imgs[i].height;
-			shift = fit_segment(&s, used, &cnt_used, imgs[i].shift_direction);
-			if (shift) shift_event(imgs + i, shift);
-			// make it fixed
-			priv->top = imgs[i].top;
-			priv->height = imgs[i].height;
-		}
+    // try to fit other events in free spaces
+    for (i = 0; i < cnt; ++i) {
+        ASS_RenderPriv *priv;
+        if (!imgs[i].detect_collisions)
+            continue;
+        priv = get_render_priv(render_priv, imgs[i].event);
+        if (priv->height == 0) {        // not a fixed event
+            int shift;
+            Segment s;
+            s.a = imgs[i].top;
+            s.b = imgs[i].top + imgs[i].height;
+            s.ha = imgs[i].left;
+            s.hb = imgs[i].left + imgs[i].width;
+            shift = fit_segment(&s, used, &cnt_used, imgs[i].shift_direction);
+            if (shift)
+                shift_event(render_priv, imgs + i, shift);
+            // make it fixed
+            priv->top = imgs[i].top;
+            priv->height = imgs[i].height;
+            priv->left = imgs[i].left;
+            priv->width = imgs[i].width;
+        }
 
-	}
+    }
+
+    free(used);
 }
 
 /**
@@ -2516,17 +2564,23 @@ static void fix_collisions(event_images_
  * \param i2 second image
  * \return 0 if identical, 1 if different positions, 2 if different content
  */
-int ass_image_compare(ass_image_t *i1, ass_image_t *i2)
+static int ass_image_compare(ASS_Image *i1, ASS_Image *i2)
 {
-	if (i1->w != i2->w) return 2;
-	if (i1->h != i2->h) return 2;
-	if (i1->stride != i2->stride) return 2;
-	if (i1->color != i2->color) return 2;
-	if (i1->bitmap != i2->bitmap)
-		return 2;
-	if (i1->dst_x != i2->dst_x) return 1;
-	if (i1->dst_y != i2->dst_y) return 1;
-	return 0;
+    if (i1->w != i2->w)
+        return 2;
+    if (i1->h != i2->h)
+        return 2;
+    if (i1->stride != i2->stride)
+        return 2;
+    if (i1->color != i2->color)
+        return 2;
+    if (i1->bitmap != i2->bitmap)
+        return 2;
+    if (i1->dst_x != i2->dst_x)
+        return 1;
+    if (i1->dst_y != i2->dst_y)
+        return 1;
+    return 0;
 }
 
 /**
@@ -2534,35 +2588,36 @@ int ass_image_compare(ass_image_t *i1, a
  * \param priv library handle
  * \return 0 if identical, 1 if different positions, 2 if different content
  */
-int ass_detect_change(ass_renderer_t *priv)
+static int ass_detect_change(ASS_Renderer *priv)
 {
-	ass_image_t* img, *img2;
-	int diff;
+    ASS_Image *img, *img2;
+    int diff;
 
-	img = priv->prev_images_root;
-	img2 = priv->images_root;
-	diff = 0;
-	while (img && diff < 2) {
-		ass_image_t* next, *next2;
-		next = img->next;
-		if (img2) {
-			int d = ass_image_compare(img, img2);
-			if (d > diff) diff = d;
-			next2 = img2->next;
-		} else {
-			// previous list is shorter
-			diff = 2;
-			break;
-		}
-		img = next;
-		img2 = next2;
-	}
+    img = priv->prev_images_root;
+    img2 = priv->images_root;
+    diff = 0;
+    while (img && diff < 2) {
+        ASS_Image *next, *next2;
+        next = img->next;
+        if (img2) {
+            int d = ass_image_compare(img, img2);
+            if (d > diff)
+                diff = d;
+            next2 = img2->next;
+        } else {
+            // previous list is shorter
+            diff = 2;
+            break;
+        }
+        img = next;
+        img2 = next2;
+    }
 
-	// is the previous list longer?
-	if (img2)
-		diff = 2;
+    // is the previous list longer?
+    if (img2)
+        diff = 2;
 
-	return diff;
+    return diff;
 }
 
 /**
@@ -2574,62 +2629,66 @@ int ass_detect_change(ass_renderer_t *pr
  *        0 if identical, 1 if different positions, 2 if different content.
  *        Can be NULL, in that case no detection is performed.
  */
-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)
 {
-	int i, cnt, rc;
-	event_images_t* last;
-	ass_image_t** tail;
+    int i, cnt, rc;
+    EventImages *last;
+    ASS_Image **tail;
 
-	// init frame
-	rc = ass_start_frame(priv, track, now);
-	if (rc != 0)
-		return 0;
+    // init frame
+    rc = ass_start_frame(priv, track, now);
+    if (rc != 0)
+        return 0;
 
-	// render events separately
-	cnt = 0;
-	for (i = 0; i < track->n_events; ++i) {
-		ass_event_t* event = track->events + i;
-		if ( (event->Start <= now) && (now < (event->Start + event->Duration)) ) {
-			if (cnt >= priv->eimg_size) {
-				priv->eimg_size += 100;
-				priv->eimg = realloc(priv->eimg, priv->eimg_size * sizeof(event_images_t));
-			}
-			rc = ass_render_event(event, priv->eimg + cnt);
-			if (!rc) ++cnt;
-		}
-	}
+    // render events separately
+    cnt = 0;
+    for (i = 0; i < track->n_events; ++i) {
+        ASS_Event *event = track->events + i;
+        if ((event->Start <= now)
+            && (now < (event->Start + event->Duration))) {
+            if (cnt >= priv->eimg_size) {
+                priv->eimg_size += 100;
+                priv->eimg =
+                    realloc(priv->eimg,
+                            priv->eimg_size * sizeof(EventImages));
+            }
+            rc = ass_render_event(priv, event, priv->eimg + cnt);
+            if (!rc)
+                ++cnt;
+        }
+    }
 
-	// sort by layer
-	qsort(priv->eimg, cnt, sizeof(event_images_t), cmp_event_layer);
+    // sort by layer
+    qsort(priv->eimg, cnt, sizeof(EventImages), cmp_event_layer);
 
-	// call fix_collisions for each group of events with the same layer
-	last = priv->eimg;
-	for (i = 1; i < cnt; ++i)
-		if (last->event->Layer != priv->eimg[i].event->Layer) {
-			fix_collisions(last, priv->eimg + i - last);
-			last = priv->eimg + i;
-		}
-	if (cnt > 0)
-		fix_collisions(last, priv->eimg + cnt - last);
+    // call fix_collisions for each group of events with the same layer
+    last = priv->eimg;
+    for (i = 1; i < cnt; ++i)
+        if (last->event->Layer != priv->eimg[i].event->Layer) {
+            fix_collisions(priv, last, priv->eimg + i - last);
+            last = priv->eimg + i;
+        }
+    if (cnt > 0)
+        fix_collisions(priv, last, priv->eimg + cnt - last);
 
-	// concat lists
-	tail = &ass_renderer->images_root;
-	for (i = 0; i < cnt; ++i) {
-		ass_image_t* cur = priv->eimg[i].imgs;
-		while (cur) {
-			*tail = cur;
-			tail = &cur->next;
-			cur = cur->next;
-		}
-	}
+    // concat lists
+    tail = &priv->images_root;
+    for (i = 0; i < cnt; ++i) {
+        ASS_Image *cur = priv->eimg[i].imgs;
+        while (cur) {
+            *tail = cur;
+            tail = &cur->next;
+            cur = cur->next;
+        }
+    }
 
-	if (detect_change)
-		*detect_change = ass_detect_change(priv);
+    if (detect_change)
+        *detect_change = ass_detect_change(priv);
 
-	// free the previous image list
-	ass_free_images(priv->prev_images_root);
-	priv->prev_images_root = 0;
+    // free the previous image list
+    ass_free_images(priv->prev_images_root);
+    priv->prev_images_root = 0;
 
-	return ass_renderer->images_root;
+    return priv->images_root;
 }
-

Added: trunk/libass/ass_render.h
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ trunk/libass/ass_render.h	Fri Jan  8 19:35:44 2010	(r30242)
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov at gmail.com>
+ * Copyright (C) 2009 Grigori Goronzy <greg at geekmind.org>
+ *
+ * This file is part of libass.
+ *
+ * libass is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * libass is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with libass; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef LIBASS_RENDER_H
+#define LIBASS_RENDER_H
+
+#include <inttypes.h>
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include FT_STROKER_H
+#include FT_GLYPH_H
+#include FT_SYNTHESIS_H
+
+#include "ass.h"
+#include "ass_font.h"
+#include "ass_bitmap.h"
+#include "ass_cache.h"
+#include "ass_utils.h"
+#include "ass_fontconfig.h"
+#include "ass_library.h"
+#include "ass_drawing.h"
+
+typedef struct {
+    double xMin;
+    double xMax;
+    double yMin;
+    double yMax;
+} DBBox;
+
+typedef struct {
+    double x;
+    double y;
+} DVector;
+
+typedef struct free_list {
+    void *object;
+    struct free_list *next;
+} FreeList;
+
+typedef struct {
+    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.
+    double storage_aspect;      // pixel ratio of the source image
+    ASS_Hinting hinting;
+
+    char *default_font;
+    char *default_family;
+} ASS_Settings;
+
+// a rendered event
+typedef struct {
+    ASS_Image *imgs;
+    int top, height, left, width;
+    int detect_collisions;
+    int shift_direction;
+    ASS_Event *event;
+} EventImages;
+
+typedef enum {
+    EF_NONE = 0,
+    EF_KARAOKE,
+    EF_KARAOKE_KF,
+    EF_KARAOKE_KO
+} Effect;
+
+// describes a glyph
+// GlyphInfo and TextInfo are used for text centering and word-wrapping operations
+typedef struct {
+    unsigned symbol;
+    unsigned skip;              // skip glyph when layouting text
+    FT_Glyph glyph;
+    FT_Glyph outline_glyph;
+    Bitmap *bm;                 // glyph bitmap
+    Bitmap *bm_o;               // outline bitmap
+    Bitmap *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 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 be;                     // blur edges
+    double blur;                // gaussian blur
+    double shadow_x;
+    double shadow_y;
+    double frx, fry, frz;       // rotation
+    double fax, fay;            // text shearing
+
+    BitmapHashKey hash_key;
+} GlyphInfo;
+
+typedef struct {
+    double asc, desc;
+} LineInfo;
+
+typedef struct {
+    GlyphInfo *glyphs;
+    int length;
+    LineInfo *lines;
+    int n_lines;
+    double height;
+    int max_glyphs;
+    int max_lines;
+} TextInfo;
+
+// Renderer state.
+// Values like current font face, color, screen position, clipping and so on are stored here.
+typedef struct {
+    ASS_Event *event;
+    ASS_Style *style;
+
+    ASS_Font *font;
+    char *font_path;
+    double font_size;
+    int flags;                  // decoration flags (underline/strike-through)
+
+    FT_Stroker stroker;
+    int alignment;              // alignment overrides go here; if zero, style value will be used
+    double frx, fry, frz;
+    double fax, fay;            // text shearing
+    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_x;            // outline width
+    double border_y;
+    uint32_t c[4];              // colors(Primary, Secondary, so on) in RGBA
+    int clip_x0, clip_y0, clip_x1, clip_y1;
+    char clip_mode;             // 1 = iclip
+    char detect_collisions;
+    uint32_t fade;              // alpha from \fad
+    char be;                    // blur edges
+    double blur;                // gaussian blur
+    double shadow_x;
+    double shadow_y;
+    int drawing_mode;           // not implemented; when != 0 text is discarded, except for style override tags
+    ASS_Drawing *drawing;       // current drawing
+    ASS_Drawing *clip_drawing;  // clip vector
+    int clip_drawing_mode;      // 0 = regular clip, 1 = inverse clip
+
+    Effect 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;
+    int wrap_style;
+} RenderContext;
+
+typedef struct {
+    Hashmap *font_cache;
+    Hashmap *glyph_cache;
+    Hashmap *bitmap_cache;
+    Hashmap *composite_cache;
+    size_t glyph_max;
+    size_t bitmap_max_size;
+} CacheStore;
+
+struct ass_renderer {
+    ASS_Library *library;
+    FT_Library ftlibrary;
+    FCInstance *fontconfig_priv;
+    ASS_Settings settings;
+    int render_id;
+    ASS_SynthPriv *synth_priv;
+
+    ASS_Image *images_root;     // rendering result is stored here
+    ASS_Image *prev_images_root;
+
+    EventImages *eimg;          // temporary buffer for sorting rendered events
+    int eimg_size;              // allocated buffer size
+
+    // frame-global data
+    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 *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;
+
+    RenderContext state;
+    TextInfo text_info;
+    CacheStore cache;
+
+    FreeList *free_head;
+    FreeList *free_tail;
+};
+
+typedef struct render_priv {
+    int top, height, left, width;
+    int render_id;
+} RenderPriv;
+
+typedef struct {
+    int x0;
+    int y0;
+    int x1;
+    int y1;
+} Rect;
+
+typedef struct {
+    int a, b;                   // top and height
+    int ha, hb;                 // left and width
+} Segment;
+
+void reset_render_context(ASS_Renderer *render_priv);
+
+#endif /* LIBASS_RENDER_H */

Added: trunk/libass/ass_strtod.c
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ trunk/libass/ass_strtod.c	Fri Jan  8 19:35:44 2010	(r30242)
@@ -0,0 +1,247 @@
+/*
+ * Copyright (c) 1988-1993 The Regents of the University of California.
+ * Copyright (c) 1994 Sun Microsystems, Inc.
+ *
+ * Permission to use, copy, modify, and distribute this
+ * software and its documentation for any purpose and without
+ * fee is hereby granted, provided that the above copyright
+ * notice appear in all copies.  The University of California
+ * makes no representations about the suitability of this
+ * software for any purpose.  It is provided "as is" without
+ * express or implied warranty.
+ *
+ */
+
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+
+static int maxExponent = 511;   /* Largest possible base 10 exponent.  Any
+                                 * exponent larger than this will already
+                                 * produce underflow or overflow, so there's
+                                 * no need to worry about additional digits.
+                                 */
+
+static double powersOf10[] = {  /* Table giving binary powers of 10.  Entry */
+    10.,                        /* is 10^2^i.  Used to convert decimal */
+    100.,                       /* exponents into floating-point numbers. */
+    1.0e4,
+    1.0e8,
+    1.0e16,
+    1.0e32,
+    1.0e64,
+    1.0e128,
+    1.0e256
+};
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * strtod --
+ *
+ * This procedure converts a floating-point number from an ASCII
+ * decimal representation to internal double-precision format.
+ *
+ * Results:
+ * The return value is the double-precision floating-point
+ * representation of the characters in string.  If endPtr isn't
+ * NULL, then *endPtr is filled in with the address of the
+ * next character after the last one that was part of the
+ * floating-point number.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+double
+ass_strtod(string, endPtr)
+    const char *string;     /* A decimal ASCII floating-point number,
+                             * optionally preceded by white space.
+                             * Must have form "-I.FE-X", where I is the
+                             * integer part of the mantissa, F is the
+                             * fractional part of the mantissa, and X
+                             * is the exponent.  Either of the signs
+                             * may be "+", "-", or omitted.  Either I
+                             * or F may be omitted, or both.  The decimal
+                             * point isn't necessary unless F is present.
+                             * The "E" may actually be an "e".  E and X
+                             * may both be omitted (but not just one).
+                             */
+    char **endPtr;          /* If non-NULL, store terminating character's
+                             * address here. */
+{
+    int sign, expSign = 0;
+    double fraction, dblExp, *d;
+    register const char *p;
+    register int c;
+    int exp = 0;            /* Exponent read from "EX" field. */
+    int fracExp = 0;        /* Exponent that derives from the fractional
+                             * part.  Under normal circumstatnces, it is
+                             * the negative of the number of digits in F.
+                             * However, if I is very long, the last digits
+                             * of I get dropped (otherwise a long I with a
+                             * large negative exponent could cause an
+                             * unnecessary overflow on I alone).  In this
+                             * case, fracExp is incremented one for each
+                             * dropped digit. */
+    int mantSize;       /* Number of digits in mantissa. */
+    int decPt;          /* Number of mantissa digits BEFORE decimal
+                         * point. */
+    const char *pExp;       /* Temporarily holds location of exponent
+                             * in string. */
+
+    /*
+     * Strip off leading blanks and check for a sign.
+     */
+
+    p = string;
+    while (isspace(*p)) {
+        p += 1;
+    }
+    if (*p == '-') {
+        sign = 1;
+        p += 1;
+    } else {
+        if (*p == '+') {
+            p += 1;
+        }
+        sign = 0;
+    }
+
+    /*
+     * Count the number of digits in the mantissa (including the decimal
+     * point), and also locate the decimal point.
+     */
+
+    decPt = -1;
+    for (mantSize = 0; ; mantSize += 1)
+    {
+        c = *p;
+        if (!isdigit(c)) {
+            if ((c != '.') || (decPt >= 0)) {
+                break;
+            }
+            decPt = mantSize;
+        }
+        p += 1;
+    }
+
+    /*
+     * Now suck up the digits in the mantissa.  Use two integers to
+     * collect 9 digits each (this is faster than using floating-point).
+     * If the mantissa has more than 18 digits, ignore the extras, since
+     * they can't affect the value anyway.
+     */
+
+    pExp  = p;
+    p -= mantSize;
+    if (decPt < 0) {
+        decPt = mantSize;
+    } else {
+        mantSize -= 1;      /* One of the digits was the point. */
+    }
+    if (mantSize > 18) {
+        fracExp = decPt - 18;
+        mantSize = 18;
+    } else {
+        fracExp = decPt - mantSize;
+    }
+    if (mantSize == 0) {
+        fraction = 0.0;
+        p = string;
+        goto done;
+    } else {
+        int frac1, frac2;
+        frac1 = 0;
+        for ( ; mantSize > 9; mantSize -= 1)
+        {
+            c = *p;
+            p += 1;
+            if (c == '.') {
+                c = *p;
+                p += 1;
+            }
+            frac1 = 10*frac1 + (c - '0');
+        }
+        frac2 = 0;
+        for (; mantSize > 0; mantSize -= 1)
+        {
+            c = *p;
+            p += 1;
+            if (c == '.') {
+                c = *p;
+                p += 1;
+            }
+            frac2 = 10*frac2 + (c - '0');
+        }
+        fraction = (1.0e9 * frac1) + frac2;
+    }
+
+    /*
+     * Skim off the exponent.
+     */
+
+    p = pExp;
+    if ((*p == 'E') || (*p == 'e')) {
+        p += 1;
+        if (*p == '-') {
+            expSign = 1;
+            p += 1;
+        } else {
+            if (*p == '+') {
+                p += 1;
+            }
+            expSign = 0;
+        }
+        while (isdigit(*p)) {
+            exp = exp * 10 + (*p - '0');
+            p += 1;
+        }
+    }
+    if (expSign) {
+        exp = fracExp - exp;
+    } else {
+        exp = fracExp + exp;
+    }
+
+    /*
+     * Generate a floating-point number that represents the exponent.
+     * Do this by processing the exponent one bit at a time to combine
+     * many powers of 2 of 10. Then combine the exponent with the
+     * fraction.
+     */
+
+    if (exp < 0) {
+        expSign = 1;
+        exp = -exp;
+    } else {
+        expSign = 0;
+    }
+    if (exp > maxExponent) {
+        exp = maxExponent;
+        errno = ERANGE;
+    }
+    dblExp = 1.0;
+    for (d = powersOf10; exp != 0; exp >>= 1, d += 1) {
+        if (exp & 01) {
+            dblExp *= *d;
+        }
+    }
+    if (expSign) {
+        fraction /= dblExp;
+    } else {
+        fraction *= dblExp;
+    }
+
+done:
+    if (endPtr != NULL) {
+        *endPtr = (char *) p;
+    }
+
+    if (sign) {
+        return -fraction;
+    }
+    return fraction;
+}

Modified: trunk/libass/ass_types.h
==============================================================================
--- trunk/libass/ass_types.h	Fri Jan  8 19:19:10 2010	(r30241)
+++ trunk/libass/ass_types.h	Fri Jan  8 19:35:44 2010	(r30242)
@@ -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 at gmail.com>
  *
@@ -32,88 +30,96 @@
 #define HALIGN_CENTER 2
 #define HALIGN_RIGHT 3
 
-/// ass Style: line
-typedef struct ass_style_s {
-	char* Name;
-	char* FontName;
-	double FontSize;
-	uint32_t PrimaryColour;
-	uint32_t SecondaryColour;
-	uint32_t OutlineColour;
-	uint32_t BackColour;
-	int Bold;
-	int Italic;
-	int Underline;
-	int StrikeOut;
-	double ScaleX;
-	double ScaleY;
-	double Spacing;
-	int Angle;
-	int BorderStyle;
-	double Outline;
-	double Shadow;
-	int Alignment;
-	int MarginL;
-	int MarginR;
-	int MarginV;
-//        int AlphaLevel;
-	int Encoding;
-	int treat_fontname_as_pattern;
-} ass_style_t;
-
-typedef struct render_priv_s render_priv_t;
-
-/// ass_event_t corresponds to a single Dialogue line
-/// Text is stored as-is, style overrides will be parsed later
-typedef struct ass_event_s {
-	long long Start; // ms
-	long long Duration; // ms
-
-	int ReadOrder;
-	int Layer;
-	int Style;
-	char* Name;
-	int MarginL;
-	int MarginR;
-	int MarginV;
-	char* Effect;
-	char* Text;
+/* Opaque objects internally used by libass.  Contents are private. */
+typedef struct ass_renderer ASS_Renderer;
+typedef struct render_priv ASS_RenderPriv;
+typedef struct parser_priv ASS_ParserPriv;
+typedef struct ass_library ASS_Library;
 
-	render_priv_t* render_priv;
-} ass_event_t;
+/* ASS Style: line */
+typedef struct ass_style {
+    char *Name;
+    char *FontName;
+    double FontSize;
+    uint32_t PrimaryColour;
+    uint32_t SecondaryColour;
+    uint32_t OutlineColour;
+    uint32_t BackColour;
+    int Bold;
+    int Italic;
+    int Underline;
+    int StrikeOut;
+    double ScaleX;
+    double ScaleY;
+    double Spacing;
+    int Angle;
+    int BorderStyle;
+    double Outline;
+    double Shadow;
+    int Alignment;
+    int MarginL;
+    int MarginR;
+    int MarginV;
+    int Encoding;
+    int treat_fontname_as_pattern;
+} ASS_Style;
 
-typedef struct parser_priv_s parser_priv_t;
+/*
+ * ASS_Event corresponds to a single Dialogue line;
+ * text is stored as-is, style overrides will be parsed later.
+ */
+typedef struct ass_event {
+    long long Start;            // ms
+    long long Duration;         // ms
 
-typedef struct ass_library_s ass_library_t;
+    int ReadOrder;
+    int Layer;
+    int Style;
+    char *Name;
+    int MarginL;
+    int MarginR;
+    int MarginV;
+    char *Effect;
+    char *Text;
 
-/// ass track represent either an external script or a matroska subtitle stream (no real difference between them)
-/// it can be used in rendering after the headers are parsed (i.e. events format line read)
-typedef struct ass_track_s {
-	int n_styles; // amount used
-	int max_styles; // amount allocated
-	int n_events;
-	int max_events;
-	ass_style_t* styles; // array of styles, max_styles length, n_styles used
-	ass_event_t* events; // the same as styles
+    ASS_RenderPriv *render_priv;
+} ASS_Event;
 
-	char* style_format; // style format line (everything after "Format: ")
-	char* event_format; // event format line
+/*
+ * ass track represent either an external script or a matroska subtitle stream
+ * (no real difference between them); it can be used in rendering after the
+ * headers are parsed (i.e. events format line read).
+ */
+typedef struct ass_track {
+    int n_styles;           // amount used
+    int max_styles;         // amount allocated
+    int n_events;
+    int max_events;
+    ASS_Style *styles;    // array of styles, max_styles length, n_styles used
+    ASS_Event *events;    // the same as styles
 
-	enum {TRACK_TYPE_UNKNOWN = 0, TRACK_TYPE_ASS, TRACK_TYPE_SSA} track_type;
+    char *style_format;     // style format line (everything after "Format: ")
+    char *event_format;     // event format line
 
-	// script header fields
-	int PlayResX;
-	int PlayResY;
-	double Timer;
-	int WrapStyle;
-	char ScaledBorderAndShadow;
+    enum {
+        TRACK_TYPE_UNKNOWN = 0,
+        TRACK_TYPE_ASS,
+        TRACK_TYPE_SSA
+    } track_type;
 
+    // Script header fields
+    int PlayResX;
+    int PlayResY;
+    double Timer;
+    int WrapStyle;
+    int ScaledBorderAndShadow;
+    int Kerning;
 
-	int default_style; // index of default style
-	char* name; // file name in case of external subs, 0 for streams
+    int default_style;      // index of default style
+    char *name;             // file name in case of external subs, 0 for streams
 
-	ass_library_t* library;
-	parser_priv_t* parser_priv;
-} ass_track_t;
+    ASS_Library *library;
+    ASS_ParserPriv *parser_priv;
+} ASS_Track;
 
 #endif /* LIBASS_TYPES_H */

Modified: trunk/libass/ass_utils.c
==============================================================================
--- trunk/libass/ass_utils.c	Fri Jan  8 19:19:10 2010	(r30241)
+++ trunk/libass/ass_utils.c	Fri Jan  8 19:35:44 2010	(r30242)
@@ -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 at gmail.com>
  *
@@ -23,113 +21,187 @@
 #include "config.h"
 
 #include <stdlib.h>
+#include <stdio.h>
 #include <inttypes.h>
 #include <ft2build.h>
 #include FT_GLYPH_H
 
-#include "mputils.h"
+#include "ass_library.h"
+#include "ass.h"
 #include "ass_utils.h"
 
-int mystrtoi(char** p, int* res)
+int mystrtoi(char **p, int *res)
 {
-	// NOTE: base argument is ignored, but not used in libass anyway
-	double temp_res;
-	char* start = *p;
-	temp_res = strtod(*p, p);
-	*res = (int) (temp_res + 0.5);
-	if (*p != start) return 1;
-	else return 0;
+    double temp_res;
+    char *start = *p;
+    temp_res = ass_strtod(*p, p);
+    *res = (int) (temp_res + (temp_res > 0 ? 0.5 : -0.5));
+    if (*p != start)
+        return 1;
+    else
+        return 0;
 }
 
-int mystrtoll(char** p, long long* res)
+int mystrtoll(char **p, long long *res)
 {
-	double temp_res;
-	char* start = *p;
-	temp_res = strtod(*p, p);
-	*res = (long long) (temp_res + 0.5);
-	if (*p != start) return 1;
-	else return 0;
+    double temp_res;
+    char *start = *p;
+    temp_res = ass_strtod(*p, p);
+    *res = (int) (temp_res + (temp_res > 0 ? 0.5 : -0.5));
+    if (*p != start)
+        return 1;
+    else
+        return 0;
 }
 
-int mystrtou32(char** p, int base, uint32_t* res)
+int mystrtou32(char **p, int base, uint32_t *res)
 {
-	char* start = *p;
-	*res = strtoll(*p, p, base);
-	if (*p != start) return 1;
-	else return 0;
+    char *start = *p;
+    *res = strtoll(*p, p, base);
+    if (*p != start)
+        return 1;
+    else
+        return 0;
 }
 
-int mystrtod(char** p, double* res)
+int mystrtod(char **p, double *res)
 {
-	char* start = *p;
-	*res = strtod(*p, p);
-	if (*p != start) return 1;
-	else return 0;
+    char *start = *p;
+    *res = ass_strtod(*p, p);
+    if (*p != start)
+        return 1;
+    else
+        return 0;
 }
 
-int strtocolor(char** q, uint32_t* res)
+int strtocolor(ASS_Library *library, char **q, uint32_t *res, int hex)
 {
-	uint32_t color = 0;
-	int result;
-	char* p = *q;
+    uint32_t color = 0;
+    int result;
+    char *p = *q;
+    int base = hex ? 16 : 10;
 
-	if (*p == '&') ++p;
-	else mp_msg(MSGT_ASS, MSGL_DBG2, "suspicious color format: \"%s\"\n", p);
+    if (*p == '&')
+        ++p;
+    else
+        ass_msg(library, MSGL_DBG2, "suspicious color format: \"%s\"\n", p);
 
-	if (*p == 'H' || *p == 'h') {
-		++p;
-		result = mystrtou32(&p, 16, &color);
-	} else {
-		result = mystrtou32(&p, 0, &color);
-	}
+    if (*p == 'H' || *p == 'h') {
+        ++p;
+        result = mystrtou32(&p, 16, &color);
+    } else {
+        result = mystrtou32(&p, base, &color);
+    }
 
-	{
-		unsigned char* tmp = (unsigned char*)(&color);
-		unsigned char b;
-		b = tmp[0]; tmp[0] = tmp[3]; tmp[3] = b;
-		b = tmp[1]; tmp[1] = tmp[2]; tmp[2] = b;
-	}
-	if (*p == '&') ++p;
-	*q = p;
+    {
+        unsigned char *tmp = (unsigned char *) (&color);
+        unsigned char b;
+        b = tmp[0];
+        tmp[0] = tmp[3];
+        tmp[3] = b;
+        b = tmp[1];
+        tmp[1] = tmp[2];
+        tmp[2] = b;
+    }
+    if (*p == '&')
+        ++p;
+    *q = p;
 
-	*res = color;
-	return result;
+    *res = color;
+    return result;
 }
 
 // Return a boolean value for a string
-char parse_bool(char* str) {
-	while (*str == ' ' || *str == '\t')
-		str++;
-	if (!strncasecmp(str, "yes", 3))
-		return 1;
-	else if (strtol(str, NULL, 10) > 0)
-		return 1;
-	return 0;
+char parse_bool(char *str)
+{
+    while (*str == ' ' || *str == '\t')
+        str++;
+    if (!strncasecmp(str, "yes", 3))
+        return 1;
+    else if (strtol(str, NULL, 10) > 0)
+        return 1;
+    return 0;
 }
 
-#if 0
-static void sprint_tag(uint32_t tag, char* dst)
+void ass_msg(ASS_Library *priv, int lvl, char *fmt, ...)
 {
-	dst[0] = (tag >> 24) & 0xFF;
-	dst[1] = (tag >> 16) & 0xFF;
-	dst[2] = (tag >> 8) & 0xFF;
-	dst[3] = tag & 0xFF;
-	dst[4] = 0;
+    va_list va;
+    va_start(va, fmt);
+    priv->msg_callback(lvl, fmt, va, priv->msg_callback_data);
+    va_end(va);
 }
 
-void dump_glyph(FT_Glyph g)
+unsigned ass_utf8_get_char(char **str)
 {
-	char tag[5];
-	int i;
-	FT_OutlineGlyph og = (FT_OutlineGlyph)g;
-	FT_Outline* o = &(og->outline);
-	sprint_tag(g->format, tag);
-	printf("glyph: %p \n", g);
-	printf("format: %s \n", tag);
-	printf("outline: %p \n", o);
-	printf("contours: %d, points: %d, points ptr: %p \n", o->n_contours, o->n_points, o->points);
-	for (i = 0; i < o->n_points; ++i) {
-		printf("  point %f, %f \n", d6_to_double(o->points[i].x), d6_to_double(o->points[i].y));
-	}
+    uint8_t *strp = (uint8_t *) * str;
+    unsigned c = *strp++;
+    unsigned mask = 0x80;
+    int len = -1;
+    while (c & mask) {
+        mask >>= 1;
+        len++;
+    }
+    if (len <= 0 || len > 4)
+        goto no_utf8;
+    c &= mask - 1;
+    while ((*strp & 0xc0) == 0x80) {
+        if (len-- <= 0)
+            goto no_utf8;
+        c = (c << 6) | (*strp++ & 0x3f);
+    }
+    if (len)
+        goto no_utf8;
+    *str = (char *) strp;
+    return c;
+
+  no_utf8:
+    strp = (uint8_t *) * str;
+    c = *strp++;
+    *str = (char *) strp;
+    return c;
+}
+
+#ifdef CONFIG_ENCA
+void *ass_guess_buffer_cp(ASS_Library *library, unsigned char *buffer,
+                          int buflen, char *preferred_language,
+                          char *fallback)
+{
+    const char **languages;
+    size_t langcnt;
+    EncaAnalyser analyser;
+    EncaEncoding encoding;
+    char *detected_sub_cp = NULL;
+    int i;
+
+    languages = enca_get_languages(&langcnt);
+    ass_msg(library, MSGL_V, "ENCA supported languages");
+    for (i = 0; i < langcnt; i++) {
+        ass_msg(library, MSGL_V, "lang %s", languages[i]);
+    }
+
+    for (i = 0; i < langcnt; i++) {
+        const char *tmp;
+
+        if (strcasecmp(languages[i], preferred_language) != 0)
+            continue;
+        analyser = enca_analyser_alloc(languages[i]);
+        encoding = enca_analyse_const(analyser, buffer, buflen);
+        tmp = enca_charset_name(encoding.charset, ENCA_NAME_STYLE_ICONV);
+        if (tmp && encoding.charset != ENCA_CS_UNKNOWN) {
+            detected_sub_cp = strdup(tmp);
+            ass_msg(library, MSGL_INFO, "ENCA detected charset: %s", tmp);
+        }
+        enca_analyser_free(analyser);
+    }
+
+    free(languages);
+
+    if (!detected_sub_cp) {
+        detected_sub_cp = strdup(fallback);
+        ass_msg(library, MSGL_INFO,
+               "ENCA detection failed: fallback to %s", fallback);
+    }
+
+    return detected_sub_cp;
 }
 #endif

Modified: trunk/libass/ass_utils.h
==============================================================================
--- trunk/libass/ass_utils.h	Fri Jan  8 19:19:10 2010	(r30241)
+++ trunk/libass/ass_utils.h	Fri Jan  8 19:35:44 2010	(r30242)
@@ -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 at gmail.com>
  *
@@ -23,44 +21,127 @@
 #ifndef LIBASS_UTILS_H
 #define LIBASS_UTILS_H
 
+#include <stdio.h>
+#include <stdarg.h>
 #include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
 
-int mystrtoi(char** p, int* res);
-int mystrtoll(char** p, long long* res);
-int mystrtou32(char** p, int base, uint32_t* res);
-int mystrtod(char** p, double* res);
-int strtocolor(char** q, uint32_t* res);
-char parse_bool(char* str);
+#ifdef CONFIG_ENCA
+#include <enca.h>
+#endif
 
-static inline int d6_to_int(int x) {
-	return (x + 32) >> 6;
+#include "ass.h"
+
+#define MSGL_FATAL 0
+#define MSGL_ERR 1
+#define MSGL_WARN 2
+#define MSGL_INFO 4
+#define MSGL_V 6
+#define MSGL_DBG2 7
+
+#define FFMAX(a,b) ((a) > (b) ? (a) : (b))
+#define FFMIN(a,b) ((a) > (b) ? (b) : (a))
+#define FFMINMAX(c,a,b) FFMIN(FFMAX(c, a), b)
+
+int mystrtoi(char **p, int *res);
+int mystrtoll(char **p, long long *res);
+int mystrtou32(char **p, int base, uint32_t *res);
+int mystrtod(char **p, double *res);
+int strtocolor(ASS_Library *library, char **q, uint32_t *res, int hex);
+char parse_bool(char *str);
+unsigned ass_utf8_get_char(char **str);
+void ass_msg(ASS_Library *priv, int lvl, char *fmt, ...);
+#ifdef CONFIG_ENCA
+void *ass_guess_buffer_cp(ASS_Library *library, unsigned char *buffer,
+                          int buflen, char *preferred_language,
+                          char *fallback);
+#endif
+
+/* defined in ass_strtod.c */
+double ass_strtod(const char *string, char **endPtr);
+
+static inline int d6_to_int(int x)
+{
+    return (x + 32) >> 6;
 }
-static inline int d16_to_int(int x) {
-	return (x + 32768) >> 16;
+static inline int d16_to_int(int x)
+{
+    return (x + 32768) >> 16;
 }
-static inline int int_to_d6(int x) {
-	return x << 6;
+static inline int int_to_d6(int x)
+{
+    return x << 6;
 }
-static inline int int_to_d16(int x) {
-	return x << 16;
+static inline int int_to_d16(int x)
+{
+    return x << 16;
 }
-static inline int d16_to_d6(int x) {
-	return (x + 512) >> 10;
+static inline int d16_to_d6(int x)
+{
+    return (x + 512) >> 10;
 }
-static inline int d6_to_d16(int x) {
-	return x << 10;
+static inline int d6_to_d16(int x)
+{
+    return x << 10;
 }
-static inline double d6_to_double(int x) {
-	return x / 64.;
+static inline double d6_to_double(int x)
+{
+    return x / 64.;
 }
-static inline int double_to_d6(double x) {
-	return (int)(x * 64);
+static inline int double_to_d6(double x)
+{
+    return (int) (x * 64);
 }
-static inline double d16_to_double(int x) {
-	return ((double)x) / 0x10000;
+static inline double d16_to_double(int x)
+{
+    return ((double) x) / 0x10000;
 }
-static inline int double_to_d16(double x) {
-	return (int)(x * 0x10000);
+static inline int double_to_d16(double x)
+{
+    return (int) (x * 0x10000);
+}
+static inline double d22_to_double(int x)
+{
+    return ((double) x) / 0x400000;
+}
+static inline int double_to_d22(double x)
+{
+    return (int) (x * 0x400000);
 }
 
-#endif /* LIBASS_UTILS_H */
+// Calculate cache key for a rotational angle in degrees
+static inline int rot_key(double a)
+{
+    const int m = double_to_d22(360.0);
+    return double_to_d22(a) % m;
+}
+
+#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)
+{
+    unsigned char *s = (unsigned char *) str;
+    while (*s) {
+        hval ^= (unsigned) *s++;
+        hval +=
+            (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) +
+            (hval << 24);
+    }
+    return hval;
+}
+
+#endif                          /* LIBASS_UTILS_H */


More information about the MPlayer-cvslog mailing list