[SCM] LibASS packaging branch, master, updated. debian/0.9.7-2-5-gee7a496

xtophe-guest at users.alioth.debian.org xtophe-guest at users.alioth.debian.org
Mon Oct 19 10:19:11 UTC 2009


The following commit has been merged in the master branch:
commit c1cec6fdcd4da9a4095f67c142096e45713aac2b
Author: Christophe Mutricy <xtophe at videolan.org>
Date:   Mon Oct 19 12:10:11 2009 +0200

    Imported Upstream version 0.9.8

diff --git a/Changelog b/Changelog
index aeb100d..ca1ba53 100644
--- a/Changelog
+++ b/Changelog
@@ -1,3 +1,18 @@
+libass (0.9.8)
+ * Support \q override tag
+ * Support wrap style 1 (i.e. wrap, but do not equalize line lengths)
+ * Support border style 3 (opaque box)
+ * Use the event bounding box (instead of vertical position and height) for
+   collision detection
+ * Embold glyphs if no bold variant is available, but was requested
+ * Modify \fax to be similar to VSFilter
+ * Trim spaces after line wrapping
+ * Fix border/shadow overlap combining in some cases
+ * Disable kerning by default.  Use "Kerning=yes" style override or
+   "Kerning: yes" in [Script Info] to enable it
+ * Slight bitmap handling optimizations
+ * Various bugfixes
+
 libass (0.9.7)
  * Build system fixes
  * Fixed cache lookup and overload problems
diff --git a/configure b/configure
index 32fe64d..4e096d0 100755
--- a/configure
+++ b/configure
@@ -1,6 +1,6 @@
 #! /bin/sh
 # Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.61 for libass 0.9.7.
+# Generated by GNU Autoconf 2.61 for libass 0.9.8.
 #
 # Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
 # 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc.
@@ -722,8 +722,8 @@ SHELL=${CONFIG_SHELL-/bin/sh}
 # Identity of this package.
 PACKAGE_NAME='libass'
 PACKAGE_TARNAME='libass'
-PACKAGE_VERSION='0.9.7'
-PACKAGE_STRING='libass 0.9.7'
+PACKAGE_VERSION='0.9.8'
+PACKAGE_STRING='libass 0.9.8'
 PACKAGE_BUGREPORT=''
 
 # Factoring default headers for most tests.
@@ -1402,7 +1402,7 @@ if test "$ac_init_help" = "long"; then
   # Omit some internal or obsolete options to make the list less imposing.
   # This message is too long to be a string in the A/UX 3.1 sh.
   cat <<_ACEOF
-\`configure' configures libass 0.9.7 to adapt to many kinds of systems.
+\`configure' configures libass 0.9.8 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -1472,7 +1472,7 @@ fi
 
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of libass 0.9.7:";;
+     short | recursive ) echo "Configuration of libass 0.9.8:";;
    esac
   cat <<\_ACEOF
 
@@ -1486,7 +1486,7 @@ Optional Features:
   --disable-dependency-tracking  speeds up one-time build
   --enable-dependency-tracking   do not reject slow dependency extractors
   --disable-libtool-lock  avoid locking (might break parallel builds)
-  --disable-png           disable png support [default=check]
+  --enable-png            enable png (test program) [default=no]
   --disable-enca          disable enca (charset autodetect) support
                           [default=check]
   --disable-fontconfig    disable fontconfig support [default=check]
@@ -1587,7 +1587,7 @@ fi
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-libass configure 0.9.7
+libass configure 0.9.8
 generated by GNU Autoconf 2.61
 
 Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
@@ -1601,7 +1601,7 @@ cat >config.log <<_ACEOF
 This file contains any messages produced by compilers while
 running configure, to aid debugging if configure makes a mistake.
 
-It was created by libass $as_me 0.9.7, which was
+It was created by libass $as_me 0.9.8, which was
 generated by GNU Autoconf 2.61.  Invocation command line was
 
   $ $0 $@
@@ -2291,7 +2291,7 @@ fi
 
 # Define the identity of the package.
  PACKAGE='libass'
- VERSION='0.9.7'
+ VERSION='0.9.8'
 
 
 cat >>confdefs.h <<_ACEOF
@@ -16009,7 +16009,8 @@ _ACEOF
 fi
 fi
 
-if test x$enable_png != xno; then
+libpng=false
+if test x$enable_png = xyes; then
 
 pkg_failed=no
 { echo "$as_me:$LINENO: checking for LIBPNG" >&5
@@ -16069,11 +16070,51 @@ fi
 	# Put the nasty error message in config.log where it belongs
 	echo "$LIBPNG_PKG_ERRORS" >&5
 
-	{ echo "$as_me:$LINENO: result: no" >&5
-echo "${ECHO_T}no" >&6; }
-                libpng=false
+	{ { echo "$as_me:$LINENO: error: Package requirements (libpng >= 1.2.0) were not met:
+
+$LIBPNG_PKG_ERRORS
+
+Consider adjusting the PKG_CONFIG_PATH environment variable if you
+installed software in a non-standard prefix.
+
+Alternatively, you may set the environment variables LIBPNG_CFLAGS
+and LIBPNG_LIBS to avoid the need to call pkg-config.
+See the pkg-config man page for more details.
+" >&5
+echo "$as_me: error: Package requirements (libpng >= 1.2.0) were not met:
+
+$LIBPNG_PKG_ERRORS
+
+Consider adjusting the PKG_CONFIG_PATH environment variable if you
+installed software in a non-standard prefix.
+
+Alternatively, you may set the environment variables LIBPNG_CFLAGS
+and LIBPNG_LIBS to avoid the need to call pkg-config.
+See the pkg-config man page for more details.
+" >&2;}
+   { (exit 1); exit 1; }; }
 elif test $pkg_failed = untried; then
-	libpng=false
+	{ { echo "$as_me:$LINENO: error: The pkg-config script could not be found or is too old.  Make sure it
+is in your PATH or set the PKG_CONFIG environment variable to the full
+path to pkg-config.
+
+Alternatively, you may set the environment variables LIBPNG_CFLAGS
+and LIBPNG_LIBS to avoid the need to call pkg-config.
+See the pkg-config man page for more details.
+
+To get pkg-config, see <http://pkg-config.freedesktop.org/>.
+See \`config.log' for more details." >&5
+echo "$as_me: error: The pkg-config script could not be found or is too old.  Make sure it
+is in your PATH or set the PKG_CONFIG environment variable to the full
+path to pkg-config.
+
+Alternatively, you may set the environment variables LIBPNG_CFLAGS
+and LIBPNG_LIBS to avoid the need to call pkg-config.
+See the pkg-config man page for more details.
+
+To get pkg-config, see <http://pkg-config.freedesktop.org/>.
+See \`config.log' for more details." >&2;}
+   { (exit 1); exit 1; }; }
 else
 	LIBPNG_CFLAGS=$pkg_cv_LIBPNG_CFLAGS
 	LIBPNG_LIBS=$pkg_cv_LIBPNG_LIBS
@@ -16633,7 +16674,7 @@ exec 6>&1
 # report actual input values of CONFIG_FILES etc. instead of their
 # values after options handling.
 ac_log="
-This file was extended by libass $as_me 0.9.7, which was
+This file was extended by libass $as_me 0.9.8, which was
 generated by GNU Autoconf 2.61.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -16686,7 +16727,7 @@ Report bugs to <bug-autoconf at gnu.org>."
 _ACEOF
 cat >>$CONFIG_STATUS <<_ACEOF
 ac_cs_version="\\
-libass config.status 0.9.7
+libass config.status 0.9.8
 configured by $0, generated by GNU Autoconf 2.61,
   with options \\"`echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`\\"
 
diff --git a/configure.ac b/configure.ac
index 03aa72e..7698492 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,4 +1,4 @@
-AC_INIT(libass, 0.9.7)
+AC_INIT(libass, 0.9.8)
 AM_INIT_AUTOMAKE
 AC_CONFIG_MACRO_DIR([shave])
 # Disable C++/Fortran checks
@@ -34,8 +34,8 @@ AC_SEARCH_LIBS([iconv_open], [iconv], AC_DEFINE(CONFIG_ICONV, 1, [use iconv]))
 AC_CHECK_LIB([m], [fabs])
 
 # Check for libraries via pkg-config
-AC_ARG_ENABLE([png], AS_HELP_STRING([--disable-png],
-    [disable png support @<:@default=check@:>@]))
+AC_ARG_ENABLE([png], AS_HELP_STRING([--enable-png],
+    [enable png (test program) @<:@default=no@:>@]))
 AC_ARG_ENABLE([enca], AS_HELP_STRING([--disable-enca],
     [disable enca (charset autodetect) support @<:@default=check@:>@]))
 AC_ARG_ENABLE([fontconfig], AS_HELP_STRING([--disable-fontconfig],
@@ -65,13 +65,14 @@ PKG_CHECK_MODULES([ENCA], enca, [
 	], [enca=false])
 fi
 
-if test x$enable_png != xno; then
+libpng=false
+if test x$enable_png = xyes; then
 PKG_CHECK_MODULES([LIBPNG], libpng >= 1.2.0, [
     CFLAGS="$CFLAGS $LIBPNG_CFLAGS"
     LIBS="$LIBS $LIBPNG_LIBS"
     AC_DEFINE(CONFIG_LIBPNG, 1, [found libpng via pkg-config])
     libpng=true
-	], [libpng=false])
+    ])
 fi
 
 AM_CONDITIONAL([HAVE_LIBPNG], [test x$libpng = xtrue])
diff --git a/libass/Makefile.am b/libass/Makefile.am
index fd818ae..0cf613e 100644
--- a/libass/Makefile.am
+++ b/libass/Makefile.am
@@ -7,7 +7,8 @@ libass_la_SOURCES = ass.c ass_cache.c ass_font.c ass_fontconfig.c ass_render.c \
                     ass_utils.c ass_bitmap.c ass_library.c ass_bitmap.h \
                     ass_cache.h ass_fontconfig.h ass_font.h ass.h \
                     ass_library.h ass_types.h ass_utils.h ass_drawing.c \
-                    ass_drawing.h ass_cache_template.h
+                    ass_drawing.h ass_cache_template.h ass_render.h \
+                    ass_parse.c ass_parse.h
 libass_la_LDFLAGS = -version-info $(LIBASS_LT_CURRENT):$(LIBASS_LT_REVISION):$(LIBASS_LT_AGE)
 libass_la_LDFLAGS += -export-symbols $(srcdir)/libass.sym
 
diff --git a/libass/Makefile.in b/libass/Makefile.in
index 1b54bb0..a162f3c 100644
--- a/libass/Makefile.in
+++ b/libass/Makefile.in
@@ -56,7 +56,7 @@ LTLIBRARIES = $(lib_LTLIBRARIES)
 libass_la_LIBADD =
 am_libass_la_OBJECTS = ass.lo ass_cache.lo ass_font.lo \
 	ass_fontconfig.lo ass_render.lo ass_utils.lo ass_bitmap.lo \
-	ass_library.lo ass_drawing.lo
+	ass_library.lo ass_drawing.lo ass_parse.lo
 libass_la_OBJECTS = $(am_libass_la_OBJECTS)
 libass_la_LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) \
 	$(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
@@ -208,7 +208,8 @@ libass_la_SOURCES = ass.c ass_cache.c ass_font.c ass_fontconfig.c ass_render.c \
                     ass_utils.c ass_bitmap.c ass_library.c ass_bitmap.h \
                     ass_cache.h ass_fontconfig.h ass_font.h ass.h \
                     ass_library.h ass_types.h ass_utils.h ass_drawing.c \
-                    ass_drawing.h ass_cache_template.h
+                    ass_drawing.h ass_cache_template.h ass_render.h \
+                    ass_parse.c ass_parse.h
 
 libass_la_LDFLAGS = -version-info \
 	$(LIBASS_LT_CURRENT):$(LIBASS_LT_REVISION):$(LIBASS_LT_AGE) \
@@ -292,6 +293,7 @@ distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/ass_font.Plo at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/ass_fontconfig.Plo at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/ass_library.Plo at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/ass_parse.Plo at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/ass_render.Plo at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/ass_utils.Plo at am__quote@
 
diff --git a/libass/ass.c b/libass/ass.c
index 6c28a97..f7f5bcc 100644
--- a/libass/ass.c
+++ b/libass/ass.c
@@ -198,7 +198,7 @@ static int lookup_style(ASS_Track *track, char *name)
 static uint32_t string2color(ASS_Library *library, char *p)
 {
     uint32_t tmp;
-    (void) strtocolor(library, &p, &tmp);
+    (void) strtocolor(library, &p, &tmp, 0);
     return tmp;
 }
 
@@ -389,6 +389,8 @@ void ass_process_force_style(ASS_Track *track)
             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) {
@@ -571,6 +573,8 @@ static int process_info_line(ASS_Track *track, char *str)
         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;
 }
diff --git a/libass/ass.h b/libass/ass.h
index 908b199..3684b44 100644
--- a/libass/ass.h
+++ b/libass/ass.h
@@ -25,7 +25,7 @@
 #include <stdarg.h>
 #include "ass_types.h"
 
-#define LIBASS_VERSION 0x00907010
+#define LIBASS_VERSION 0x00908000
 
 /*
  * A linked list of images produced by an ass renderer.
diff --git a/libass/ass_bitmap.c b/libass/ass_bitmap.c
index faddcf3..c7c039d 100644
--- a/libass/ass_bitmap.c
+++ b/libass/ass_bitmap.c
@@ -230,12 +230,12 @@ static Bitmap *glyph_to_bitmap_internal(ASS_Library *library,
 }
 
 /**
- * \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 *fix_outline_and_shadow(Bitmap *bm_g, Bitmap *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;
@@ -247,30 +247,21 @@ static Bitmap *fix_outline_and_shadow(Bitmap *bm_g, Bitmap *bm_o)
         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 *bm_s = copy_bitmap(bm_o);
-
     unsigned char *g =
         bm_g->buffer + (t - bm_g->top) * bm_g->w + (l - bm_g->left);
     unsigned char *o =
         bm_o->buffer + (t - bm_o->top) * bm_o->w + (l - bm_o->left);
-    unsigned char *s =
-        bm_s->buffer + (t - bm_s->top) * bm_s->w + (l - bm_s->left);
 
     for (y = 0; y < b - t; ++y) {
         for (x = 0; x < r - l; ++x) {
             unsigned char c_g, c_o;
             c_g = g[x];
             c_o = o[x];
-            o[x] = (c_o > (3 * c_g) / 5) ? c_o - (3 * c_g) / 5 : 0;
-            s[x] = (c_o < 0xFF - c_g) ? c_o + c_g : 0xFF;
+            o[x] = (c_o > c_g) ? c_o - (c_g / 2) : 0;
         }
         g += bm_g->w;
         o += bm_o->w;
-        s += bm_s->w;
     }
-
-    assert(bm_s);
-    return bm_s;
 }
 
 /**
@@ -475,7 +466,8 @@ static void be_blur(unsigned char *buf, int w, int h)
 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 be, double blur_radius, FT_Vector shadow_offset,
+                    int border_style)
 {
     blur_radius *= 2;
     int bbord = be > 0 ? sqrt(2 * be) : 0;
@@ -527,14 +519,19 @@ int glyph_to_bitmap(ASS_Library *library, ASS_SynthPriv *priv_blur,
                            priv_blur->g_w);
     }
 
-    if (*bm_o)
-        *bm_s = fix_outline_and_shadow(*bm_g, *bm_o);
-    else
+    // 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);
 
-    assert(bm_s);
     return 0;
 }
diff --git a/libass/ass_bitmap.h b/libass/ass_bitmap.h
index 3b63cee..338db01 100644
--- a/libass/ass_bitmap.h
+++ b/libass/ass_bitmap.h
@@ -49,7 +49,8 @@ typedef struct {
 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 be, double blur_radius, FT_Vector shadow_offset,
+                    int border_style);
 
 void ass_free_bitmap(Bitmap *bm);
 
diff --git a/libass/ass_cache.c b/libass/ass_cache.c
index ac0a00f..643d991 100644
--- a/libass/ass_cache.c
+++ b/libass/ass_cache.c
@@ -226,7 +226,7 @@ void *cache_add_bitmap(Hashmap *bitmap_cache, BitmapHashKey *key,
     // Note: this is only an approximation
     if (val->bm_o)
         bitmap_cache->cache_size += val->bm_o->w * val->bm_o->h * 3;
-    else
+    else if (val->bm)
         bitmap_cache->cache_size += val->bm->w * val->bm->h * 3;
 
     return hashmap_insert(bitmap_cache, key, val);
diff --git a/libass/ass_cache_template.h b/libass/ass_cache_template.h
index 6a6c3f2..f335c6b 100644
--- a/libass/ass_cache_template.h
+++ b/libass/ass_cache_template.h
@@ -54,7 +54,7 @@
 
 
 // describes a bitmap; bitmaps with equivalents structs are considered identical
-START(bitmap, bipmap_hash_key)
+START(bitmap, bitmap_hash_key)
     GENERIC(char, bitmap) // bool : true = bitmap, false = outline
     GENERIC(ASS_Font *, font)
     GENERIC(double, size) // font size
@@ -79,6 +79,8 @@ START(bitmap, bipmap_hash_key)
     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
@@ -93,6 +95,7 @@ START(glyph, glyph_hash_key)
     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
@@ -105,8 +108,10 @@ START(composite, composite_hash_key)
     GENERIC(int, ay)
     GENERIC(int, bx)
     GENERIC(int, by)
-    BITMAPHASHKEY(a)
-    BITMAPHASHKEY(b)
+    GENERIC(int, as)
+    GENERIC(int, bs)
+    GENERIC(unsigned char *, a)
+    GENERIC(unsigned char *, b)
 END(CompositeHashKey)
 
 
diff --git a/libass/ass_drawing.c b/libass/ass_drawing.c
index 95348a2..8c5f062 100644
--- a/libass/ass_drawing.c
+++ b/libass/ass_drawing.c
@@ -86,8 +86,10 @@ static inline void drawing_close_shape(ASS_Drawing *drawing)
                                drawing->max_contours);
     }
 
-    ol->contours[ol->n_contours] = ol->n_points - 1;
-    ol->n_contours++;
+    if (ol->n_points) {
+        ol->contours[ol->n_contours] = ol->n_points - 1;
+        ol->n_contours++;
+    }
 }
 
 /*
diff --git a/libass/ass_font.c b/libass/ass_font.c
index e6da4bc..7d848a2 100644
--- a/libass/ass_font.c
+++ b/libass/ass_font.c
@@ -26,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"
@@ -299,7 +300,10 @@ static int ass_strike_outline_glyph(FT_Face face, ASS_Font *font,
     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;
+    int bear, advance, y_scale, i, dir;
+
+    if (!under && !through)
+        return 0;
 
     // Grow outline
     i = (under ? 4 : 0) + (through ? 4 : 0);
@@ -317,6 +321,9 @@ static int ass_strike_outline_glyph(FT_Face face, ASS_Font *font,
     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;
@@ -325,7 +332,7 @@ static int ass_strike_outline_glyph(FT_Face face, ASS_Font *font,
                          y_scale * font->scale_y / 2);
 
         if (pos > 0 || size <= 0)
-            return 0;
+            return 1;
 
         FT_Vector points[4] = {
             {.x = bear,      .y = pos + size},
@@ -334,10 +341,18 @@ static int ass_strike_outline_glyph(FT_Face face, ASS_Font *font,
             {.x = bear,      .y = pos - size},
         };
 
-        for (i = 0; i < 4; i++) {
-            ol->points[ol->n_points] = points[i];
-            ol->tags[ol->n_points++] = 1;
+        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;
     }
 
@@ -347,7 +362,7 @@ static int ass_strike_outline_glyph(FT_Face face, ASS_Font *font,
         size = FT_MulFix(os2->yStrikeoutSize, y_scale * font->scale_y / 2);
 
         if (pos < 0 || size <= 0)
-            return 0;
+            return 1;
 
         FT_Vector points[4] = {
             {.x = bear,      .y = pos + size},
@@ -356,15 +371,38 @@ static int ass_strike_outline_glyph(FT_Face face, ASS_Font *font,
             {.x = bear,      .y = pos - size},
         };
 
-        for (i = 0; i < 4; i++) {
-            ol->points[ol->n_points] = points[i];
-            ol->tags[ol->n_points++] = 1;
+        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 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);
 }
 
 /**
@@ -383,6 +421,9 @@ FT_Glyph ass_font_get_glyph(void *fontconfig_priv, ASS_Font *font,
 
     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;
 
@@ -443,6 +484,11 @@ FT_Glyph ass_font_get_glyph(void *fontconfig_priv, ASS_Font *font,
         (font->desc.italic > 55)) {
         FT_GlyphSlot_Oblique(face->glyph);
     }
+
+    if (!(face->style_flags & FT_STYLE_FLAG_BOLD) &&
+        (font->desc.bold > 80)) {
+        ass_glyph_embolden(face->glyph);
+    }
 #endif
     error = FT_Get_Glyph(face->glyph, &glyph);
     if (error) {
diff --git a/libass/ass_fontconfig.c b/libass/ass_fontconfig.c
index 684c2a4..006be97 100644
--- a/libass/ass_fontconfig.c
+++ b/libass/ass_fontconfig.c
@@ -564,8 +564,10 @@ int fontconfig_update(FCInstance *priv)
 
 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)
diff --git a/libass/ass_parse.c b/libass/ass_parse.c
new file mode 100644
index 0000000..535e16f
--- /dev/null
+++ b/libass/ass_parse.c
@@ -0,0 +1,932 @@
+/*
+ * 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;
+#if (FREETYPE_MAJOR > 2) || ((FREETYPE_MAJOR == 2) && (FREETYPE_MINOR > 1))
+            error =
+                FT_Stroker_New(render_priv->ftlibrary,
+                               &render_priv->state.stroker);
+#else                           // < 2.2
+            error =
+                FT_Stroker_New(render_priv->state.font->faces[0]->
+                               memory, &render_priv->state.stroker);
+#endif
+            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) +
+        (_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(')');
+    ass_drawing_parse(drawing, 1);
+
+    // We need to translate the clip according to screen borders
+    if (render_priv->settings.left_margin != 0 ||
+        render_priv->settings.top_margin != 0) {
+        FT_Vector trans = {
+            .x = int_to_d6(render_priv->settings.left_margin),
+            .y = -int_to_d6(render_priv->settings.top_margin),
+        };
+        FT_Outline_Translate(&drawing->glyph->outline, trans.x, trans.y);
+    }
+    ass_msg(render_priv->library, MSGL_DBG2,
+            "Parsed vector clip: scale %d, scales (%f, %f) string [%s]\n",
+            scale, drawing->scale_x, drawing->scale_y, drawing->text);
+
+    return p;
+}
+
+/**
+ * \brief Parse style override tag.
+ * \param p string to parse
+ * \param pwr multiplier for some tag effects (comes from \t tags)
+ */
+static char *parse_tag(ASS_Renderer *render_priv, char *p, double pwr)
+{
+    skip_to('\\');
+    skip('\\');
+    if ((*p == '}') || (*p == 0))
+        return p;
+
+    // New tags introduced in vsfilter 2.39
+    if (mystrcmp(&p, "xbord")) {
+        double val;
+        if (mystrtod(&p, &val))
+            val = render_priv->state.border_x * (1 - pwr) + val * pwr;
+        else
+            val = -1.;
+        change_border(render_priv, val, render_priv->state.border_y);
+    } else if (mystrcmp(&p, "ybord")) {
+        double val;
+        if (mystrtod(&p, &val))
+            val = render_priv->state.border_y * (1 - pwr) + val * pwr;
+        else
+            val = -1.;
+        change_border(render_priv, render_priv->state.border_x, val);
+    } else if (mystrcmp(&p, "xshad")) {
+        double val;
+        if (mystrtod(&p, &val))
+            val = render_priv->state.shadow_x * (1 - pwr) + val * pwr;
+        else
+            val = 0.;
+        render_priv->state.shadow_x = val;
+    } else if (mystrcmp(&p, "yshad")) {
+        double val;
+        if (mystrtod(&p, &val))
+            val = render_priv->state.shadow_y * (1 - pwr) + val * pwr;
+        else
+            val = 0.;
+        render_priv->state.shadow_y = val;
+    } else if (mystrcmp(&p, "fax")) {
+        double val;
+        if (mystrtod(&p, &val))
+            render_priv->state.fax =
+                val * pwr + render_priv->state.fax * (1 - pwr);
+        else
+            render_priv->state.fax = 0.;
+    } else if (mystrcmp(&p, "fay")) {
+        double val;
+        if (mystrtod(&p, &val))
+            render_priv->state.fay =
+                val * pwr + render_priv->state.fay * (1 - pwr);
+        else
+            render_priv->state.fay = 0.;
+    } else if (mystrcmp(&p, "iclip")) {
+        int x0, y0, x1, y1;
+        int res = 1;
+        char *start = p;
+        skipopt('(');
+        res &= mystrtoi(&p, &x0);
+        skipopt(',');
+        res &= mystrtoi(&p, &y0);
+        skipopt(',');
+        res &= mystrtoi(&p, &x1);
+        skipopt(',');
+        res &= mystrtoi(&p, &y1);
+        skipopt(')');
+        if (res) {
+            render_priv->state.clip_x0 =
+                render_priv->state.clip_x0 * (1 - pwr) + x0 * pwr;
+            render_priv->state.clip_x1 =
+                render_priv->state.clip_x1 * (1 - pwr) + x1 * pwr;
+            render_priv->state.clip_y0 =
+                render_priv->state.clip_y0 * (1 - pwr) + y0 * pwr;
+            render_priv->state.clip_y1 =
+                render_priv->state.clip_y1 * (1 - pwr) + y1 * pwr;
+            render_priv->state.clip_mode = 1;
+        } else if (!render_priv->state.clip_drawing) {
+            p = parse_vector_clip(render_priv, start);
+            render_priv->state.clip_drawing_mode = 1;
+        } else
+            render_priv->state.clip_mode = 0;
+    } else if (mystrcmp(&p, "blur")) {
+        double val;
+        if (mystrtod(&p, &val)) {
+            val = render_priv->state.blur * (1 - pwr) + val * pwr;
+            val = (val < 0) ? 0 : val;
+            val = (val > BLUR_MAX_RADIUS) ? BLUR_MAX_RADIUS : val;
+            render_priv->state.blur = val;
+        } else
+            render_priv->state.blur = 0.0;
+        // ASS standard tags
+    } else if (mystrcmp(&p, "fsc")) {
+        char tp = *p++;
+        double val;
+        if (tp == 'x') {
+            if (mystrtod(&p, &val)) {
+                val /= 100;
+                render_priv->state.scale_x =
+                    render_priv->state.scale_x * (1 - pwr) + val * pwr;
+            } else
+                render_priv->state.scale_x =
+                    render_priv->state.style->ScaleX;
+        } else if (tp == 'y') {
+            if (mystrtod(&p, &val)) {
+                val /= 100;
+                render_priv->state.scale_y =
+                    render_priv->state.scale_y * (1 - pwr) + val * pwr;
+            } else
+                render_priv->state.scale_y =
+                    render_priv->state.style->ScaleY;
+        }
+    } else if (mystrcmp(&p, "fsp")) {
+        double val;
+        if (mystrtod(&p, &val))
+            render_priv->state.hspacing =
+                render_priv->state.hspacing * (1 - pwr) + val * pwr;
+        else
+            render_priv->state.hspacing = render_priv->state.style->Spacing;
+    } else if (mystrcmp(&p, "fs")) {
+        double val;
+        if (mystrtod(&p, &val))
+            val = render_priv->state.font_size * (1 - pwr) + val * pwr;
+        else
+            val = render_priv->state.style->FontSize;
+        if (render_priv->state.font)
+            change_font_size(render_priv, val);
+    } else if (mystrcmp(&p, "bord")) {
+        double val;
+        if (mystrtod(&p, &val)) {
+            if (render_priv->state.border_x == render_priv->state.border_y)
+                val = render_priv->state.border_x * (1 - pwr) + val * pwr;
+        } else
+            val = -1.;          // reset to default
+        change_border(render_priv, val, val);
+    } else if (mystrcmp(&p, "move")) {
+        double x1, x2, y1, y2;
+        long long t1, t2, delta_t, t;
+        double x, y;
+        double k;
+        skip('(');
+        mystrtod(&p, &x1);
+        skip(',');
+        mystrtod(&p, &y1);
+        skip(',');
+        mystrtod(&p, &x2);
+        skip(',');
+        mystrtod(&p, &y2);
+        if (*p == ',') {
+            skip(',');
+            mystrtoll(&p, &t1);
+            skip(',');
+            mystrtoll(&p, &t2);
+            ass_msg(render_priv->library, MSGL_DBG2,
+                   "movement6: (%f, %f) -> (%f, %f), (%" PRId64 " .. %"
+                   PRId64 ")\n", x1, y1, x2, y2, (int64_t) t1,
+                   (int64_t) t2);
+        } else {
+            t1 = 0;
+            t2 = render_priv->state.event->Duration;
+            ass_msg(render_priv->library, MSGL_DBG2,
+                   "movement: (%f, %f) -> (%f, %f)", x1, y1, x2, y2);
+        }
+        skip(')');
+        delta_t = t2 - t1;
+        t = render_priv->time - render_priv->state.event->Start;
+        if (t < t1)
+            k = 0.;
+        else if (t > t2)
+            k = 1.;
+        else
+            k = ((double) (t - t1)) / delta_t;
+        x = k * (x2 - x1) + x1;
+        y = k * (y2 - y1) + y1;
+        if (render_priv->state.evt_type != EVENT_POSITIONED) {
+            render_priv->state.pos_x = x;
+            render_priv->state.pos_y = y;
+            render_priv->state.detect_collisions = 0;
+            render_priv->state.evt_type = EVENT_POSITIONED;
+        }
+    } else if (mystrcmp(&p, "frx")) {
+        double val;
+        if (mystrtod(&p, &val)) {
+            val *= M_PI / 180;
+            render_priv->state.frx =
+                val * pwr + render_priv->state.frx * (1 - pwr);
+        } else
+            render_priv->state.frx = 0.;
+    } else if (mystrcmp(&p, "fry")) {
+        double val;
+        if (mystrtod(&p, &val)) {
+            val *= M_PI / 180;
+            render_priv->state.fry =
+                val * pwr + render_priv->state.fry * (1 - pwr);
+        } else
+            render_priv->state.fry = 0.;
+    } else if (mystrcmp(&p, "frz") || mystrcmp(&p, "fr")) {
+        double val;
+        if (mystrtod(&p, &val)) {
+            val *= M_PI / 180;
+            render_priv->state.frz =
+                val * pwr + render_priv->state.frz * (1 - pwr);
+        } else
+            render_priv->state.frz =
+                M_PI * render_priv->state.style->Angle / 180.;
+    } else if (mystrcmp(&p, "fn")) {
+        char *start = p;
+        char *family;
+        skip_to('\\');
+        if (p > start) {
+            family = malloc(p - start + 1);
+            strncpy(family, start, p - start);
+            family[p - start] = '\0';
+        } else
+            family = strdup(render_priv->state.style->FontName);
+        if (render_priv->state.family)
+            free(render_priv->state.family);
+        render_priv->state.family = family;
+        update_font(render_priv);
+    } else if (mystrcmp(&p, "alpha")) {
+        uint32_t val;
+        int i;
+        int hex = render_priv->track->track_type == TRACK_TYPE_ASS;
+        if (strtocolor(render_priv->library, &p, &val, hex)) {
+            unsigned char a = val >> 24;
+            for (i = 0; i < 4; ++i)
+                change_alpha(&render_priv->state.c[i], a, pwr);
+        } else {
+            change_alpha(&render_priv->state.c[0],
+                         render_priv->state.style->PrimaryColour, pwr);
+            change_alpha(&render_priv->state.c[1],
+                         render_priv->state.style->SecondaryColour, pwr);
+            change_alpha(&render_priv->state.c[2],
+                         render_priv->state.style->OutlineColour, pwr);
+            change_alpha(&render_priv->state.c[3],
+                         render_priv->state.style->BackColour, pwr);
+        }
+        // FIXME: simplify
+    } else if (mystrcmp(&p, "an")) {
+        int val;
+        if (mystrtoi(&p, &val) && val) {
+            int v = (val - 1) / 3;      // 0, 1 or 2 for vertical alignment
+            ass_msg(render_priv->library, MSGL_DBG2, "an %d", val);
+            if (v != 0)
+                v = 3 - v;
+            val = ((val - 1) % 3) + 1;  // horizontal alignment
+            val += v * 4;
+            ass_msg(render_priv->library, MSGL_DBG2, "align %d", val);
+            render_priv->state.alignment = val;
+        } else
+            render_priv->state.alignment =
+                render_priv->state.style->Alignment;
+    } else if (mystrcmp(&p, "a")) {
+        int val;
+        if (mystrtoi(&p, &val) && val)
+            // take care of a vsfilter quirk: handle illegal \a8 like \a5
+            render_priv->state.alignment = (val == 8) ? 5 : val;
+        else
+            render_priv->state.alignment =
+                render_priv->state.style->Alignment;
+    } else if (mystrcmp(&p, "pos")) {
+        double v1, v2;
+        skip('(');
+        mystrtod(&p, &v1);
+        skip(',');
+        mystrtod(&p, &v2);
+        skip(')');
+        ass_msg(render_priv->library, MSGL_DBG2, "pos(%f, %f)", v1, v2);
+        if (render_priv->state.evt_type == EVENT_POSITIONED) {
+            ass_msg(render_priv->library, MSGL_V, "Subtitle has a new \\pos "
+                   "after \\move or \\pos, ignoring");
+        } else {
+            render_priv->state.evt_type = EVENT_POSITIONED;
+            render_priv->state.detect_collisions = 0;
+            render_priv->state.pos_x = v1;
+            render_priv->state.pos_y = v2;
+        }
+    } else if (mystrcmp(&p, "fad")) {
+        int a1, a2, a3;
+        long long t1, t2, t3, t4;
+        if (*p == 'e')
+            ++p;                // either \fad or \fade
+        skip('(');
+        mystrtoi(&p, &a1);
+        skip(',');
+        mystrtoi(&p, &a2);
+        if (*p == ')') {
+            // 2-argument version (\fad, according to specs)
+            // a1 and a2 are fade-in and fade-out durations
+            t1 = 0;
+            t4 = render_priv->state.event->Duration;
+            t2 = a1;
+            t3 = t4 - a2;
+            a1 = 0xFF;
+            a2 = 0;
+            a3 = 0xFF;
+        } else {
+            // 6-argument version (\fade)
+            // a1 and a2 (and a3) are opacity values
+            skip(',');
+            mystrtoi(&p, &a3);
+            skip(',');
+            mystrtoll(&p, &t1);
+            skip(',');
+            mystrtoll(&p, &t2);
+            skip(',');
+            mystrtoll(&p, &t3);
+            skip(',');
+            mystrtoll(&p, &t4);
+        }
+        skip(')');
+        render_priv->state.fade =
+            interpolate_alpha(render_priv->time -
+                              render_priv->state.event->Start, t1, t2,
+                              t3, t4, a1, a2, a3);
+    } else if (mystrcmp(&p, "org")) {
+        int v1, v2;
+        skip('(');
+        mystrtoi(&p, &v1);
+        skip(',');
+        mystrtoi(&p, &v2);
+        skip(')');
+        ass_msg(render_priv->library, MSGL_DBG2, "org(%d, %d)", v1, v2);
+        if (!render_priv->state.have_origin) {
+            render_priv->state.org_x = v1;
+            render_priv->state.org_y = v2;
+            render_priv->state.have_origin = 1;
+            render_priv->state.detect_collisions = 0;
+        }
+    } else if (mystrcmp(&p, "t")) {
+        double v[3];
+        int v1, v2;
+        double v3;
+        int cnt;
+        long long t1, t2, t, delta_t;
+        double k;
+        skip('(');
+        for (cnt = 0; cnt < 3; ++cnt) {
+            if (*p == '\\')
+                break;
+            v[cnt] = strtod(p, &p);
+            skip(',');
+        }
+        if (cnt == 3) {
+            v1 = v[0];
+            v2 = (v[1] < v1) ? render_priv->state.event->Duration : v[1];
+            v3 = v[2];
+        } else if (cnt == 2) {
+            v1 = v[0];
+            v2 = (v[1] < v1) ? render_priv->state.event->Duration : v[1];
+            v3 = 1.;
+        } else if (cnt == 1) {
+            v1 = 0;
+            v2 = render_priv->state.event->Duration;
+            v3 = v[0];
+        } else {                // cnt == 0
+            v1 = 0;
+            v2 = render_priv->state.event->Duration;
+            v3 = 1.;
+        }
+        render_priv->state.detect_collisions = 0;
+        t1 = v1;
+        t2 = v2;
+        delta_t = v2 - v1;
+        if (v3 < 0.)
+            v3 = 0.;
+        t = render_priv->time - render_priv->state.event->Start;        // FIXME: move to render_context
+        if (t <= t1)
+            k = 0.;
+        else if (t >= t2)
+            k = 1.;
+        else {
+            assert(delta_t != 0.);
+            k = pow(((double) (t - t1)) / delta_t, v3);
+        }
+        while (*p == '\\')
+            p = parse_tag(render_priv, p, k);   // maybe k*pwr ? no, specs forbid nested \t's
+        skip_to(')');           // in case there is some unknown tag or a comment
+        skip(')');
+    } else if (mystrcmp(&p, "clip")) {
+        char *start = p;
+        int x0, y0, x1, y1;
+        int res = 1;
+        skipopt('(');
+        res &= mystrtoi(&p, &x0);
+        skipopt(',');
+        res &= mystrtoi(&p, &y0);
+        skipopt(',');
+        res &= mystrtoi(&p, &x1);
+        skipopt(',');
+        res &= mystrtoi(&p, &y1);
+        skipopt(')');
+        if (res) {
+            render_priv->state.clip_x0 =
+                render_priv->state.clip_x0 * (1 - pwr) + x0 * pwr;
+            render_priv->state.clip_x1 =
+                render_priv->state.clip_x1 * (1 - pwr) + x1 * pwr;
+            render_priv->state.clip_y0 =
+                render_priv->state.clip_y0 * (1 - pwr) + y0 * pwr;
+            render_priv->state.clip_y1 =
+                render_priv->state.clip_y1 * (1 - pwr) + y1 * pwr;
+        // Might be a vector clip
+        } else if (!render_priv->state.clip_drawing) {
+            p = parse_vector_clip(render_priv, start);
+            render_priv->state.clip_drawing_mode = 0;
+        } else {
+            render_priv->state.clip_x0 = 0;
+            render_priv->state.clip_y0 = 0;
+            render_priv->state.clip_x1 = render_priv->track->PlayResX;
+            render_priv->state.clip_y1 = render_priv->track->PlayResY;
+        }
+    } else if (mystrcmp(&p, "c")) {
+        uint32_t val;
+        int hex = render_priv->track->track_type == TRACK_TYPE_ASS;
+        if (!strtocolor(render_priv->library, &p, &val, hex))
+            val = render_priv->state.style->PrimaryColour;
+        ass_msg(render_priv->library, MSGL_DBG2, "color: %X", val);
+        change_color(&render_priv->state.c[0], val, pwr);
+    } else if ((*p >= '1') && (*p <= '4') && (++p)
+               && (mystrcmp(&p, "c") || mystrcmp(&p, "a"))) {
+        char n = *(p - 2);
+        int cidx = n - '1';
+        char cmd = *(p - 1);
+        uint32_t val;
+        int hex = render_priv->track->track_type == TRACK_TYPE_ASS;
+        assert((n >= '1') && (n <= '4'));
+        if (!strtocolor(render_priv->library, &p, &val, hex))
+            switch (n) {
+            case '1':
+                val = render_priv->state.style->PrimaryColour;
+                break;
+            case '2':
+                val = render_priv->state.style->SecondaryColour;
+                break;
+            case '3':
+                val = render_priv->state.style->OutlineColour;
+                break;
+            case '4':
+                val = render_priv->state.style->BackColour;
+                break;
+            default:
+                val = 0;
+                break;          // impossible due to assert; avoid compilation warning
+            }
+        switch (cmd) {
+        case 'c':
+            change_color(render_priv->state.c + cidx, val, pwr);
+            break;
+        case 'a':
+            change_alpha(render_priv->state.c + cidx, val >> 24, pwr);
+            break;
+        default:
+            ass_msg(render_priv->library, MSGL_WARN, "Bad command: %c%c",
+                    n, cmd);
+            break;
+        }
+        ass_msg(render_priv->library, MSGL_DBG2, "single c/a at %f: %c%c = %X",
+               pwr, n, cmd, render_priv->state.c[cidx]);
+    } else if (mystrcmp(&p, "r")) {
+        reset_render_context(render_priv);
+    } else if (mystrcmp(&p, "be")) {
+        int val;
+        if (mystrtoi(&p, &val)) {
+            // Clamp to a safe upper limit, since high values need excessive CPU
+            val = (val < 0) ? 0 : val;
+            val = (val > MAX_BE) ? MAX_BE : val;
+            render_priv->state.be = val;
+        } else
+            render_priv->state.be = 0;
+    } else if (mystrcmp(&p, "b")) {
+        int b;
+        if (mystrtoi(&p, &b)) {
+            if (pwr >= .5)
+                render_priv->state.bold = b;
+        } else
+            render_priv->state.bold = render_priv->state.style->Bold;
+        update_font(render_priv);
+    } else if (mystrcmp(&p, "i")) {
+        int i;
+        if (mystrtoi(&p, &i)) {
+            if (pwr >= .5)
+                render_priv->state.italic = i;
+        } else
+            render_priv->state.italic = render_priv->state.style->Italic;
+        update_font(render_priv);
+    } else if (mystrcmp(&p, "kf") || mystrcmp(&p, "K")) {
+        int val = 0;
+        mystrtoi(&p, &val);
+        render_priv->state.effect_type = EF_KARAOKE_KF;
+        if (render_priv->state.effect_timing)
+            render_priv->state.effect_skip_timing +=
+                render_priv->state.effect_timing;
+        render_priv->state.effect_timing = val * 10;
+    } else if (mystrcmp(&p, "ko")) {
+        int val = 0;
+        mystrtoi(&p, &val);
+        render_priv->state.effect_type = EF_KARAOKE_KO;
+        if (render_priv->state.effect_timing)
+            render_priv->state.effect_skip_timing +=
+                render_priv->state.effect_timing;
+        render_priv->state.effect_timing = val * 10;
+    } else if (mystrcmp(&p, "k")) {
+        int val = 0;
+        mystrtoi(&p, &val);
+        render_priv->state.effect_type = EF_KARAOKE;
+        if (render_priv->state.effect_timing)
+            render_priv->state.effect_skip_timing +=
+                render_priv->state.effect_timing;
+        render_priv->state.effect_timing = val * 10;
+    } else if (mystrcmp(&p, "shad")) {
+        double val;
+        if (mystrtod(&p, &val)) {
+            if (render_priv->state.shadow_x == render_priv->state.shadow_y)
+                val = render_priv->state.shadow_x * (1 - pwr) + val * pwr;
+        } else
+            val = 0.;
+        render_priv->state.shadow_x = render_priv->state.shadow_y = val;
+    } else if (mystrcmp(&p, "s")) {
+        int val;
+        if (mystrtoi(&p, &val) && val)
+            render_priv->state.flags |= DECO_STRIKETHROUGH;
+        else
+            render_priv->state.flags &= ~DECO_STRIKETHROUGH;
+    } else if (mystrcmp(&p, "u")) {
+        int val;
+        if (mystrtoi(&p, &val) && val)
+            render_priv->state.flags |= DECO_UNDERLINE;
+        else
+            render_priv->state.flags &= ~DECO_UNDERLINE;
+    } else if (mystrcmp(&p, "pbo")) {
+        double val = 0;
+        if (mystrtod(&p, &val))
+            render_priv->state.drawing->pbo = val;
+    } else if (mystrcmp(&p, "p")) {
+        int val;
+        if (!mystrtoi(&p, &val))
+            val = 0;
+        if (val)
+            render_priv->state.drawing->scale = val;
+        render_priv->state.drawing_mode = !!val;
+    } else if (mystrcmp(&p, "q")) {
+        int val;
+        if (!mystrtoi(&p, &val))
+            val = render_priv->track->WrapStyle;
+        render_priv->state.wrap_style = val;
+    }
+
+    return p;
+}
+
+void apply_transition_effects(ASS_Renderer *render_priv, ASS_Event *event)
+{
+    int v[4];
+    int cnt;
+    char *p = event->Effect;
+
+    if (!p || !*p)
+        return;
+
+    cnt = 0;
+    while (cnt < 4 && (p = strchr(p, ';'))) {
+        v[cnt++] = atoi(++p);
+    }
+
+    if (strncmp(event->Effect, "Banner;", 7) == 0) {
+        int delay;
+        if (cnt < 1) {
+            ass_msg(render_priv->library, MSGL_V,
+                    "Error parsing effect: '%s'", event->Effect);
+            return;
+        }
+        if (cnt >= 2 && v[1] == 0)      // right-to-left
+            render_priv->state.scroll_direction = SCROLL_RL;
+        else                    // left-to-right
+            render_priv->state.scroll_direction = SCROLL_LR;
+
+        delay = v[0];
+        if (delay == 0)
+            delay = 1;          // ?
+        render_priv->state.scroll_shift =
+            (render_priv->time - render_priv->state.event->Start) / delay;
+        render_priv->state.evt_type = EVENT_HSCROLL;
+        return;
+    }
+
+    if (strncmp(event->Effect, "Scroll up;", 10) == 0) {
+        render_priv->state.scroll_direction = SCROLL_BT;
+    } else if (strncmp(event->Effect, "Scroll down;", 12) == 0) {
+        render_priv->state.scroll_direction = SCROLL_TB;
+    } else {
+        ass_msg(render_priv->library, MSGL_V,
+                "Unknown transition effect: '%s'", event->Effect);
+        return;
+    }
+    // parse scroll up/down parameters
+    {
+        int delay;
+        int y0, y1;
+        if (cnt < 3) {
+            ass_msg(render_priv->library, MSGL_V,
+                    "Error parsing effect: '%s'", event->Effect);
+            return;
+        }
+        delay = v[2];
+        if (delay == 0)
+            delay = 1;          // ?
+        render_priv->state.scroll_shift =
+            (render_priv->time - render_priv->state.event->Start) / delay;
+        if (v[0] < v[1]) {
+            y0 = v[0];
+            y1 = v[1];
+        } else {
+            y0 = v[1];
+            y1 = v[0];
+        }
+        if (y1 == 0)
+            y1 = render_priv->track->PlayResY;  // y0=y1=0 means fullscreen scrolling
+        render_priv->state.clip_y0 = y0;
+        render_priv->state.clip_y1 = y1;
+        render_priv->state.evt_type = EVENT_VSCROLL;
+        render_priv->state.detect_collisions = 0;
+    }
+
+}
+
+/**
+ * \brief Get next ucs4 char from string, parsing and executing style overrides
+ * \param str string pointer
+ * \return ucs4 code of the next char
+ * On return str points to the unparsed part of the string
+ */
+unsigned get_next_char(ASS_Renderer *render_priv, char **str)
+{
+    char *p = *str;
+    unsigned chr;
+    if (*p == '{') {            // '\0' goes here
+        p++;
+        while (1) {
+            p = parse_tag(render_priv, p, 1.);
+            if (*p == '}') {    // end of tag
+                p++;
+                if (*p == '{') {
+                    p++;
+                    continue;
+                } else
+                    break;
+            } else if (*p != '\\')
+                ass_msg(render_priv->library, MSGL_V,
+                        "Unable to parse: '%s'", p);
+            if (*p == 0)
+                break;
+        }
+    }
+    if (*p == '\t') {
+        ++p;
+        *str = p;
+        return ' ';
+    }
+    if (*p == '\\') {
+        if ((p[1] == 'N') || ((p[1] == 'n') &&
+                              (render_priv->state.wrap_style == 2))) {
+            p += 2;
+            *str = p;
+            return '\n';
+        } else if (p[1] == 'n') {
+            p += 2;
+            *str = p;
+            return ' ';
+        } else if (p[1] == 'h') {
+            p += 2;
+            *str = p;
+            return NBSP;
+        }
+    }
+    chr = ass_utf8_get_char((char **) &p);
+    *str = p;
+    return chr;
+}
diff --git a/libass/ass_parse.h b/libass/ass_parse.h
new file mode 100644
index 0000000..c65b565
--- /dev/null
+++ b/libass/ass_parse.h
@@ -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 */
diff --git a/libass/ass_render.c b/libass/ass_render.c
index 3d1e8e6..c2756fd 100644
--- a/libass/ass_render.c
+++ b/libass/ass_render.c
@@ -37,232 +37,16 @@
 #include "ass_fontconfig.h"
 #include "ass_library.h"
 #include "ass_drawing.h"
+#include "ass_render.h"
+#include "ass_parse.h"
 
 #define MAX_GLYPHS_INITIAL 1024
 #define MAX_LINES_INITIAL 64
-#define BLUR_MAX_RADIUS 100.0
-#define MAX_BE 127
 #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 {
-    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;
-    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;
-    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;
-
-} 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;
-};
-
-struct render_priv {
-    int top, height;
-    int render_id;
-};
-
-typedef struct {
-    int x0;
-    int y0;
-    int x1;
-    int y1;
-} Rect;
-
-typedef struct {
-    int a, b;                   // top and height
-} Segment;
-
-/* End of type definitions */
-
 static void ass_lazy_track_init(ASS_Renderer *render_priv)
 {
     ASS_Track *track = render_priv->track;
@@ -409,8 +193,8 @@ void ass_renderer_done(ASS_Renderer *render_priv)
  * 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)
+                                 int bitmap_h, int stride, int dst_x,
+                                 int dst_y, uint32_t color)
 {
     ASS_Image *img = calloc(1, sizeof(ASS_Image));
 
@@ -442,9 +226,9 @@ static double y2scr_pos(ASS_Renderer *render_priv, double y);
  * karaoke effects.  This can result in a lot of bitmaps (6 to be exact).
  */
 static ASS_Image **render_glyph_i(ASS_Renderer *render_priv,
-                                    Bitmap *bm, int dst_x, int dst_y,
-                                    uint32_t color, uint32_t color2, int brk,
-                                    ASS_Image **tail)
+                                  Bitmap *bm, int dst_x, int dst_y,
+                                  uint32_t color, uint32_t color2, int brk,
+                                  ASS_Image **tail)
 {
     int i, j, x0, y0, x1, y1, cx0, cy0, cx1, cy1, sx, sy, zx, zy;
     Rect r[4];
@@ -539,10 +323,9 @@ static ASS_Image **render_glyph_i(ASS_Renderer *render_priv,
  * \return pointer to the new list tail
  * Performs clipping. Uses my_draw_bitmap for actual bitmap convertion.
  */
-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)
+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)
 {
     // Inverse clipping in use?
     if (render_priv->state.clip_mode)
@@ -632,14 +415,13 @@ static unsigned char *clone_bitmap_buffer(ASS_Image *img)
 
 /**
  * \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_Renderer *render_priv, ASS_Image **last_tail,
-               ASS_Image **tail, BitmapHashKey *last_hash,
-               BitmapHashKey *hash)
+               ASS_Image **tail)
 {
     int left, top, bottom, right;
     int old_left, old_top, w, h, cur_left, cur_top;
@@ -683,8 +465,8 @@ render_overlap(ASS_Renderer *render_priv, ASS_Image **last_tail,
 
     // Query cache
     memset(&hk, 0, sizeof(hk));
-    memcpy(&hk.a, last_hash, sizeof(*last_hash));
-    memcpy(&hk.b, hash, sizeof(*hash));
+    hk.a = (*last_tail)->bitmap;
+    hk.b = (*tail)->bitmap;
     hk.aw = aw;
     hk.ah = ah;
     hk.bw = bw;
@@ -693,6 +475,8 @@ render_overlap(ASS_Renderer *render_priv, ASS_Image **last_tail,
     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;
@@ -703,12 +487,12 @@ render_overlap(ASS_Renderer *render_priv, ASS_Image **last_tail,
     a = clone_bitmap_buffer(*last_tail);
     b = clone_bitmap_buffer(*tail);
 
-    // Composite overlapping area
+    // 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 = (a[opos] > b[cpos]) ? a[opos] : b[cpos];
+            m = FFMIN(a[opos] + b[cpos], 0xff);
             (*last_tail)->bitmap[opos] = 0;
             (*tail)->bitmap[cpos] = m;
         }
@@ -808,7 +592,7 @@ static void blend_vector_clip(ASS_Renderer *render_priv,
             free_list_add(render_priv, nbuffer);
 
             // Blend together
-            memcpy(nbuffer, abuffer, as * ah);
+            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;
@@ -859,13 +643,12 @@ static ASS_Image *render_text(ASS_Renderer *render_priv, int dst_x,
     ASS_Image **tail = &head;
     ASS_Image **last_tail = 0;
     ASS_Image **here_tail = 0;
-    BitmapHashKey *last_hash = 0;
     TextInfo *text_info = &render_priv->text_info;
 
     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->shadow_x == 0 && info->shadow_y == 0) || info->skip)
             continue;
 
         pen_x =
@@ -881,16 +664,16 @@ static ASS_Image *render_text(ASS_Renderer *render_priv, int dst_x,
             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, last_hash,
-                           &info->hash_key);
+            render_overlap(render_priv, last_tail, here_tail);
+
         last_tail = here_tail;
-        last_hash = &info->hash_key;
     }
 
     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)
+        if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm_o
+            || info->skip)
             continue;
 
         pen_x = dst_x + (info->pos.x >> 6);
@@ -906,15 +689,16 @@ static ASS_Image *render_text(ASS_Renderer *render_priv, int dst_x,
                 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,
-                               last_hash, &info->hash_key);
+                render_overlap(render_priv, last_tail, here_tail);
+
             last_tail = here_tail;
-            last_hash = &info->hash_key;
         }
     }
+
     for (i = 0; i < text_info->length; ++i) {
         GlyphInfo *info = text_info->glyphs + i;
-        if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm)
+        if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm
+            || info->skip)
             continue;
 
         pen_x = dst_x + (info->pos.x >> 6);
@@ -1015,6 +799,7 @@ static void compute_string_bbox(TextInfo *info, DBBox *bbox)
                      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);
@@ -1024,911 +809,11 @@ static void compute_string_bbox(TextInfo *info, DBBox *bbox)
         bbox->xMin = bbox->xMax = bbox->yMin = bbox->yMax = 0.;
 }
 
-
-/**
- * \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.
- */
-static 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
- */
-static 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)
-            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;
-#if (FREETYPE_MAJOR > 2) || ((FREETYPE_MAJOR == 2) && (FREETYPE_MINOR > 1))
-            error =
-                FT_Stroker_New(render_priv->ftlibrary,
-                               &render_priv->state.stroker);
-#else                           // < 2.2
-            error =
-                FT_Stroker_New(render_priv->state.font->faces[0]->
-                               memory, &render_priv->state.stroker);
-#endif
-            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;
-    }
-}
-
-#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)
-{
-    (*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
-static void change_alpha(uint32_t *var, uint32_t new, double pwr)
-{
-    *var =
-        (_r(*var) << 24) + (_g(*var) << 16) + (_b(*var) << 8) +
-        (_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.
- */
-static 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;
-}
-
-#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; }
-
-/**
- * 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(')');
-    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;
-}
-
-static void reset_render_context(ASS_Renderer *);
-
-/**
- * \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;
-        if (strtocolor(render_priv->library, &p, &val)) {
-            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)
-            render_priv->state.alignment = 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;
-        if (!strtocolor(render_priv->library, &p, &val))
-            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;
-        assert((n >= '1') && (n <= '4'));
-        if (!strtocolor(render_priv->library, &p, &val))
-            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;
-    }
-
-    return p;
-
-#undef skip
-#undef skipopt
-#undef skip_to
-}
-
-/**
- * \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(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->track->WrapStyle == 2))) {
-            p += 2;
-            *str = p;
-            return '\n';
-        } else if ((*(p + 1) == 'n') || (*(p + 1) == 'h')) {
-            p += 2;
-            *str = p;
-            return ' ';
-        }
-    }
-    chr = ass_utf8_get_char((char **) &p);
-    *str = p;
-    return chr;
-}
-
-static 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 partially reset render_context to style values
  * Works like {\r}: resets some style overrides
  */
-static void reset_render_context(ASS_Renderer *render_priv)
+void reset_render_context(ASS_Renderer *render_priv)
 {
     render_priv->state.c[0] = render_priv->state.style->PrimaryColour;
     render_priv->state.c[1] = render_priv->state.style->SecondaryColour;
@@ -1959,6 +844,7 @@ static void reset_render_context(ASS_Renderer *render_priv)
     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.
 }
@@ -2092,6 +978,64 @@ static void fix_freetype_stroker(FT_OutlineGlyph glyph, int border_x,
 }
 
 /*
+ * Replace the outline of a glyph by a contour which makes up a simple
+ * opaque rectangle.
+ */
+static void draw_opaque_box(ASS_Renderer *render_priv, uint32_t ch,
+                            FT_Glyph glyph, int sx, int sy)
+{
+    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;
+
+    // to avoid gaps
+    sx = FFMAX(64, sx);
+    sy = FFMAX(64, sy);
+
+    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;
+    }
+
+    // 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;
+}
+
+/*
  * Stroke an outline glyph in x/y direction.  Applies various fixups to get
  * around limitations of the FreeType stroker.
  */
@@ -2144,8 +1088,8 @@ static void stroke_outline_glyph(ASS_Renderer *render_priv,
  * The glyphs are returned in info->glyph and info->outline_glyph
  */
 static void
-get_outline_glyph(ASS_Renderer *render_priv, int symbol,
-                  GlyphInfo *info, ASS_Drawing *drawing)
+get_outline_glyph(ASS_Renderer *render_priv, int symbol, GlyphInfo *info,
+                  ASS_Drawing *drawing)
 {
     GlyphHashValue *val;
     GlyphHashKey key;
@@ -2156,6 +1100,7 @@ get_outline_glyph(ASS_Renderer *render_priv, int symbol,
         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;
@@ -2168,6 +1113,7 @@ get_outline_glyph(ASS_Renderer *render_priv, int symbol,
         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));
 
@@ -2201,8 +1147,17 @@ get_outline_glyph(ASS_Renderer *render_priv, int symbol,
         info->advance.y = d16_to_d6(info->glyph->advance.y);
         FT_Glyph_Get_CBox(info->glyph, FT_GLYPH_BBOX_SUBPIXELS, &info->bbox);
 
-        if (render_priv->state.border_x > 0 ||
-            render_priv->state.border_y > 0) {
+        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) {
 
             FT_Glyph_Copy(info->glyph, &info->outline_glyph);
             stroke_outline_glyph(render_priv,
@@ -2229,7 +1184,8 @@ get_outline_glyph(ASS_Renderer *render_priv, int symbol,
 
 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);
+                         double frz, double fax, double fay, double scale,
+                         int yshift);
 
 /**
  * \brief Get bitmaps for a glyph
@@ -2255,15 +1211,20 @@ get_bitmap_glyph(ASS_Renderer *render_priv, GlyphInfo *info)
         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) {
+        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, info->fax,
-                         info->fay, render_priv->font_scale);
+                         info->frx, info->fry, info->frz, fax_scaled,
+                         fay_scaled, render_priv->font_scale, info->asc);
 
             // subpixel shift
             if (info->glyph)
@@ -2284,7 +1245,8 @@ get_bitmap_glyph(ASS_Renderer *render_priv, GlyphInfo *info)
                                     &info->bm, &info->bm_o,
                                     &info->bm_s, info->be,
                                     info->blur * render_priv->border_scale,
-                                    info->hash_key.shadow_offset);
+                                    info->hash_key.shadow_offset,
+                                    info->hash_key.border_style);
             if (error)
                 info->symbol = 0;
 
@@ -2350,6 +1312,61 @@ static void measure_text(ASS_Renderer *render_priv)
 }
 
 /**
+ * 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:
@@ -2357,6 +1374,8 @@ static void measure_text(ASS_Renderer *render_priv)
  * 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(ASS_Renderer *render_priv, double max_text_width)
@@ -2391,7 +1410,7 @@ wrap_lines_smart(ASS_Renderer *render_priv, double max_text_width)
         }
 
         if ((len >= max_text_width)
-            && (render_priv->track->WrapStyle != 2)) {
+            && (render_priv->state.wrap_style != 2)) {
             break_type = 1;
             break_at = last_space;
             if (break_at == -1)
@@ -2432,7 +1451,7 @@ wrap_lines_smart(ASS_Renderer *render_priv, double max_text_width)
     }
 #define DIFF(x,y) (((x) < (y)) ? (y - x) : (x - y))
     exit = 0;
-    while (!exit) {
+    while (!exit && render_priv->state.wrap_style != 1) {
         exit = 1;
         w = s3 = text_info->glyphs;
         s1 = s2 = 0;
@@ -2486,13 +1505,23 @@ wrap_lines_smart(ASS_Renderer *render_priv, double max_text_width)
 #undef DIFF
 
     measure_text(render_priv);
+    trim_whitespace(render_priv);
 
     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;
@@ -2620,9 +1649,9 @@ static void get_base_point(DBBox *bbox, int alignment, double *bx, double *by)
  * onto the screen plane.
  */
 static void
-transform_3d_points(FT_Vector shift, FT_Glyph glyph, double frx,
-                    double fry, double frz, double fax, double fay,
-                    double scale)
+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);
@@ -2637,7 +1666,7 @@ transform_3d_points(FT_Vector shift, FT_Glyph glyph, double frx,
 
     dist = 20000 * scale;
     for (i = 0; i < outline->n_points; i++) {
-        x = (double) p[i].x + shift.x + (-fax * p[i].y);
+        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.;
 
@@ -2675,18 +1704,18 @@ transform_3d_points(FT_Vector shift, FT_Glyph glyph, double frx,
 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)
+             double scale, int yshift)
 {
     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);
+                                fax, fay, scale, yshift);
 
         if (glyph2 && *glyph2)
             transform_3d_points(shift, *glyph2, frx, fry, frz,
-                                fax, fay, scale);
+                                fax, fay, scale, yshift);
     }
 }
 
@@ -2711,6 +1740,7 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event,
     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;
@@ -2774,13 +1804,15 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event,
         }
 
         // Add kerning to pen
-        if (previous && code && !drawing->hash) {
+        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;
-            pen.y += delta.y * render_priv->state.scale_y;
+            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;
         }
 
         ass_font_set_transform(render_priv->state.font,
@@ -2791,14 +1823,30 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event,
         get_outline_glyph(render_priv, code,
                           text_info->glyphs + text_info->length, drawing);
 
+        // 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;
+            }
+        }
+
         text_info->glyphs[text_info->length].pos.x = pen.x;
         text_info->glyphs[text_info->length].pos.y = pen.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->font_scale
+                              * render_priv->state.scale_x);
         pen.y += text_info->glyphs[text_info->length].advance.y;
-        pen.y += render_priv->state.fay *
+        pen.y += (render_priv->state.fay * render_priv->state.scale_y) *
                  text_info->glyphs[text_info->length].advance.x;
 
         previous = code;
@@ -2880,6 +1928,8 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event,
             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 -
@@ -2890,6 +1940,8 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event,
                 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;
 
         text_info->length++;
 
@@ -2953,9 +2005,13 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event,
                     text_info->glyphs + last_break + 1;
                 GlyphInfo *last_glyph = text_info->glyphs + i - 1;
 
+                while (first_glyph < last_glyph && first_glyph->skip)
+                    first_glyph++;
+
                 while ((last_glyph > first_glyph)
                        && ((last_glyph->symbol == '\n')
-                           || (last_glyph->symbol == 0)))
+                           || (last_glyph->symbol == 0)
+                           || (last_glyph->skip)))
                     last_glyph--;
 
                 width = d6_to_double(
@@ -3126,6 +2182,8 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event,
     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;
@@ -3342,21 +2400,22 @@ static int cmp_event_layer(const void *p1, const void *p2)
 }
 
 static ASS_RenderPriv *get_render_priv(ASS_Renderer *render_priv,
-                                      ASS_Event *event)
+                                       ASS_Event *event)
 {
     if (!event->render_priv)
         event->render_priv = calloc(1, sizeof(ASS_RenderPriv));
-    // FIXME: check render_id
     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;
     }
+
     return event->render_priv;
 }
 
 static int overlap(Segment *s1, Segment *s2)
 {
-    if (s1->a >= s2->b || s2->a >= s1->b)
+    if (s1->a >= s2->b || s2->a >= s1->b ||
+        s1->ha >= s2->hb || s2->ha >= s1->hb)
         return 0;
     return 1;
 }
@@ -3401,18 +2460,22 @@ static int fit_segment(Segment *s, Segment *fixed, int *cnt, int dir)
 
     if (dir == 1)               // move down
         for (i = 0; i < *cnt; ++i) {
-            if (s->b + shift <= fixed[i].a || s->a + shift >= fixed[i].b)
+            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)
+            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;
+    fixed[*cnt].ha = s->ha;
+    fixed[*cnt].hb = s->hb;
     (*cnt)++;
     qsort(fixed, *cnt, sizeof(Segment), cmp_segment);
 
@@ -3436,20 +2499,28 @@ fix_collisions(ASS_Renderer *render_priv, EventImages *imgs, int cnt)
             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);
             }
@@ -3468,13 +2539,16 @@ fix_collisions(ASS_Renderer *render_priv, EventImages *imgs, int cnt)
             Segment 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);
+            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;
         }
 
     }
@@ -3554,7 +2628,7 @@ static int ass_detect_change(ASS_Renderer *priv)
  *        Can be NULL, in that case no detection is performed.
  */
 ASS_Image *ass_render_frame(ASS_Renderer *priv, ASS_Track *track,
-                              long long now, int *detect_change)
+                            long long now, int *detect_change)
 {
     int i, cnt, rc;
     EventImages *last;
diff --git a/libass/ass_render.h b/libass/ass_render.h
new file mode 100644
index 0000000..6d9db23
--- /dev/null
+++ b/libass/ass_render.h
@@ -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 */
diff --git a/libass/ass_types.h b/libass/ass_types.h
index d6a0111..63bc36c 100644
--- a/libass/ass_types.h
+++ b/libass/ass_types.h
@@ -113,6 +113,7 @@ typedef struct ass_track {
     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
diff --git a/libass/ass_utils.c b/libass/ass_utils.c
index e8fce67..6ca78b8 100644
--- a/libass/ass_utils.c
+++ b/libass/ass_utils.c
@@ -74,11 +74,12 @@ int mystrtod(char **p, double *res)
         return 0;
 }
 
-int strtocolor(ASS_Library *library, 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;
+    int base = hex ? 16 : 10;
 
     if (*p == '&')
         ++p;
@@ -89,7 +90,7 @@ int strtocolor(ASS_Library *library, char **q, uint32_t *res)
         ++p;
         result = mystrtou32(&p, 16, &color);
     } else {
-        result = mystrtou32(&p, 0, &color);
+        result = mystrtou32(&p, base, &color);
     }
 
     {
diff --git a/libass/ass_utils.h b/libass/ass_utils.h
index 8590bb4..bade578 100644
--- a/libass/ass_utils.h
+++ b/libass/ass_utils.h
@@ -49,7 +49,7 @@ 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 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, ...);

-- 
LibASS packaging



More information about the pkg-multimedia-commits mailing list