changeset 34295:6e7f60f6f9d4

Update libass to 0.10 release. Patch by [subjunk gmail com], see bug #2008.
author reimar
date Sat, 03 Dec 2011 21:35:56 +0000
parents d30ea496ed2a
children d9dfe3689e14
files Makefile libass/ass.c libass/ass.h libass/ass_bitmap.c libass/ass_bitmap.h libass/ass_cache.c libass/ass_cache.h libass/ass_cache_template.h libass/ass_drawing.c libass/ass_drawing.h libass/ass_font.c libass/ass_font.h libass/ass_library.c libass/ass_parse.c libass/ass_parse.h libass/ass_render.c libass/ass_render.h libass/ass_render_api.c libass/ass_types.h
diffstat 19 files changed, 1391 insertions(+), 1198 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Sat Dec 03 21:33:28 2011 +0000
+++ b/Makefile	Sat Dec 03 21:35:56 2011 +0000
@@ -115,6 +115,7 @@
                                         libass/ass_parse.c \
                                         libass/ass_render.c \
                                         libass/ass_render_api.c \
+                                        libass/ass_shaper.c \
                                         libass/ass_strtod.c \
                                         libass/ass_utils.c \
 
--- a/libass/ass.c	Sat Dec 03 21:33:28 2011 +0000
+++ b/libass/ass.c	Sat Dec 03 21:35:56 2011 +0000
@@ -28,6 +28,7 @@
 #include <sys/stat.h>
 #include <unistd.h>
 #include <inttypes.h>
+#include <ctype.h>
 
 #ifdef CONFIG_ICONV
 #include <iconv.h>
@@ -69,6 +70,7 @@
     }
     free(track->style_format);
     free(track->event_format);
+    free(track->Language);
     if (track->styles) {
         for (i = 0; i < track->n_styles; ++i)
             ass_free_style(track, i);
@@ -595,6 +597,12 @@
         track->ScaledBorderAndShadow = parse_bool(str + 22);
     } else if (!strncmp(str, "Kerning:", 8)) {
         track->Kerning = parse_bool(str + 8);
+    } else if (!strncmp(str, "Language:", 9)) {
+        char *p = str + 9;
+        while (*p && isspace(*p)) p++;
+        track->Language = malloc(3);
+        strncpy(track->Language, p, 2);
+        track->Language[2] = 0;
     }
     return 0;
 }
@@ -1269,3 +1277,36 @@
     track->parser_priv = calloc(1, sizeof(ASS_ParserPriv));
     return track;
 }
+
+/**
+ * \brief Prepare track for rendering
+ */
+void ass_lazy_track_init(ASS_Library *lib, ASS_Track *track)
+{
+    if (track->PlayResX && track->PlayResY)
+        return;
+    if (!track->PlayResX && !track->PlayResY) {
+        ass_msg(lib, MSGL_WARN,
+               "Neither PlayResX nor PlayResY defined. Assuming 384x288");
+        track->PlayResX = 384;
+        track->PlayResY = 288;
+    } else {
+        if (!track->PlayResY && track->PlayResX == 1280) {
+            track->PlayResY = 1024;
+            ass_msg(lib, MSGL_WARN,
+                   "PlayResY undefined, setting to %d", track->PlayResY);
+        } else if (!track->PlayResY) {
+            track->PlayResY = track->PlayResX * 3 / 4;
+            ass_msg(lib, MSGL_WARN,
+                   "PlayResY undefined, setting to %d", track->PlayResY);
+        } else if (!track->PlayResX && track->PlayResY == 1024) {
+            track->PlayResX = 1280;
+            ass_msg(lib, MSGL_WARN,
+                   "PlayResX undefined, setting to %d", track->PlayResX);
+        } else if (!track->PlayResX) {
+            track->PlayResX = track->PlayResY * 4 / 3;
+            ass_msg(lib, MSGL_WARN,
+                   "PlayResX undefined, setting to %d", track->PlayResX);
+        }
+    }
+}
--- a/libass/ass.h	Sat Dec 03 21:33:28 2011 +0000
+++ b/libass/ass.h	Sat Dec 03 21:35:56 2011 +0000
@@ -23,7 +23,7 @@
 #include <stdarg.h>
 #include "ass_types.h"
 
-#define LIBASS_VERSION 0x00913000
+#define LIBASS_VERSION 0x01000000
 
 /*
  * A linked list of images produced by an ass renderer.
@@ -61,6 +61,19 @@
 } ASS_Hinting;
 
 /**
+ * \brief Text shaping levels.
+ *
+ * SIMPLE is a fast, font-agnostic shaper that can do only substitutions.
+ * COMPLEX is a slower shaper using OpenType for substitutions and positioning.
+ *
+ * libass uses the best shaper available by default.
+ */
+typedef enum {
+    ASS_SHAPING_SIMPLE = 0,
+    ASS_SHAPING_COMPLEX
+} ASS_ShapingLevel;
+
+/**
  * \brief Initialize the library.
  * \return library handle or NULL if failed
  */
@@ -147,6 +160,13 @@
 void ass_set_frame_size(ASS_Renderer *priv, int w, int h);
 
 /**
+ * \brief Set shaping level. This is merely a hint, the renderer will use
+ * whatever is available if the request cannot be fulfilled.
+ * \param level shaping level
+ */
+void ass_set_shaper(ASS_Renderer *priv, ASS_ShapingLevel level);
+
+/**
  * \brief Set frame margins.  These values may be negative if pan-and-scan
  * is used.
  * \param priv renderer handle
--- a/libass/ass_bitmap.c	Sat Dec 03 21:33:28 2011 +0000
+++ b/libass/ass_bitmap.c	Sat Dec 03 21:35:56 2011 +0000
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
+ * Copyright (C) 2011 Grigori Goronzy <greg@chown.ath.cx>
  *
  * This file is part of libass.
  *
@@ -22,6 +23,7 @@
 #include <assert.h>
 #include <ft2build.h>
 #include FT_GLYPH_H
+#include FT_OUTLINE_H
 
 #include "ass_utils.h"
 #include "ass_bitmap.h"
@@ -133,10 +135,12 @@
 static Bitmap *alloc_bitmap(int w, int h)
 {
     Bitmap *bm;
+    unsigned s = w; // XXX: alignment
     bm = malloc(sizeof(Bitmap));
-    bm->buffer = calloc(w, h);
+    bm->buffer = calloc(s, h);
     bm->w = w;
     bm->h = h;
+    bm->stride = s;
     bm->left = bm->top = 0;
     return bm;
 }
@@ -153,70 +157,57 @@
     Bitmap *dst = alloc_bitmap(src->w, src->h);
     dst->left = src->left;
     dst->top = src->top;
-    memcpy(dst->buffer, src->buffer, src->w * src->h);
+    memcpy(dst->buffer, src->buffer, src->stride * src->h);
     return dst;
 }
 
-int check_glyph_area(ASS_Library *library, FT_Glyph glyph)
+Bitmap *outline_to_bitmap(ASS_Library *library, FT_Library ftlib,
+                          FT_Outline *outline, int bord)
 {
-    FT_BBox bbox;
-    long long dx, dy;
-    FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_TRUNCATE, &bbox);
-    dx = bbox.xMax - bbox.xMin;
-    dy = bbox.yMax - bbox.yMin;
-    if (dx * dy > 8000000) {
-        ass_msg(library, MSGL_WARN, "Glyph bounding box too large: %dx%dpx",
-               (int) dx, (int) dy);
-        return 1;
-    } else
-        return 0;
-}
-
-static Bitmap *glyph_to_bitmap_internal(ASS_Library *library,
-                                          FT_Glyph glyph, int bord)
-{
-    FT_BitmapGlyph bg;
-    FT_Bitmap *bit;
     Bitmap *bm;
     int w, h;
-    unsigned char *src;
-    unsigned char *dst;
-    int i;
     int error;
+    FT_BBox bbox;
+    FT_Bitmap bitmap;
 
-    if (check_glyph_area(library, glyph))
-        return 0;
-    error = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 0);
-    if (error) {
-        ass_msg(library, MSGL_WARN, "FT_Glyph_To_Bitmap error %d",
-               error);
-        return 0;
+    FT_Outline_Get_CBox(outline, &bbox);
+    // move glyph to origin (0, 0)
+    bbox.xMin &= ~63;
+    bbox.yMin &= ~63;
+    FT_Outline_Translate(outline, -bbox.xMin, -bbox.yMin);
+    // bitmap size
+    bbox.xMax = (bbox.xMax + 63) & ~63;
+    bbox.yMax = (bbox.yMax + 63) & ~63;
+    w = (bbox.xMax - bbox.xMin) >> 6;
+    h = (bbox.yMax - bbox.yMin) >> 6;
+    // pen offset
+    bbox.xMin >>= 6;
+    bbox.yMax >>= 6;
+
+    if (w * h > 8000000) {
+        ass_msg(library, MSGL_WARN, "Glyph bounding box too large: %dx%dpx",
+                w, h);
+        return NULL;
     }
 
-    bg = (FT_BitmapGlyph) glyph;
-    bit = &(bg->bitmap);
-    if (bit->pixel_mode != FT_PIXEL_MODE_GRAY) {
-        ass_msg(library, MSGL_WARN, "Unsupported pixel mode: %d",
-               (int) (bit->pixel_mode));
-        FT_Done_Glyph(glyph);
-        return 0;
+    // allocate and set up bitmap
+    bm = alloc_bitmap(w + 2 * bord, h + 2 * bord);
+    bm->left = bbox.xMin - bord;
+    bm->top = -bbox.yMax - bord;
+    bitmap.width = w;
+    bitmap.rows = h;
+    bitmap.pitch = bm->stride;
+    bitmap.buffer = bm->buffer + bord + bm->stride * bord;
+    bitmap.num_grays = 256;
+    bitmap.pixel_mode = FT_PIXEL_MODE_GRAY;
+
+    // render into target bitmap
+    if ((error = FT_Outline_Get_Bitmap(ftlib, outline, &bitmap))) {
+        ass_msg(library, MSGL_WARN, "Failed to rasterize glyph: %d\n", error);
+        ass_free_bitmap(bm);
+        return NULL;
     }
 
-    w = bit->width;
-    h = bit->rows;
-    bm = alloc_bitmap(w + 2 * bord, h + 2 * bord);
-    bm->left = bg->left - bord;
-    bm->top = -bg->top - bord;
-
-    src = bit->buffer;
-    dst = bm->buffer + bord + bm->w * bord;
-    for (i = 0; i < h; ++i) {
-        memcpy(dst, src, w);
-        src += bit->pitch;
-        dst += bm->w;
-    }
-
-    FT_Done_Glyph(glyph);
     return bm;
 }
 
@@ -232,16 +223,16 @@
     const int l = bm_o->left > bm_g->left ? bm_o->left : bm_g->left;
     const int t = bm_o->top > bm_g->top ? bm_o->top : bm_g->top;
     const int r =
-        bm_o->left + bm_o->w <
-        bm_g->left + bm_g->w ? bm_o->left + bm_o->w : bm_g->left + bm_g->w;
+        bm_o->left + bm_o->stride <
+        bm_g->left + bm_g->stride ? bm_o->left + bm_o->stride : bm_g->left + bm_g->stride;
     const int b =
         bm_o->top + bm_o->h <
         bm_g->top + bm_g->h ? bm_o->top + bm_o->h : bm_g->top + bm_g->h;
 
     unsigned char *g =
-        bm_g->buffer + (t - bm_g->top) * bm_g->w + (l - bm_g->left);
+        bm_g->buffer + (t - bm_g->top) * bm_g->stride + (l - bm_g->left);
     unsigned char *o =
-        bm_o->buffer + (t - bm_o->top) * bm_o->w + (l - bm_o->left);
+        bm_o->buffer + (t - bm_o->top) * bm_o->stride + (l - bm_o->left);
 
     for (y = 0; y < b - t; ++y) {
         for (x = 0; x < r - l; ++x) {
@@ -250,8 +241,8 @@
             c_o = o[x];
             o[x] = (c_o > c_g) ? c_o - (c_g / 2) : 0;
         }
-        g += bm_g->w;
-        o += bm_o->w;
+        g += bm_g->stride;
+        o += bm_o->stride;
     }
 }
 
@@ -259,27 +250,30 @@
  * \brief Shift a bitmap by the fraction of a pixel in x and y direction
  * expressed in 26.6 fixed point
  */
-static void shift_bitmap(unsigned char *buf, int w, int h, int shift_x,
-                         int shift_y)
+static void shift_bitmap(Bitmap *bm, int shift_x, int shift_y)
 {
     int x, y, b;
+    int w = bm->w;
+    int h = bm->h;
+    int s = bm->stride;
+    unsigned char *buf = bm->buffer;
 
     // Shift in x direction
     if (shift_x > 0) {
         for (y = 0; y < h; y++) {
             for (x = w - 1; x > 0; x--) {
-                b = (buf[x + y * w - 1] * shift_x) >> 6;
-                buf[x + y * w - 1] -= b;
-                buf[x + y * w] += b;
+                b = (buf[x + y * s - 1] * shift_x) >> 6;
+                buf[x + y * s - 1] -= b;
+                buf[x + y * s] += b;
             }
         }
     } else if (shift_x < 0) {
         shift_x = -shift_x;
         for (y = 0; y < h; y++) {
             for (x = 0; x < w - 1; x++) {
-                b = (buf[x + y * w + 1] * shift_x) >> 6;
-                buf[x + y * w + 1] -= b;
-                buf[x + y * w] += b;
+                b = (buf[x + y * s + 1] * shift_x) >> 6;
+                buf[x + y * s + 1] -= b;
+                buf[x + y * s] += b;
             }
         }
     }
@@ -288,18 +282,18 @@
     if (shift_y > 0) {
         for (x = 0; x < w; x++) {
             for (y = h - 1; y > 0; y--) {
-                b = (buf[x + (y - 1) * w] * shift_y) >> 6;
-                buf[x + (y - 1) * w] -= b;
-                buf[x + y * w] += b;
+                b = (buf[x + (y - 1) * s] * shift_y) >> 6;
+                buf[x + (y - 1) * s] -= b;
+                buf[x + y * s] += b;
             }
         }
     } else if (shift_y < 0) {
         shift_y = -shift_y;
         for (x = 0; x < w; x++) {
             for (y = 0; y < h - 1; y++) {
-                b = (buf[x + (y + 1) * w] * shift_y) >> 6;
-                buf[x + (y + 1) * w] -= b;
-                buf[x + y * w] += b;
+                b = (buf[x + (y + 1) * s] * shift_y) >> 6;
+                buf[x + (y + 1) * s] -= b;
+                buf[x + y * s] += b;
             }
         }
     }
@@ -430,16 +424,20 @@
  * \brief Blur with [[1,2,1]. [2,4,2], [1,2,1]] kernel
  * This blur is the same as the one employed by vsfilter.
  */
-static void be_blur(unsigned char *buf, int w, int h)
+static void be_blur(Bitmap *bm)
 {
+    int w = bm->w;
+    int h = bm->h;
+    int s = bm->stride;
+    unsigned char *buf = bm->buffer;
     unsigned int x, y;
     unsigned int old_sum, new_sum;
 
     for (y = 0; y < h; y++) {
-        old_sum = 2 * buf[y * w];
+        old_sum = 2 * buf[y * s];
         for (x = 0; x < w - 1; x++) {
-            new_sum = buf[y * w + x] + buf[y * w + x + 1];
-            buf[y * w + x] = (old_sum + new_sum) >> 2;
+            new_sum = buf[y * s + x] + buf[y * s + x + 1];
+            buf[y * s + x] = (old_sum + new_sum) >> 2;
             old_sum = new_sum;
         }
     }
@@ -447,18 +445,18 @@
     for (x = 0; x < w; x++) {
         old_sum = 2 * buf[x];
         for (y = 0; y < h - 1; y++) {
-            new_sum = buf[y * w + x] + buf[(y + 1) * w + x];
-            buf[y * w + x] = (old_sum + new_sum) >> 2;
+            new_sum = buf[y * s + x] + buf[(y + 1) * s + x];
+            buf[y * s + x] = (old_sum + new_sum) >> 2;
             old_sum = new_sum;
         }
     }
 }
 
-int glyph_to_bitmap(ASS_Library *library, ASS_SynthPriv *priv_blur,
-                    FT_Glyph glyph, FT_Glyph outline_glyph,
-                    Bitmap **bm_g, Bitmap **bm_o, Bitmap **bm_s,
-                    int be, double blur_radius, FT_Vector shadow_offset,
-                    int border_style)
+int outline_to_bitmap3(ASS_Library *library, ASS_SynthPriv *priv_blur,
+                       FT_Library ftlib, FT_Outline *outline, FT_Outline *border,
+                       Bitmap **bm_g, Bitmap **bm_o, Bitmap **bm_s,
+                       int be, double blur_radius, FT_Vector shadow_offset,
+                       int border_style)
 {
     blur_radius *= 2;
     int bbord = be > 0 ? sqrt(2 * be) : 0;
@@ -471,13 +469,13 @@
 
     *bm_g = *bm_o = *bm_s = 0;
 
-    if (glyph)
-        *bm_g = glyph_to_bitmap_internal(library, glyph, bord);
+    if (outline)
+        *bm_g = outline_to_bitmap(library, ftlib, outline, bord);
     if (!*bm_g)
         return 1;
 
-    if (outline_glyph) {
-        *bm_o = glyph_to_bitmap_internal(library, outline_glyph, bord);
+    if (border) {
+        *bm_o = outline_to_bitmap(library, ftlib, border, bord);
         if (!*bm_o) {
             return 1;
         }
@@ -486,9 +484,9 @@
     // Apply box blur (multiple passes, if requested)
     while (be--) {
         if (*bm_o)
-            be_blur((*bm_o)->buffer, (*bm_o)->w, (*bm_o)->h);
+            be_blur(*bm_o);
         else
-            be_blur((*bm_g)->buffer, (*bm_g)->w, (*bm_g)->h);
+            be_blur(*bm_g);
     }
 
     // Apply gaussian blur
@@ -500,12 +498,12 @@
         generate_tables(priv_blur, blur_radius);
         if (*bm_o)
             ass_gauss_blur((*bm_o)->buffer, priv_blur->tmp,
-                           (*bm_o)->w, (*bm_o)->h, (*bm_o)->w,
+                           (*bm_o)->w, (*bm_o)->h, (*bm_o)->stride,
                            (int *) priv_blur->gt2, priv_blur->g_r,
                            priv_blur->g_w);
         else
             ass_gauss_blur((*bm_g)->buffer, priv_blur->tmp,
-                           (*bm_g)->w, (*bm_g)->h, (*bm_g)->w,
+                           (*bm_g)->w, (*bm_g)->h, (*bm_g)->stride,
                            (int *) priv_blur->gt2, priv_blur->g_r,
                            priv_blur->g_w);
     }
@@ -521,8 +519,7 @@
 
     assert(bm_s);
 
-    shift_bitmap((*bm_s)->buffer, (*bm_s)->w,(*bm_s)->h,
-                 shadow_offset.x, shadow_offset.y);
+    shift_bitmap(*bm_s, shadow_offset.x, shadow_offset.y);
 
     return 0;
 }
--- a/libass/ass_bitmap.h	Sat Dec 03 21:33:28 2011 +0000
+++ b/libass/ass_bitmap.h	Sat Dec 03 21:35:56 2011 +0000
@@ -32,9 +32,12 @@
 typedef struct {
     int left, top;
     int w, h;                   // width, height
+    int stride;
     unsigned char *buffer;      // w x h buffer
 } Bitmap;
 
+Bitmap *outline_to_bitmap(ASS_Library *library, FT_Library ftlib,
+                          FT_Outline *outline, int bord);
 /**
  * \brief perform glyph rendering
  * \param glyph original glyph
@@ -44,13 +47,12 @@
  * \param bm_g out: pointer to the bitmap of glyph shadow is returned here
  * \param be 1 = produces blurred bitmaps, 0 = normal bitmaps
  */
-int glyph_to_bitmap(ASS_Library *library, ASS_SynthPriv *priv_blur,
-                    FT_Glyph glyph, FT_Glyph outline_glyph,
-                    Bitmap **bm_g, Bitmap **bm_o, Bitmap **bm_s,
-                    int be, double blur_radius, FT_Vector shadow_offset,
-                    int border_style);
+int outline_to_bitmap3(ASS_Library *library, ASS_SynthPriv *priv_blur,
+                       FT_Library ftlib, FT_Outline *outline, FT_Outline *border,
+                       Bitmap **bm_g, Bitmap **bm_o, Bitmap **bm_s,
+                       int be, double blur_radius, FT_Vector shadow_offset,
+                       int border_style);
 
 void ass_free_bitmap(Bitmap *bm);
-int check_glyph_area(ASS_Library *library, FT_Glyph glyph);
 
 #endif                          /* LIBASS_BITMAP_H */
--- a/libass/ass_cache.c	Sat Dec 03 21:33:28 2011 +0000
+++ b/libass/ass_cache.c	Sat Dec 03 21:35:56 2011 +0000
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
+ * Copyright (C) 2011 Grigori Goronzy <greg@chown.ath.cx>
  *
  * This file is part of libass.
  *
@@ -20,129 +21,35 @@
 
 #include <inttypes.h>
 #include <ft2build.h>
-#include FT_FREETYPE_H
-#include FT_GLYPH_H
-
+#include FT_OUTLINE_H
 #include <assert.h>
 
 #include "ass_utils.h"
-#include "ass.h"
-#include "ass_fontconfig.h"
 #include "ass_font.h"
-#include "ass_bitmap.h"
 #include "ass_cache.h"
 
-static unsigned hashmap_hash(void *buf, size_t len)
-{
-    return fnv_32a_buf(buf, len, FNV1_32A_INIT);
-}
-
-static int hashmap_key_compare(void *a, void *b, size_t size)
-{
-    return memcmp(a, b, size) == 0;
-}
-
-static void hashmap_item_dtor(void *key, size_t key_size, void *value,
-                              size_t value_size)
-{
-    free(key);
-    free(value);
-}
-
-Hashmap *hashmap_init(ASS_Library *library, size_t key_size,
-                      size_t value_size, int nbuckets,
-                      HashmapItemDtor item_dtor,
-                      HashmapKeyCompare key_compare,
-                      HashmapHash hash)
-{
-    Hashmap *map = calloc(1, sizeof(Hashmap));
-    map->library = library;
-    map->nbuckets = nbuckets;
-    map->key_size = key_size;
-    map->value_size = value_size;
-    map->root = calloc(nbuckets, sizeof(hashmap_item_p));
-    map->item_dtor = item_dtor ? item_dtor : hashmap_item_dtor;
-    map->key_compare = key_compare ? key_compare : hashmap_key_compare;
-    map->hash = hash ? hash : hashmap_hash;
-    return map;
-}
-
-void hashmap_done(Hashmap *map)
-{
-    int i;
-    // print stats
-    if (map->count > 0 || map->hit_count + map->miss_count > 0)
-        ass_msg(map->library, MSGL_V,
-               "cache statistics: \n  total accesses: %d\n  hits: %d\n  "
-               "misses: %d\n  object count: %d",
-               map->hit_count + map->miss_count, map->hit_count,
-               map->miss_count, map->count);
+// type-specific functions
+// create hash/compare functions for bitmap, outline and composite cache
+#define CREATE_HASH_FUNCTIONS
+#include "ass_cache_template.h"
+#define CREATE_COMPARISON_FUNCTIONS
+#include "ass_cache_template.h"
 
-    for (i = 0; i < map->nbuckets; ++i) {
-        HashmapItem *item = map->root[i];
-        while (item) {
-            HashmapItem *next = item->next;
-            map->item_dtor(item->key, map->key_size, item->value,
-                           map->value_size);
-            free(item);
-            item = next;
-        }
-    }
-    free(map->root);
-    free(map);
-}
-
-// does nothing if key already exists
-void *hashmap_insert(Hashmap *map, void *key, void *value)
-{
-    unsigned hash = map->hash(key, map->key_size);
-    HashmapItem **next = map->root + (hash % map->nbuckets);
-    while (*next) {
-        if (map->key_compare(key, (*next)->key, map->key_size))
-            return (*next)->value;
-        next = &((*next)->next);
-        assert(next);
-    }
-    (*next) = malloc(sizeof(HashmapItem));
-    (*next)->key = malloc(map->key_size);
-    (*next)->value = malloc(map->value_size);
-    memcpy((*next)->key, key, map->key_size);
-    memcpy((*next)->value, value, map->value_size);
-    (*next)->next = 0;
-
-    map->count++;
-    return (*next)->value;
-}
-
-void *hashmap_find(Hashmap *map, void *key)
-{
-    unsigned hash = map->hash(key, map->key_size);
-    HashmapItem *item = map->root[hash % map->nbuckets];
-    while (item) {
-        if (map->key_compare(key, item->key, map->key_size)) {
-            map->hit_count++;
-            return item->value;
-        }
-        item = item->next;
-    }
-    map->miss_count++;
-    return 0;
-}
-
-//---------------------------------
 // font cache
-
-static unsigned font_desc_hash(void *buf, size_t len)
+static unsigned font_hash(void *buf, size_t len)
 {
     ASS_FontDesc *desc = buf;
     unsigned hval;
     hval = fnv_32a_str(desc->family, FNV1_32A_INIT);
     hval = fnv_32a_buf(&desc->bold, sizeof(desc->bold), hval);
     hval = fnv_32a_buf(&desc->italic, sizeof(desc->italic), hval);
+    hval = fnv_32a_buf(&desc->treat_family_as_pattern,
+            sizeof(desc->treat_family_as_pattern), hval);
+    hval = fnv_32a_buf(&desc->vertical, sizeof(desc->vertical), hval);
     return hval;
 }
 
-static int font_compare(void *key1, void *key2, size_t key_size)
+static unsigned font_compare(void *key1, void *key2, size_t key_size)
 {
     ASS_FontDesc *a = key1;
     ASS_FontDesc *b = key2;
@@ -159,181 +66,63 @@
     return 1;
 }
 
-static void font_hash_dtor(void *key, size_t key_size, void *value,
-                           size_t value_size)
+static void font_destruct(void *key, void *value)
 {
     ass_font_free(value);
     free(key);
 }
 
-ASS_Font *ass_font_cache_find(Hashmap *font_cache,
-                              ASS_FontDesc *desc)
-{
-    return hashmap_find(font_cache, desc);
-}
-
-/**
- * \brief Add a face struct to cache.
- * \param font font struct
-*/
-void *ass_font_cache_add(Hashmap *font_cache, ASS_Font *font)
-{
-    return hashmap_insert(font_cache, &(font->desc), font);
-}
-
-Hashmap *ass_font_cache_init(ASS_Library *library)
-{
-    Hashmap *font_cache;
-    font_cache = hashmap_init(library, sizeof(ASS_FontDesc),
-                              sizeof(ASS_Font),
-                              1000,
-                              font_hash_dtor, font_compare, font_desc_hash);
-    return font_cache;
-}
-
-void ass_font_cache_done(Hashmap *font_cache)
-{
-    hashmap_done(font_cache);
-}
-
-
-// Create hash/compare functions for bitmap and glyph
-#define CREATE_HASH_FUNCTIONS
-#include "ass_cache_template.h"
-#define CREATE_COMPARISON_FUNCTIONS
-#include "ass_cache_template.h"
-
-//---------------------------------
 // bitmap cache
-
-static void bitmap_hash_dtor(void *key, size_t key_size, void *value,
-                             size_t value_size)
+static void bitmap_destruct(void *key, void *value)
 {
     BitmapHashValue *v = value;
+    BitmapHashKey *k = key;
     if (v->bm)
         ass_free_bitmap(v->bm);
     if (v->bm_o)
         ass_free_bitmap(v->bm_o);
     if (v->bm_s)
         ass_free_bitmap(v->bm_s);
+    if (k->type == BITMAP_CLIP)
+        free(k->u.clip.text);
     free(key);
     free(value);
 }
 
-void *cache_add_bitmap(Hashmap *bitmap_cache, BitmapHashKey *key,
-                       BitmapHashValue *val)
-{
-    // Note: this is only an approximation
-    if (val->bm_o)
-        bitmap_cache->cache_size += val->bm_o->w * val->bm_o->h * 3;
-    else if (val->bm)
-        bitmap_cache->cache_size += val->bm->w * val->bm->h * 3;
-
-    return hashmap_insert(bitmap_cache, key, val);
-}
-
-/**
- * \brief Get a bitmap from bitmap cache.
- * \param key hash key
- * \return requested hash val or 0 if not found
-*/
-BitmapHashValue *cache_find_bitmap(Hashmap *bitmap_cache,
-                                   BitmapHashKey *key)
+static size_t bitmap_size(void *value, size_t value_size)
 {
-    return hashmap_find(bitmap_cache, key);
-}
-
-Hashmap *ass_bitmap_cache_init(ASS_Library *library)
-{
-    Hashmap *bitmap_cache;
-    bitmap_cache = hashmap_init(library,
-                                sizeof(BitmapHashKey),
-                                sizeof(BitmapHashValue),
-                                0xFFFF + 13,
-                                bitmap_hash_dtor, bitmap_compare,
-                                bitmap_hash);
-    return bitmap_cache;
-}
-
-void ass_bitmap_cache_done(Hashmap *bitmap_cache)
-{
-    hashmap_done(bitmap_cache);
-}
-
-Hashmap *ass_bitmap_cache_reset(Hashmap *bitmap_cache)
-{
-    ASS_Library *lib = bitmap_cache->library;
-
-    ass_bitmap_cache_done(bitmap_cache);
-    return ass_bitmap_cache_init(lib);
+    BitmapHashValue *val = value;
+    if (val->bm_o)
+        return val->bm_o->w * val->bm_o->h * 3;
+    else if (val->bm)
+        return val->bm->w * val->bm->h * 3;
+    return 0;
 }
 
-//---------------------------------
-// glyph cache
-
-static void glyph_hash_dtor(void *key, size_t key_size, void *value,
-                            size_t value_size)
+static unsigned bitmap_hash(void *key, size_t key_size)
 {
-    GlyphHashValue *v = value;
-    if (v->glyph)
-        FT_Done_Glyph(v->glyph);
-    if (v->outline_glyph)
-        FT_Done_Glyph(v->outline_glyph);
-    free(key);
-    free(value);
-}
-
-void *cache_add_glyph(Hashmap *glyph_cache, GlyphHashKey *key,
-                      GlyphHashValue *val)
-{
-	if (val->glyph && val->glyph->format == FT_GLYPH_FORMAT_BITMAP) {
-		FT_Bitmap *bitmap = &((FT_BitmapGlyph) val->glyph)->bitmap;
-		glyph_cache->cache_size += bitmap->rows * bitmap->pitch;
-	}
-
-    return hashmap_insert(glyph_cache, key, val);
+    BitmapHashKey *k = key;
+    switch (k->type) {
+        case BITMAP_OUTLINE: return outline_bitmap_hash(&k->u, key_size);
+        case BITMAP_CLIP: return clip_bitmap_hash(&k->u, key_size);
+        default: return 0;
+    }
 }
 
-/**
- * \brief Get a glyph from glyph cache.
- * \param key hash key
- * \return requested hash val or 0 if not found
-*/
-GlyphHashValue *cache_find_glyph(Hashmap *glyph_cache,
-                                 GlyphHashKey *key)
+static unsigned bitmap_compare (void *a, void *b, size_t key_size)
 {
-    return hashmap_find(glyph_cache, key);
+    BitmapHashKey *ak = a;
+    BitmapHashKey *bk = b;
+    if (ak->type != bk->type) return 0;
+    switch (ak->type) {
+        case BITMAP_OUTLINE: return outline_bitmap_compare(&ak->u, &bk->u, key_size);
+        case BITMAP_CLIP: return clip_bitmap_compare(&ak->u, &bk->u, key_size);
+        default: return 0;
+    }
 }
 
-Hashmap *ass_glyph_cache_init(ASS_Library *library)
-{
-    Hashmap *glyph_cache;
-    glyph_cache = hashmap_init(library, sizeof(GlyphHashKey),
-                               sizeof(GlyphHashValue),
-                               0xFFFF + 13,
-                               glyph_hash_dtor, glyph_compare, glyph_hash);
-    return glyph_cache;
-}
-
-void ass_glyph_cache_done(Hashmap *glyph_cache)
-{
-    hashmap_done(glyph_cache);
-}
-
-Hashmap *ass_glyph_cache_reset(Hashmap *glyph_cache)
-{
-    ASS_Library *lib = glyph_cache->library;
-
-    ass_glyph_cache_done(glyph_cache);
-    return ass_glyph_cache_init(lib);
-}
-
-
-//---------------------------------
 // composite cache
-
-static void composite_hash_dtor(void *key, size_t key_size, void *value,
-                                size_t value_size)
+static void composite_destruct(void *key, void *value)
 {
     CompositeHashValue *v = value;
     free(v->a);
@@ -342,44 +131,222 @@
     free(value);
 }
 
-void *cache_add_composite(Hashmap *composite_cache,
-                          CompositeHashKey *key,
-                          CompositeHashValue *val)
+// outline cache
+
+static unsigned outline_hash(void *key, size_t key_size)
+{
+    OutlineHashKey *k = key;
+    switch (k->type) {
+        case OUTLINE_GLYPH: return glyph_hash(&k->u, key_size);
+        case OUTLINE_DRAWING: return drawing_hash(&k->u, key_size);
+        default: return 0;
+    }
+}
+
+static unsigned outline_compare(void *a, void *b, size_t key_size)
 {
-    return hashmap_insert(composite_cache, key, val);
+    OutlineHashKey *ak = a;
+    OutlineHashKey *bk = b;
+    if (ak->type != bk->type) return 0;
+    switch (ak->type) {
+        case OUTLINE_GLYPH: return glyph_compare(&ak->u, &bk->u, key_size);
+        case OUTLINE_DRAWING: return drawing_compare(&ak->u, &bk->u, key_size);
+        default: return 0;
+    }
+}
+
+static void outline_destruct(void *key, void *value)
+{
+    OutlineHashValue *v = value;
+    OutlineHashKey *k = key;
+    if (v->outline)
+        outline_free(v->lib, v->outline);
+    if (v->border)
+        outline_free(v->lib, v->border);
+    if (k->type == OUTLINE_DRAWING)
+        free(k->u.drawing.text);
+    free(key);
+    free(value);
 }
 
-/**
- * \brief Get a composite bitmap from composite cache.
- * \param key hash key
- * \return requested hash val or 0 if not found
-*/
-CompositeHashValue *cache_find_composite(Hashmap *composite_cache,
-                                         CompositeHashKey *key)
+
+
+// Cache data
+typedef struct cache_item {
+    void *key;
+    void *value;
+    struct cache_item *next;
+} CacheItem;
+
+struct cache {
+    unsigned buckets;
+    CacheItem **map;
+
+    HashFunction hash_func;
+    ItemSize size_func;
+    HashCompare compare_func;
+    CacheItemDestructor destruct_func;
+    size_t key_size;
+    size_t value_size;
+
+    size_t cache_size;
+    unsigned hits;
+    unsigned misses;
+    unsigned items;
+};
+
+// Hash for a simple (single value or array) type
+static unsigned hash_simple(void *key, size_t key_size)
 {
-    return hashmap_find(composite_cache, key);
+    return fnv_32a_buf(key, key_size, FNV1_32A_INIT);
+}
+
+// Comparison of a simple type
+static unsigned compare_simple(void *a, void *b, size_t key_size)
+{
+    return memcmp(a, b, key_size) == 0;
+}
+
+// Default destructor
+static void destruct_simple(void *key, void *value)
+{
+    free(key);
+    free(value);
+}
+
+
+// Create a cache with type-specific hash/compare/destruct/size functions
+Cache *ass_cache_create(HashFunction hash_func, HashCompare compare_func,
+                        CacheItemDestructor destruct_func, ItemSize size_func,
+                        size_t key_size, size_t value_size)
+{
+    Cache *cache = calloc(1, sizeof(*cache));
+    cache->buckets = 0xFFFF;
+    cache->hash_func = hash_simple;
+    cache->compare_func = compare_simple;
+    cache->destruct_func = destruct_simple;
+    cache->size_func = size_func;
+    if (hash_func)
+        cache->hash_func = hash_func;
+    if (compare_func)
+        cache->compare_func = compare_func;
+    if (destruct_func)
+        cache->destruct_func = destruct_func;
+    cache->key_size = key_size;
+    cache->value_size = value_size;
+    cache->map = calloc(cache->buckets, sizeof(CacheItem *));
+
+    return cache;
 }
 
-Hashmap *ass_composite_cache_init(ASS_Library *library)
+void *ass_cache_put(Cache *cache, void *key, void *value)
 {
-    Hashmap *composite_cache;
-    composite_cache = hashmap_init(library, sizeof(CompositeHashKey),
-                                   sizeof(CompositeHashValue),
-                                   0xFFFF + 13,
-                                   composite_hash_dtor, composite_compare,
-                                   composite_hash);
-    return composite_cache;
+    unsigned bucket = cache->hash_func(key, cache->key_size) % cache->buckets;
+    CacheItem **item = &cache->map[bucket];
+    while (*item)
+        item = &(*item)->next;
+    (*item) = calloc(1, sizeof(CacheItem));
+    (*item)->key = malloc(cache->key_size);
+    (*item)->value = malloc(cache->value_size);
+    memcpy((*item)->key, key, cache->key_size);
+    memcpy((*item)->value, value, cache->value_size);
+
+    cache->items++;
+    if (cache->size_func)
+        cache->cache_size += cache->size_func(value, cache->value_size);
+    else
+        cache->cache_size++;
+
+    return (*item)->value;
+}
+
+void *ass_cache_get(Cache *cache, void *key)
+{
+    unsigned bucket = cache->hash_func(key, cache->key_size) % cache->buckets;
+    CacheItem *item = cache->map[bucket];
+    while (item) {
+        if (cache->compare_func(key, item->key, cache->key_size)) {
+            cache->hits++;
+            return item->value;
+        }
+        item = item->next;
+    }
+    cache->misses++;
+    return NULL;
 }
 
-void ass_composite_cache_done(Hashmap *composite_cache)
+int ass_cache_empty(Cache *cache, size_t max_size)
 {
-    hashmap_done(composite_cache);
+    int i;
+
+    if (cache->cache_size < max_size)
+        return 0;
+
+    for (i = 0; i < cache->buckets; i++) {
+        CacheItem *item = cache->map[i];
+        while (item) {
+            CacheItem *next = item->next;
+            cache->destruct_func(item->key, item->value);
+            free(item);
+            item = next;
+        }
+        cache->map[i] = NULL;
+    }
+
+    cache->items = cache->hits = cache->misses = cache->cache_size = 0;
+
+    return 1;
+}
+
+void ass_cache_stats(Cache *cache, size_t *size, unsigned *hits,
+                     unsigned *misses, unsigned *count)
+{
+    if (size)
+        *size = cache->cache_size;
+    if (hits)
+        *hits = cache->hits;
+    if (misses)
+        *misses = cache->misses;
+    if (count)
+        *count = cache->items;
 }
 
-Hashmap *ass_composite_cache_reset(Hashmap *composite_cache)
+void ass_cache_done(Cache *cache)
+{
+    ass_cache_empty(cache, 0);
+    free(cache->map);
+    free(cache);
+}
+
+// Type-specific creation function
+Cache *ass_font_cache_create(void)
 {
-    ASS_Library *lib = composite_cache->library;
+    return ass_cache_create(font_hash, font_compare, font_destruct,
+            (ItemSize)NULL, sizeof(ASS_FontDesc), sizeof(ASS_Font));
+}
+
+Cache *ass_outline_cache_create(void)
+{
+    return ass_cache_create(outline_hash, outline_compare, outline_destruct,
+            NULL, sizeof(OutlineHashKey), sizeof(OutlineHashValue));
+}
 
-    ass_composite_cache_done(composite_cache);
-    return ass_composite_cache_init(lib);
+Cache *ass_glyph_metrics_cache_create(void)
+{
+    return ass_cache_create(glyph_metrics_hash, glyph_metrics_compare, NULL,
+            (ItemSize) NULL, sizeof(GlyphMetricsHashKey),
+            sizeof(GlyphMetricsHashValue));
 }
+
+Cache *ass_bitmap_cache_create(void)
+{
+    return ass_cache_create(bitmap_hash, bitmap_compare, bitmap_destruct,
+            bitmap_size, sizeof(BitmapHashKey), sizeof(BitmapHashValue));
+}
+
+Cache *ass_composite_cache_create(void)
+{
+    return ass_cache_create(composite_hash, composite_compare,
+            composite_destruct, (ItemSize)NULL, sizeof(CompositeHashKey),
+            sizeof(CompositeHashValue));
+}
--- a/libass/ass_cache.h	Sat Dec 03 21:33:28 2011 +0000
+++ b/libass/ass_cache.h	Sat Dec 03 21:35:56 2011 +0000
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
+ * Copyright (C) 2011 Grigori Goronzy <greg@chown.ath.cx>
  *
  * This file is part of libass.
  *
@@ -23,51 +24,9 @@
 #include "ass_font.h"
 #include "ass_bitmap.h"
 
-typedef void (*HashmapItemDtor) (void *key, size_t key_size,
-                                 void *value, size_t value_size);
-typedef int (*HashmapKeyCompare) (void *key1, void *key2,
-                                  size_t key_size);
-typedef unsigned (*HashmapHash) (void *key, size_t key_size);
-
-typedef struct hashmap_item {
-    void *key;
-    void *value;
-    struct hashmap_item *next;
-} HashmapItem;
-typedef HashmapItem *hashmap_item_p;
+typedef struct cache Cache;
 
-typedef struct {
-    int nbuckets;
-    size_t key_size, value_size;
-    hashmap_item_p *root;
-    HashmapItemDtor item_dtor;      // a destructor for hashmap key/value pairs
-    HashmapKeyCompare key_compare;
-    HashmapHash hash;
-    size_t cache_size;
-    // stats
-    int hit_count;
-    int miss_count;
-    int count;
-    ASS_Library *library;
-} Hashmap;
-
-Hashmap *hashmap_init(ASS_Library *library, size_t key_size,
-                      size_t value_size, int nbuckets,
-                      HashmapItemDtor item_dtor,
-                      HashmapKeyCompare key_compare,
-                      HashmapHash hash);
-void hashmap_done(Hashmap *map);
-void *hashmap_insert(Hashmap *map, void *key, void *value);
-void *hashmap_find(Hashmap *map, void *key);
-
-Hashmap *ass_font_cache_init(ASS_Library *library);
-ASS_Font *ass_font_cache_find(Hashmap *, ASS_FontDesc *desc);
-void *ass_font_cache_add(Hashmap *, ASS_Font *font);
-void ass_font_cache_done(Hashmap *);
-
-// Create definitions for bitmap_hash_key and glyph_hash_key
-#define CREATE_STRUCT_DEFINITIONS
-#include "ass_cache_template.h"
+// cache values
 
 typedef struct {
     Bitmap *bm;               // the actual bitmaps
@@ -75,43 +34,71 @@
     Bitmap *bm_s;
 } BitmapHashValue;
 
-Hashmap *ass_bitmap_cache_init(ASS_Library *library);
-void *cache_add_bitmap(Hashmap *, BitmapHashKey *key,
-                       BitmapHashValue *val);
-BitmapHashValue *cache_find_bitmap(Hashmap *bitmap_cache,
-                                   BitmapHashKey *key);
-Hashmap *ass_bitmap_cache_reset(Hashmap *bitmap_cache);
-void ass_bitmap_cache_done(Hashmap *bitmap_cache);
-
-
 typedef struct {
     unsigned char *a;
     unsigned char *b;
 } CompositeHashValue;
 
-Hashmap *ass_composite_cache_init(ASS_Library *library);
-void *cache_add_composite(Hashmap *, CompositeHashKey *key,
-                          CompositeHashValue *val);
-CompositeHashValue *cache_find_composite(Hashmap *composite_cache,
-                                         CompositeHashKey *key);
-Hashmap *ass_composite_cache_reset(Hashmap *composite_cache);
-void ass_composite_cache_done(Hashmap *composite_cache);
-
+typedef struct {
+    FT_Library lib;
+    FT_Outline *outline;
+    FT_Outline *border;
+    FT_BBox bbox_scaled;        // bbox after scaling, but before rotation
+    FT_Vector advance;          // 26.6, advance distance to the next outline in line
+    int asc, desc;              // ascender/descender
+} OutlineHashValue;
 
 typedef struct {
-    FT_Glyph glyph;
-    FT_Glyph outline_glyph;
-    FT_BBox bbox_scaled;        // bbox after scaling, but before rotation
-    FT_Vector advance;          // 26.6, advance distance to the next bitmap in line
-    int asc, desc;              // ascender/descender of a drawing
-} GlyphHashValue;
+    FT_Glyph_Metrics metrics;
+} GlyphMetricsHashValue;
+
+// Create definitions for bitmap, outline and composite hash keys
+#define CREATE_STRUCT_DEFINITIONS
+#include "ass_cache_template.h"
+
+// Type-specific function pointers
+typedef unsigned(*HashFunction)(void *key, size_t key_size);
+typedef size_t(*ItemSize)(void *value, size_t value_size);
+typedef unsigned(*HashCompare)(void *a, void *b, size_t key_size);
+typedef void(*CacheItemDestructor)(void *key, void *value);
+
+// cache hash keys
+
+typedef struct outline_hash_key {
+    enum {
+        OUTLINE_GLYPH,
+        OUTLINE_DRAWING,
+    } type;
+    union {
+        GlyphHashKey glyph;
+        DrawingHashKey drawing;
+    } u;
+} OutlineHashKey;
 
-Hashmap *ass_glyph_cache_init(ASS_Library *library);
-void *cache_add_glyph(Hashmap *, GlyphHashKey *key,
-                      GlyphHashValue *val);
-GlyphHashValue *cache_find_glyph(Hashmap *glyph_cache,
-                                 GlyphHashKey *key);
-Hashmap *ass_glyph_cache_reset(Hashmap *glyph_cache);
-void ass_glyph_cache_done(Hashmap *glyph_cache);
+typedef struct bitmap_hash_key {
+    enum {
+        BITMAP_OUTLINE,
+        BITMAP_CLIP,
+    } type;
+    union {
+        OutlineBitmapHashKey outline;
+        ClipMaskHashKey clip;
+    } u;
+} BitmapHashKey;
+
+Cache *ass_cache_create(HashFunction hash_func, HashCompare compare_func,
+                        CacheItemDestructor destruct_func, ItemSize size_func,
+                        size_t key_size, size_t value_size);
+void *ass_cache_put(Cache *cache, void *key, void *value);
+void *ass_cache_get(Cache *cache, void *key);
+int ass_cache_empty(Cache *cache, size_t max_size);
+void ass_cache_stats(Cache *cache, size_t *size, unsigned *hits,
+                     unsigned *misses, unsigned *count);
+void ass_cache_done(Cache *cache);
+Cache *ass_font_cache_create(void);
+Cache *ass_outline_cache_create(void);
+Cache *ass_glyph_metrics_cache_create(void);
+Cache *ass_bitmap_cache_create(void);
+Cache *ass_composite_cache_create(void);
 
 #endif                          /* LIBASS_CACHE_H */
--- a/libass/ass_cache_template.h	Sat Dec 03 21:33:28 2011 +0000
+++ b/libass/ass_cache_template.h	Sat Dec 03 21:35:56 2011 +0000
@@ -4,6 +4,8 @@
     typedef struct structname {
 #define GENERIC(type, member) \
         type member;
+#define STRING(member) \
+        char *member;
 #define FTVECTOR(member) \
         FT_Vector member;
 #define BITMAPHASHKEY(member) \
@@ -14,13 +16,15 @@
 #elif defined(CREATE_COMPARISON_FUNCTIONS)
 #undef CREATE_COMPARISON_FUNCTIONS
 #define START(funcname, structname) \
-    static int funcname##_compare(void *key1, void *key2, size_t key_size) \
+    static unsigned funcname##_compare(void *key1, void *key2, size_t key_size) \
     { \
         struct structname *a = key1; \
         struct structname *b = key2; \
         return // conditions follow
 #define GENERIC(type, member) \
             a->member == b->member &&
+#define STRING(member) \
+            strcmp(a->member, b->member) == 0 &&
 #define FTVECTOR(member) \
             a->member.x == b->member.x && a->member.y == b->member.y &&
 #define BITMAPHASHKEY(member) \
@@ -38,6 +42,8 @@
         unsigned hval = FNV1_32A_INIT;
 #define GENERIC(type, member) \
         hval = fnv_32a_buf(&p->member, sizeof(p->member), hval);
+#define STRING(member) \
+        hval = fnv_32a_str(p->member, hval);
 #define FTVECTOR(member) GENERIC(, member.x); GENERIC(, member.y);
 #define BITMAPHASHKEY(member) { \
         unsigned temp = bitmap_hash(&p->member, sizeof(p->member)); \
@@ -53,19 +59,11 @@
 
 
 
-// describes a bitmap; bitmaps with equivalents structs are considered identical
-START(bitmap, bitmap_hash_key)
-    GENERIC(char, bitmap) // bool : true = bitmap, false = outline
-    GENERIC(ASS_Font *, font)
-    GENERIC(double, size) // font size
-    GENERIC(uint32_t, ch) // character code
-    FTVECTOR(outline) // border width, 16.16 fixed point value
-    GENERIC(int, bold)
-    GENERIC(int, italic)
+// describes an outline bitmap
+START(outline_bitmap, outline_bitmap_hash_key)
+    GENERIC(OutlineHashValue *, outline)
     GENERIC(char, be) // blur edges
     GENERIC(double, blur) // gaussian blur
-    GENERIC(unsigned, scale_x) // 16.16
-    GENERIC(unsigned, scale_y) // 16.16
     GENERIC(int, frx) // signed 16.16
     GENERIC(int, fry) // signed 16.16
     GENERIC(int, frz) // signed 16.16
@@ -78,26 +76,49 @@
     GENERIC(int, shift_y)
     FTVECTOR(advance) // subpixel shift vector
     FTVECTOR(shadow_offset) // shadow subpixel shift
-    GENERIC(unsigned, drawing_hash) // hashcode of a drawing
-    GENERIC(unsigned, flags)    // glyph decoration
-    GENERIC(unsigned, border_style)
-END(BitmapHashKey)
+END(OutlineBitmapHashKey)
+
+// describe a clip mask bitmap
+START(clip_bitmap, clip_bitmap_hash_key)
+    STRING(text)
+END(ClipMaskHashKey)
 
 // describes an outline glyph
 START(glyph, glyph_hash_key)
     GENERIC(ASS_Font *, font)
     GENERIC(double, size) // font size
-    GENERIC(uint32_t, ch) // character code
+    GENERIC(int, face_index)
+    GENERIC(int, glyph_index)
     GENERIC(int, bold)
     GENERIC(int, italic)
     GENERIC(unsigned, scale_x) // 16.16
     GENERIC(unsigned, scale_y) // 16.16
     FTVECTOR(outline) // border width, 16.16
-    GENERIC(unsigned, drawing_hash) // hashcode of a drawing
     GENERIC(unsigned, flags)    // glyph decoration flags
     GENERIC(unsigned, border_style)
 END(GlyphHashKey)
 
+START(glyph_metrics, glyph_metrics_hash_key)
+    GENERIC(ASS_Font *, font)
+    GENERIC(double, size)
+    GENERIC(int, face_index)
+    GENERIC(int, glyph_index)
+    GENERIC(unsigned, scale_x)
+    GENERIC(unsigned, scale_y)
+END(GlyphMetricsHashKey)
+
+// describes an outline drawing
+START(drawing, drawing_hash_key)
+    GENERIC(unsigned, scale_x)
+    GENERIC(unsigned, scale_y)
+    GENERIC(int, pbo)
+    FTVECTOR(outline)
+    GENERIC(unsigned, border_style)
+    GENERIC(int, scale)
+    GENERIC(unsigned, hash)
+    STRING(text)
+END(DrawingHashKey)
+
 // Cache for composited bitmaps
 START(composite, composite_hash_key)
     GENERIC(int, aw)
@@ -117,6 +138,7 @@
 
 #undef START
 #undef GENERIC
+#undef STRING
 #undef FTVECTOR
 #undef BITMAPHASHKEY
 #undef END
--- a/libass/ass_drawing.c	Sat Dec 03 21:33:28 2011 +0000
+++ b/libass/ass_drawing.c	Sat Dec 03 21:35:56 2011 +0000
@@ -17,13 +17,11 @@
  */
 
 #include <ft2build.h>
-#include FT_GLYPH_H
 #include FT_OUTLINE_H
 #include FT_BBOX_H
 #include <math.h>
 
 #include "ass_utils.h"
-#include "ass_font.h"
 #include "ass_drawing.h"
 
 #define CURVE_ACCURACY 64.0
@@ -31,35 +29,12 @@
 #define GLYPH_INITIAL_CONTOURS 5
 
 /*
- * \brief Get and prepare a FreeType glyph
- */
-static void drawing_make_glyph(ASS_Drawing *drawing, void *fontconfig_priv,
-                               ASS_Font *font)
-{
-    FT_OutlineGlyph glyph;
-
-    // This is hacky...
-    glyph = (FT_OutlineGlyph) ass_font_get_glyph(fontconfig_priv, font,
-                                                 (uint32_t) ' ', 0, 0);
-    if (glyph) {
-        FT_Outline_Done(drawing->ftlibrary, &glyph->outline);
-        FT_Outline_New(drawing->ftlibrary, GLYPH_INITIAL_POINTS,
-                       GLYPH_INITIAL_CONTOURS, &glyph->outline);
-
-        glyph->outline.n_contours = 0;
-        glyph->outline.n_points = 0;
-        glyph->root.advance.x = glyph->root.advance.y = 0;
-    }
-    drawing->glyph = glyph;
-}
-
-/*
  * \brief Add a single point to a contour.
  */
 static inline void drawing_add_point(ASS_Drawing *drawing,
                                      FT_Vector *point)
 {
-    FT_Outline *ol = &drawing->glyph->outline;
+    FT_Outline *ol = &drawing->outline;
 
     if (ol->n_points >= drawing->max_points) {
         drawing->max_points *= 2;
@@ -75,11 +50,11 @@
 }
 
 /*
- * \brief Close a contour and check glyph size overflow.
+ * \brief Close a contour and check outline size overflow.
  */
 static inline void drawing_close_shape(ASS_Drawing *drawing)
 {
-    FT_Outline *ol = &drawing->glyph->outline;
+    FT_Outline *ol = &drawing->outline;
 
     if (ol->n_contours >= drawing->max_contours) {
         drawing->max_contours *= 2;
@@ -107,13 +82,13 @@
 
 /*
  * \brief Finish a drawing.  This only sets the horizontal advance according
- * to the glyph's bbox at the moment.
+ * to the outline's bbox at the moment.
  */
 static void drawing_finish(ASS_Drawing *drawing, int raw_mode)
 {
     int i, offset;
     FT_BBox bbox = drawing->cbox;
-    FT_Outline *ol = &drawing->glyph->outline;
+    FT_Outline *ol = &drawing->outline;
 
     // Close the last contour
     drawing_close_shape(drawing);
@@ -126,7 +101,7 @@
     if (raw_mode)
         return;
 
-    drawing->glyph->root.advance.x = d6_to_d16(bbox.xMax - bbox.xMin);
+    drawing->advance.x = bbox.xMax - bbox.xMin;
 
     drawing->desc = double_to_d6(-drawing->pbo * drawing->scale_y);
     drawing->asc = bbox.yMax - bbox.yMin + drawing->desc;
@@ -355,8 +330,7 @@
 /*
  * \brief Create and initialize a new drawing and return it
  */
-ASS_Drawing *ass_drawing_new(void *fontconfig_priv, ASS_Font *font,
-                             FT_Library lib)
+ASS_Drawing *ass_drawing_new(ASS_Library *lib, FT_Library ftlib)
 {
     ASS_Drawing *drawing;
 
@@ -365,17 +339,18 @@
     drawing->size = DRAWING_INITIAL_SIZE;
     drawing->cbox.xMin = drawing->cbox.yMin = INT_MAX;
     drawing->cbox.xMax = drawing->cbox.yMax = INT_MIN;
-    drawing->fontconfig_priv = fontconfig_priv;
-    drawing->font = font;
-    drawing->ftlibrary = lib;
-    if (font)
-        drawing->library = font->library;
-
+    drawing->ftlibrary = ftlib;
+    drawing->library   = lib;
     drawing->scale_x = 1.;
     drawing->scale_y = 1.;
     drawing->max_contours = GLYPH_INITIAL_CONTOURS;
     drawing->max_points = GLYPH_INITIAL_POINTS;
 
+    FT_Outline_New(drawing->ftlibrary, GLYPH_INITIAL_POINTS,
+            GLYPH_INITIAL_CONTOURS, &drawing->outline);
+    drawing->outline.n_contours = 0;
+    drawing->outline.n_points = 0;
+
     return drawing;
 }
 
@@ -386,6 +361,7 @@
 {
     if (drawing) {
         free(drawing->text);
+        FT_Outline_Done(drawing->ftlibrary, &drawing->outline);
     }
     free(drawing);
 }
@@ -416,17 +392,12 @@
 /*
  * \brief Convert token list to outline.  Calls the line and curve evaluators.
  */
-FT_OutlineGlyph *ass_drawing_parse(ASS_Drawing *drawing, int raw_mode)
+FT_Outline *ass_drawing_parse(ASS_Drawing *drawing, int raw_mode)
 {
     int started = 0;
     ASS_DrawingToken *token;
     FT_Vector pen = {0, 0};
 
-    if (drawing->font)
-        drawing_make_glyph(drawing, drawing->fontconfig_priv, drawing->font);
-    if (!drawing->glyph)
-        return NULL;
-
     drawing->tokens = drawing_tokenize(drawing->text);
     drawing_prepare(drawing);
 
@@ -486,5 +457,5 @@
 
     drawing_finish(drawing, raw_mode);
     drawing_free_tokens(drawing->tokens);
-    return &drawing->glyph;
+    return &drawing->outline;
 }
--- a/libass/ass_drawing.h	Sat Dec 03 21:33:28 2011 +0000
+++ b/libass/ass_drawing.h	Sat Dec 03 21:35:56 2011 +0000
@@ -20,7 +20,7 @@
 #define LIBASS_DRAWING_H
 
 #include <ft2build.h>
-#include FT_GLYPH_H
+#include FT_OUTLINE_H
 
 #include "ass.h"
 
@@ -53,13 +53,12 @@
     double scale_y;     // FontScaleY
     int asc;            // ascender
     int desc;           // descender
-    FT_OutlineGlyph glyph;  // the "fake" glyph created for later rendering
+    FT_Outline outline; // target outline
+    FT_Vector advance;  // advance (from cbox)
     int hash;           // hash value (for caching)
 
     // private
     FT_Library ftlibrary;   // needed for font ops
-    ASS_Font *font;         // dito
-    void *fontconfig_priv;  // dito
     ASS_Library *library;
     int size;           // current buffer size
     ASS_DrawingToken *tokens;    // tokenized drawing
@@ -70,11 +69,10 @@
     FT_BBox cbox;   // bounding box, or let's say... VSFilter's idea of it
 } ASS_Drawing;
 
-ASS_Drawing *ass_drawing_new(void *fontconfig_priv, ASS_Font *font,
-                             FT_Library lib);
+ASS_Drawing *ass_drawing_new(ASS_Library *lib, FT_Library ftlib);
 void ass_drawing_free(ASS_Drawing* drawing);
 void ass_drawing_add_char(ASS_Drawing* drawing, char symbol);
 void ass_drawing_hash(ASS_Drawing* drawing);
-FT_OutlineGlyph *ass_drawing_parse(ASS_Drawing *drawing, int raw_mode);
+FT_Outline *ass_drawing_parse(ASS_Drawing *drawing, int raw_mode);
 
 #endif /* LIBASS_DRAWING_H */
--- a/libass/ass_font.c	Sat Dec 03 21:33:28 2011 +0000
+++ b/libass/ass_font.c	Sat Dec 03 21:35:56 2011 +0000
@@ -30,12 +30,9 @@
 #include "ass.h"
 #include "ass_library.h"
 #include "ass_font.h"
-#include "ass_bitmap.h"
-#include "ass_cache.h"
 #include "ass_fontconfig.h"
 #include "ass_utils.h"
-
-#define VERTICAL_LOWER_BOUND 0x02f1
+#include "ass_shaper.h"
 
 /**
  * Select a good charmap, prefer Microsoft Unicode charmaps.
@@ -91,8 +88,6 @@
     return -1;
 }
 
-static void face_set_size(FT_Face face, double size);
-
 static void buggy_font_workaround(FT_Face face)
 {
     // Some fonts have zero Ascender/Descender fields in 'hhea' table.
@@ -161,7 +156,7 @@
     buggy_font_workaround(face);
 
     font->faces[font->n_faces++] = face;
-    face_set_size(face, font->size);
+    ass_face_set_size(face, font->size);
     free(path);
     return font->n_faces - 1;
 }
@@ -169,7 +164,7 @@
 /**
  * \brief Create a new ASS_Font according to "desc" argument
  */
-ASS_Font *ass_font_new(void *font_cache, ASS_Library *library,
+ASS_Font *ass_font_new(Cache *font_cache, ASS_Library *library,
                        FT_Library ftlibrary, void *fc_priv,
                        ASS_FontDesc *desc)
 {
@@ -177,12 +172,13 @@
     ASS_Font *fontp;
     ASS_Font font;
 
-    fontp = ass_font_cache_find((Hashmap *) font_cache, desc);
+    fontp = ass_cache_get(font_cache, desc);
     if (fontp)
         return fontp;
 
     font.library = library;
     font.ftlibrary = ftlibrary;
+    font.shaper_priv = NULL;
     font.n_faces = 0;
     font.desc.family = strdup(desc->family);
     font.desc.treat_family_as_pattern = desc->treat_family_as_pattern;
@@ -199,7 +195,7 @@
         free(font.desc.family);
         return 0;
     } else
-        return ass_font_cache_add((Hashmap *) font_cache, &font);
+        return ass_cache_put(font_cache, &font.desc, &font);
 }
 
 /**
@@ -216,7 +212,7 @@
     }
 }
 
-static void face_set_size(FT_Face face, double size)
+void ass_face_set_size(FT_Face face, double size)
 {
     TT_HoriHeader *hori = FT_Get_Sfnt_Table(face, ft_sfnt_hhea);
     TT_OS2 *os2 = FT_Get_Sfnt_Table(face, ft_sfnt_os2);
@@ -251,7 +247,7 @@
     if (font->size != size) {
         font->size = size;
         for (i = 0; i < font->n_faces; ++i)
-            face_set_size(font->faces[i], size);
+            ass_face_set_size(font->faces[i], size);
     }
 }
 
@@ -276,9 +272,6 @@
                 *asc = FT_MulFix(face->ascender, y_scale);
                 *desc = FT_MulFix(-face->descender, y_scale);
             }
-            if (font->desc.vertical && ch >= VERTICAL_LOWER_BOUND) {
-                *asc = FT_MulFix(face->max_advance_width, y_scale);
-            }
             return;
         }
     }
@@ -388,6 +381,25 @@
     return 0;
 }
 
+void outline_copy(FT_Library lib, FT_Outline *source, FT_Outline **dest)
+{
+    if (source == NULL) {
+        *dest = NULL;
+        return;
+    }
+    *dest = calloc(1, sizeof(**dest));
+
+    FT_Outline_New(lib, source->n_points, source->n_contours, *dest);
+    FT_Outline_Copy(source, *dest);
+}
+
+void outline_free(FT_Library lib, FT_Outline *outline)
+{
+    if (outline)
+        FT_Outline_Done(lib, outline);
+    free(outline);
+}
+
 /**
  * Slightly embold a glyph without touching its metrics
  */
@@ -405,33 +417,43 @@
 }
 
 /**
- * \brief Get a glyph
- * \param ch character code
- **/
-FT_Glyph ass_font_get_glyph(void *fontconfig_priv, ASS_Font *font,
-                            uint32_t ch, ASS_Hinting hinting, int deco)
+ * \brief Get glyph and face index
+ * Finds a face that has the requested codepoint and returns both face
+ * and glyph index.
+ */
+int ass_font_get_index(void *fcpriv, ASS_Font *font, uint32_t symbol,
+                       int *face_index, int *glyph_index)
 {
-    int error;
     int index = 0;
     int i;
-    FT_Glyph glyph;
     FT_Face face = 0;
-    int flags = 0;
-    int vertical = font->desc.vertical;
+
+    *glyph_index = 0;
 
-    if (ch < 0x20)
+    if (symbol < 0x20) {
+        *face_index = 0;
         return 0;
+    }
     // Handle NBSP like a regular space when rendering the glyph
-    if (ch == 0xa0)
-        ch = ' ';
-    if (font->n_faces == 0)
+    if (symbol == 0xa0)
+        symbol = ' ';
+    if (font->n_faces == 0) {
+        *face_index = 0;
         return 0;
+    }
 
-    for (i = 0; i < font->n_faces; ++i) {
+    // try with the requested face
+    if (*face_index < font->n_faces) {
+        face = font->faces[*face_index];
+        index = FT_Get_Char_Index(face, symbol);
+    }
+
+    // not found in requested face, try all others
+    for (i = 0; i < font->n_faces && index == 0; ++i) {
         face = font->faces[i];
-        index = FT_Get_Char_Index(face, ch);
+        index = FT_Get_Char_Index(face, symbol);
         if (index)
-            break;
+            *face_index = i;
     }
 
 #ifdef CONFIG_FONTCONFIG
@@ -439,30 +461,50 @@
         int face_idx;
         ass_msg(font->library, MSGL_INFO,
                 "Glyph 0x%X not found, selecting one more "
-                "font for (%s, %d, %d)", ch, font->desc.family,
+                "font for (%s, %d, %d)", symbol, font->desc.family,
                 font->desc.bold, font->desc.italic);
-        face_idx = add_face(fontconfig_priv, font, ch);
+        face_idx = *face_index = add_face(fcpriv, font, symbol);
         if (face_idx >= 0) {
             face = font->faces[face_idx];
-            index = FT_Get_Char_Index(face, ch);
+            index = FT_Get_Char_Index(face, symbol);
             if (index == 0 && face->num_charmaps > 0) {
                 int i;
                 ass_msg(font->library, MSGL_WARN,
-                    "Glyph 0x%X not found, broken font? Trying all charmaps", ch);
+                    "Glyph 0x%X not found, broken font? Trying all charmaps", symbol);
                 for (i = 0; i < face->num_charmaps; i++) {
                     FT_Set_Charmap(face, face->charmaps[i]);
-                    if ((index = FT_Get_Char_Index(face, ch)) != 0) break;
+                    if ((index = FT_Get_Char_Index(face, symbol)) != 0) break;
                 }
             }
             if (index == 0) {
                 ass_msg(font->library, MSGL_ERR,
                         "Glyph 0x%X not found in font for (%s, %d, %d)",
-                        ch, font->desc.family, font->desc.bold,
+                        symbol, font->desc.family, font->desc.bold,
                         font->desc.italic);
             }
         }
     }
 #endif
+    // FIXME: make sure we have a valid face_index. this is a HACK.
+    *face_index  = FFMAX(*face_index, 0);
+    *glyph_index = index;
+
+    return 1;
+}
+
+/**
+ * \brief Get a glyph
+ * \param ch character code
+ **/
+FT_Glyph ass_font_get_glyph(void *fontconfig_priv, ASS_Font *font,
+                            uint32_t ch, int face_index, int index,
+                            ASS_Hinting hinting, int deco)
+{
+    int error;
+    FT_Glyph glyph;
+    FT_Face face = font->faces[face_index];
+    int flags = 0;
+    int vertical = font->desc.vertical;
 
     flags = FT_LOAD_NO_BITMAP | FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH
             | FT_LOAD_IGNORE_TRANSFORM;
@@ -505,10 +547,16 @@
     // Rotate glyph, if needed
     if (vertical && ch >= VERTICAL_LOWER_BOUND) {
         FT_Matrix m = { 0, double_to_d16(-1.0), double_to_d16(1.0), 0 };
+        TT_OS2 *os2 = FT_Get_Sfnt_Table(face, ft_sfnt_os2);
+        int desc = 0;
+
+        if (os2)
+            desc = FT_MulFix(os2->sTypoDescender, face->size->metrics.y_scale);
+
+        FT_Outline_Translate(&((FT_OutlineGlyph) glyph)->outline, 0, -desc);
         FT_Outline_Transform(&((FT_OutlineGlyph) glyph)->outline, &m);
         FT_Outline_Translate(&((FT_OutlineGlyph) glyph)->outline,
-                             face->glyph->metrics.vertAdvance,
-                             0);
+                             face->glyph->metrics.vertAdvance, desc);
         glyph->advance.x = face->glyph->linearVertAdvance;
     }
 
@@ -561,6 +609,8 @@
     for (i = 0; i < font->n_faces; ++i)
         if (font->faces[i])
             FT_Done_Face(font->faces[i]);
+    if (font->shaper_priv)
+        ass_shaper_font_data_free(font->shaper_priv);
     free(font->desc.family);
     free(font);
 }
@@ -618,9 +668,9 @@
  * \param border_x border size, x direction, d6 format
  * \param border_x border size, y direction, d6 format
  */
-void fix_freetype_stroker(FT_OutlineGlyph glyph, int border_x, int border_y)
+void fix_freetype_stroker(FT_Outline *outline, int border_x, int border_y)
 {
-    int nc = glyph->outline.n_contours;
+    int nc = outline->n_contours;
     int begin, stop;
     char modified = 0;
     char *valid_cont = malloc(nc);
@@ -630,14 +680,14 @@
     int i, j;
     int inside_direction;
 
-    inside_direction = FT_Outline_Get_Orientation(&glyph->outline) ==
+    inside_direction = FT_Outline_Get_Orientation(outline) ==
         FT_ORIENTATION_TRUETYPE;
 
     // create a list of cboxes of the contours
     for (i = 0; i < nc; i++) {
         start = end + 1;
-        end = glyph->outline.contours[i];
-        get_contour_cbox(&boxes[i], glyph->outline.points, start, end);
+        end = outline->contours[i];
+        get_contour_cbox(&boxes[i], outline->points, start, end);
     }
 
     // for each contour, check direction and whether it's "outside"
@@ -645,8 +695,8 @@
     end = -1;
     for (i = 0; i < nc; i++) {
         start = end + 1;
-        end = glyph->outline.contours[i];
-        int dir = get_contour_direction(glyph->outline.points, start, end);
+        end = outline->contours[i];
+        int dir = get_contour_direction(outline->points, start, end);
         valid_cont[i] = 1;
         if (dir == inside_direction) {
             for (j = 0; j < nc; j++) {
@@ -662,19 +712,19 @@
              * inside of - assume the font is buggy and it should be
              * an "outside" contour, and reverse it */
             for (j = 0; j < (end + 1 - start) / 2; j++) {
-                FT_Vector temp = glyph->outline.points[start + j];
-                char temp2 = glyph->outline.tags[start + j];
-                glyph->outline.points[start + j] = glyph->outline.points[end - j];
-                glyph->outline.points[end - j] = temp;
-                glyph->outline.tags[start + j] = glyph->outline.tags[end - j];
-                glyph->outline.tags[end - j] = temp2;
+                FT_Vector temp = outline->points[start + j];
+                char temp2 = outline->tags[start + j];
+                outline->points[start + j] = outline->points[end - j];
+                outline->points[end - j] = temp;
+                outline->tags[start + j] = outline->tags[end - j];
+                outline->tags[end - j] = temp2;
             }
             dir ^= 1;
         }
         check_inside:
         if (dir == inside_direction) {
             FT_BBox box;
-            get_contour_cbox(&box, glyph->outline.points, start, end);
+            get_contour_cbox(&box, outline->points, start, end);
             int width = box.xMax - box.xMin;
             int height = box.yMax - box.yMin;
             if (width < border_x * 2 || height < border_y * 2) {
@@ -687,13 +737,12 @@
     // if we need to modify the outline, rewrite it and skip
     // the contours that we determined should be removed.
     if (modified) {
-        FT_Outline *outline = &glyph->outline;
         int p = 0, c = 0;
         for (i = 0; i < nc; i++) {
             if (!valid_cont[i])
                 continue;
-            begin = (i == 0) ? 0 : glyph->outline.contours[i - 1] + 1;
-            stop = glyph->outline.contours[i];
+            begin = (i == 0) ? 0 : outline->contours[i - 1] + 1;
+            stop = outline->contours[i];
             for (j = begin; j <= stop; j++) {
                 outline->points[p].x = outline->points[j].x;
                 outline->points[p].y = outline->points[j].y;
--- a/libass/ass_font.h	Sat Dec 03 21:33:28 2011 +0000
+++ b/libass/ass_font.h	Sat Dec 03 21:35:56 2011 +0000
@@ -22,13 +22,19 @@
 #include <stdint.h>
 #include <ft2build.h>
 #include FT_GLYPH_H
+#include FT_OUTLINE_H
+
 #include "ass.h"
 #include "ass_types.h"
 
+#define VERTICAL_LOWER_BOUND 0x02f1
+
 #define ASS_FONT_MAX_FACES 10
 #define DECO_UNDERLINE 1
 #define DECO_STRIKETHROUGH 2
 
+typedef struct ass_shaper_font_data ASS_ShaperFontData;
+
 typedef struct {
     char *family;
     unsigned bold;
@@ -42,25 +48,33 @@
     ASS_Library *library;
     FT_Library ftlibrary;
     FT_Face faces[ASS_FONT_MAX_FACES];
+    ASS_ShaperFontData *shaper_priv;
     int n_faces;
     double scale_x, scale_y;    // current transform
     FT_Vector v;                // current shift
     double size;
 } ASS_Font;
 
-// FIXME: passing the hashmap via a void pointer is very ugly.
-ASS_Font *ass_font_new(void *font_cache, ASS_Library *library,
+#include "ass_cache.h"
+
+ASS_Font *ass_font_new(Cache *font_cache, ASS_Library *library,
                        FT_Library ftlibrary, void *fc_priv,
                        ASS_FontDesc *desc);
 void ass_font_set_transform(ASS_Font *font, double scale_x,
                             double scale_y, FT_Vector *v);
+void ass_face_set_size(FT_Face face, double size);
 void ass_font_set_size(ASS_Font *font, double size);
 void ass_font_get_asc_desc(ASS_Font *font, uint32_t ch, int *asc,
                            int *desc);
+int ass_font_get_index(void *fcpriv, ASS_Font *font, uint32_t symbol,
+                       int *face_index, int *glyph_index);
 FT_Glyph ass_font_get_glyph(void *fontconfig_priv, ASS_Font *font,
-                            uint32_t ch, ASS_Hinting hinting, int flags);
+                            uint32_t ch, int face_index, int index,
+                            ASS_Hinting hinting, int deco);
 FT_Vector ass_font_get_kerning(ASS_Font *font, uint32_t c1, uint32_t c2);
 void ass_font_free(ASS_Font *font);
-void fix_freetype_stroker(FT_OutlineGlyph glyph, int border_x, int border_y);
+void fix_freetype_stroker(FT_Outline *outline, int border_x, int border_y);
+void outline_copy(FT_Library lib, FT_Outline *source, FT_Outline **dest);
+void outline_free(FT_Library lib, FT_Outline *outline);
 
 #endif                          /* LIBASS_FONT_H */
--- a/libass/ass_library.c	Sat Dec 03 21:33:28 2011 +0000
+++ b/libass/ass_library.c	Sat Dec 03 21:35:56 2011 +0000
@@ -78,6 +78,7 @@
             free(*p);
     }
     free(priv->style_overrides);
+    priv->style_overrides = NULL;
 
     if (!list)
         return;
--- a/libass/ass_parse.c	Sat Dec 03 21:33:28 2011 +0000
+++ b/libass/ass_parse.c	Sat Dec 03 21:35:56 2011 +0000
@@ -47,17 +47,18 @@
         return 0;
 }
 
-static void change_font_size(ASS_Renderer *render_priv, double sz)
+double ensure_font_size(ASS_Renderer *priv, double size)
 {
-    double size = sz * render_priv->font_scale;
-
     if (size < 1)
         size = 1;
-    else if (size > render_priv->height * 2)
-        size = render_priv->height * 2;
+    else if (size > priv->height * 2)
+        size = priv->height * 2;
 
-    ass_font_set_size(render_priv->state.font, size);
+    return size;
+}
 
+static void change_font_size(ASS_Renderer *render_priv, double sz)
+{
     render_priv->state.font_size = sz;
 }
 
@@ -189,17 +190,18 @@
 {
     unsigned a;
     double cf;
-    if (now <= t1) {
+
+    if (now < t1) {
         a = a1;
     } else if (now >= t4) {
         a = a3;
-    } else if (now < t2) {      // and > t1
+    } else if (now < t2 && t2 > t1) {
         cf = ((double) (now - t1)) / (t2 - t1);
         a = a1 * (1 - cf) + a2 * cf;
-    } else if (now > t3) {
+    } else if (now >= t3 && t4 > t3) {
         cf = ((double) (now - t3)) / (t4 - t3);
         a = a2 * (1 - cf) + a3 * cf;
-    } else {                    // t2 <= now <= t3
+    } else {                    // t2 <= now < t3
         a = a2;
     }
 
@@ -216,13 +218,9 @@
     int res = 0;
     ASS_Drawing *drawing = render_priv->state.clip_drawing;
 
-    if (drawing && drawing->glyph)
-        FT_Done_Glyph((FT_Glyph) drawing->glyph);
     ass_drawing_free(drawing);
-    render_priv->state.clip_drawing = ass_drawing_new(
-        render_priv->fontconfig_priv,
-        render_priv->state.font,
-        render_priv->ftlibrary);
+    render_priv->state.clip_drawing =
+        ass_drawing_new(render_priv->library, render_priv->ftlibrary);
     drawing = render_priv->state.clip_drawing;
     skipopt('(');
     res = mystrtoi(&p, &scale);
@@ -259,6 +257,7 @@
         else
             val = -1.;
         change_border(render_priv, val, render_priv->state.border_y);
+        render_priv->state.bm_run_id++;
     } else if (mystrcmp(&p, "ybord")) {
         double val;
         if (mystrtod(&p, &val))
@@ -273,6 +272,7 @@
         else
             val = 0.;
         render_priv->state.shadow_x = val;
+        render_priv->state.bm_run_id++;
     } else if (mystrcmp(&p, "yshad")) {
         double val;
         if (mystrtod(&p, &val))
@@ -280,6 +280,7 @@
         else
             val = 0.;
         render_priv->state.shadow_y = val;
+        render_priv->state.bm_run_id++;
     } else if (mystrcmp(&p, "fax")) {
         double val;
         if (mystrtod(&p, &val))
@@ -331,6 +332,7 @@
             render_priv->state.blur = val;
         } else
             render_priv->state.blur = 0.0;
+        render_priv->state.bm_run_id++;
         // ASS standard tags
     } else if (mystrcmp(&p, "fsc")) {
         char tp = *p++;
@@ -391,6 +393,7 @@
         } else
             val = -1.;          // reset to default
         change_border(render_priv, val, val);
+        render_priv->state.bm_run_id++;
     } else if (mystrcmp(&p, "move")) {
         double x1, x2, y1, y2;
         long long t1, t2, delta_t, t;
@@ -492,6 +495,7 @@
             change_alpha(&render_priv->state.c[3],
                          render_priv->state.style->BackColour, pwr);
         }
+        render_priv->state.bm_run_id++;
         // FIXME: simplify
     } else if (mystrcmp(&p, "an")) {
         int val;
@@ -682,6 +686,7 @@
             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);
+        render_priv->state.bm_run_id++;
     } else if ((*p >= '1') && (*p <= '4') && (++p)
                && (mystrcmp(&p, "c") || mystrcmp(&p, "a"))) {
         char n = *(p - 2);
@@ -711,9 +716,11 @@
         switch (cmd) {
         case 'c':
             change_color(render_priv->state.c + cidx, val, pwr);
+            render_priv->state.bm_run_id++;
             break;
         case 'a':
             change_alpha(render_priv->state.c + cidx, val >> 24, pwr);
+            render_priv->state.bm_run_id++;
             break;
         default:
             ass_msg(render_priv->library, MSGL_WARN, "Bad command: %c%c",
@@ -733,6 +740,7 @@
             render_priv->state.be = val;
         } else
             render_priv->state.be = 0;
+        render_priv->state.bm_run_id++;
     } else if (mystrcmp(&p, "b")) {
         int b;
         if (mystrtoi(&p, &b)) {
@@ -781,18 +789,21 @@
         } else
             val = 0.;
         render_priv->state.shadow_x = render_priv->state.shadow_y = val;
+        render_priv->state.bm_run_id++;
     } 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;
+        render_priv->state.bm_run_id++;
     } 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;
+        render_priv->state.bm_run_id++;
     } else if (mystrcmp(&p, "pbo")) {
         double val = 0;
         if (mystrtod(&p, &val))
@@ -809,6 +820,11 @@
         if (!mystrtoi(&p, &val))
             val = render_priv->track->WrapStyle;
         render_priv->state.wrap_style = val;
+    } else if (mystrcmp(&p, "fe")) {
+        int val;
+        if (!mystrtoi(&p, &val))
+            val = render_priv->state.style->Encoding;
+        render_priv->state.font_encoding = val;
     }
 
     return p;
@@ -890,6 +906,77 @@
 }
 
 /**
+ * \brief determine karaoke effects
+ * Karaoke effects cannot be calculated during parse stage (get_next_char()),
+ * so they are done in a separate step.
+ * Parse stage: when karaoke style override is found, its parameters are stored in the next glyph's
+ * (the first glyph of the karaoke word)'s effect_type and effect_timing.
+ * This function:
+ * 1. sets effect_type for all glyphs in the word (_karaoke_ word)
+ * 2. sets effect_timing for all glyphs to x coordinate of the border line between the left and right karaoke parts
+ * (left part is filled with PrimaryColour, right one - with SecondaryColour).
+ */
+void process_karaoke_effects(ASS_Renderer *render_priv)
+{
+    GlyphInfo *cur, *cur2;
+    GlyphInfo *s1, *e1;      // start and end of the current word
+    GlyphInfo *s2;           // start of the next word
+    int i;
+    int timing;                 // current timing
+    int tm_start, tm_end;       // timings at start and end of the current word
+    int tm_current;
+    double dt;
+    int x;
+    int x_start, x_end;
+
+    tm_current = render_priv->time - render_priv->state.event->Start;
+    timing = 0;
+    s1 = s2 = 0;
+    for (i = 0; i <= render_priv->text_info.length; ++i) {
+        cur = render_priv->text_info.glyphs + i;
+        if ((i == render_priv->text_info.length)
+            || (cur->effect_type != EF_NONE)) {
+            s1 = s2;
+            s2 = cur;
+            if (s1) {
+                e1 = s2 - 1;
+                tm_start = timing + s1->effect_skip_timing;
+                tm_end = tm_start + s1->effect_timing;
+                timing = tm_end;
+                x_start = 1000000;
+                x_end = -1000000;
+                for (cur2 = s1; cur2 <= e1; ++cur2) {
+                    x_start = FFMIN(x_start, d6_to_int(cur2->bbox.xMin + cur2->pos.x));
+                    x_end = FFMAX(x_end, d6_to_int(cur2->bbox.xMax + cur2->pos.x));
+                }
+
+                dt = (tm_current - tm_start);
+                if ((s1->effect_type == EF_KARAOKE)
+                    || (s1->effect_type == EF_KARAOKE_KO)) {
+                    if (dt > 0)
+                        x = x_end + 1;
+                    else
+                        x = x_start;
+                } else if (s1->effect_type == EF_KARAOKE_KF) {
+                    dt /= (tm_end - tm_start);
+                    x = x_start + (x_end - x_start) * dt;
+                } else {
+                    ass_msg(render_priv->library, MSGL_ERR,
+                            "Unknown effect type");
+                    continue;
+                }
+
+                for (cur2 = s1; cur2 <= e1; ++cur2) {
+                    cur2->effect_type = s1->effect_type;
+                    cur2->effect_timing = x - d6_to_int(cur2->pos.x);
+                }
+            }
+        }
+    }
+}
+
+
+/**
  * \brief Get next ucs4 char from string, parsing and executing style overrides
  * \param str string pointer
  * \return ucs4 code of the next char
--- a/libass/ass_parse.h	Sat Dec 03 21:33:28 2011 +0000
+++ b/libass/ass_parse.h	Sat Dec 03 21:35:56 2011 +0000
@@ -27,9 +27,11 @@
 #define _a(c)   ((c) & 0xFF)
 
 void update_font(ASS_Renderer *render_priv);
+double ensure_font_size(ASS_Renderer *priv, double size);
 void change_border(ASS_Renderer *render_priv, double border_x,
                    double border_y);
 void apply_transition_effects(ASS_Renderer *render_priv, ASS_Event *event);
+void process_karaoke_effects(ASS_Renderer *render_priv);
 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);
--- a/libass/ass_render.c	Sat Dec 03 21:33:28 2011 +0000
+++ b/libass/ass_render.c	Sat Dec 03 21:35:56 2011 +0000
@@ -23,44 +23,13 @@
 
 #include "ass_render.h"
 #include "ass_parse.h"
+#include "ass_shaper.h"
 
 #define MAX_GLYPHS_INITIAL 1024
 #define MAX_LINES_INITIAL 64
 #define SUBPIXEL_MASK 63
 #define SUBPIXEL_ACCURACY 7
 
-static void ass_lazy_track_init(ASS_Renderer *render_priv)
-{
-    ASS_Track *track = render_priv->track;
-
-    if (track->PlayResX && track->PlayResY)
-        return;
-    if (!track->PlayResX && !track->PlayResY) {
-        ass_msg(render_priv->library, MSGL_WARN,
-               "Neither PlayResX nor PlayResY defined. Assuming 384x288");
-        track->PlayResX = 384;
-        track->PlayResY = 288;
-    } else {
-        if (!track->PlayResY && track->PlayResX == 1280) {
-            track->PlayResY = 1024;
-            ass_msg(render_priv->library, MSGL_WARN,
-                   "PlayResY undefined, setting to %d", track->PlayResY);
-        } else if (!track->PlayResY) {
-            track->PlayResY = track->PlayResX * 3 / 4;
-            ass_msg(render_priv->library, MSGL_WARN,
-                   "PlayResY undefined, setting to %d", track->PlayResY);
-        } else if (!track->PlayResX && track->PlayResY == 1024) {
-            track->PlayResX = 1280;
-            ass_msg(render_priv->library, MSGL_WARN,
-                   "PlayResX undefined, setting to %d", track->PlayResX);
-        } else if (!track->PlayResX) {
-            track->PlayResX = track->PlayResY * 4 / 3;
-            ass_msg(render_priv->library, MSGL_WARN,
-                   "PlayResX undefined, setting to %d", track->PlayResX);
-        }
-    }
-}
-
 ASS_Renderer *ass_renderer_init(ASS_Library *library)
 {
     int error;
@@ -75,10 +44,8 @@
     }
 
     FT_Library_Version(ft, &vmajor, &vminor, &vpatch);
-    ass_msg(library, MSGL_V, "FreeType library version: %d.%d.%d",
+    ass_msg(library, MSGL_V, "Raster: FreeType %d.%d.%d",
            vmajor, vminor, vpatch);
-    ass_msg(library, MSGL_V, "FreeType headers version: %d.%d.%d",
-           FREETYPE_MAJOR, FREETYPE_MINOR, FREETYPE_PATCH);
 
     priv = calloc(1, sizeof(ASS_Renderer));
     if (!priv) {
@@ -92,10 +59,10 @@
     priv->ftlibrary = ft;
     // images_root and related stuff is zero-filled in calloc
 
-    priv->cache.font_cache = ass_font_cache_init(library);
-    priv->cache.bitmap_cache = ass_bitmap_cache_init(library);
-    priv->cache.composite_cache = ass_composite_cache_init(library);
-    priv->cache.glyph_cache = ass_glyph_cache_init(library);
+    priv->cache.font_cache = ass_font_cache_create();
+    priv->cache.bitmap_cache = ass_bitmap_cache_create();
+    priv->cache.composite_cache = ass_composite_cache_create();
+    priv->cache.outline_cache = ass_outline_cache_create();
     priv->cache.glyph_max = GLYPH_CACHE_MAX;
     priv->cache.bitmap_max_size = BITMAP_CACHE_MAX_SIZE;
 
@@ -106,11 +73,19 @@
 
     priv->settings.font_size_coeff = 1.;
 
+    priv->shaper = ass_shaper_new(0);
+    ass_shaper_info(library);
+#ifdef CONFIG_HARFBUZZ
+    priv->settings.shaper = ASS_SHAPING_COMPLEX;
+#else
+    priv->settings.shaper = ASS_SHAPING_SIMPLE;
+#endif
+
   ass_init_exit:
     if (priv)
-        ass_msg(library, MSGL_V, "Init");
+        ass_msg(library, MSGL_V, "Initialized");
     else
-        ass_msg(library, MSGL_ERR, "Init failed");
+        ass_msg(library, MSGL_ERR, "Initialization failed");
 
     return priv;
 }
@@ -131,10 +106,10 @@
 
 void ass_renderer_done(ASS_Renderer *render_priv)
 {
-    ass_font_cache_done(render_priv->cache.font_cache);
-    ass_bitmap_cache_done(render_priv->cache.bitmap_cache);
-    ass_composite_cache_done(render_priv->cache.composite_cache);
-    ass_glyph_cache_done(render_priv->cache.glyph_cache);
+    ass_cache_done(render_priv->cache.font_cache);
+    ass_cache_done(render_priv->cache.bitmap_cache);
+    ass_cache_done(render_priv->cache.composite_cache);
+    ass_cache_done(render_priv->cache.outline_cache);
 
     ass_free_images(render_priv->images_root);
     ass_free_images(render_priv->prev_images_root);
@@ -149,6 +124,7 @@
         fontconfig_done(render_priv->fontconfig_priv);
     if (render_priv->synth_priv)
         ass_synth_done(render_priv->synth_priv);
+    ass_shaper_free(render_priv->shaper);
     free(render_priv->eimg);
     free(render_priv->text_info.glyphs);
     free(render_priv->text_info.lines);
@@ -328,18 +304,18 @@
         // split up into left and right for karaoke, if needed
         if (lbrk > r[j].x0) {
             if (lbrk > r[j].x1) lbrk = r[j].x1;
-            img = my_draw_bitmap(bm->buffer + r[j].y0 * bm->w + r[j].x0,
+            img = my_draw_bitmap(bm->buffer + r[j].y0 * bm->stride + r[j].x0,
                 lbrk - r[j].x0, r[j].y1 - r[j].y0,
-                bm->w, dst_x + r[j].x0, dst_y + r[j].y0, color);
+                bm->stride, dst_x + r[j].x0, dst_y + r[j].y0, color);
             if (!img) break;
             *tail = img;
             tail = &img->next;
         }
         if (lbrk < r[j].x1) {
             if (lbrk < r[j].x0) lbrk = r[j].x0;
-            img = my_draw_bitmap(bm->buffer + r[j].y0 * bm->w + lbrk,
+            img = my_draw_bitmap(bm->buffer + r[j].y0 * bm->stride + lbrk,
                 r[j].x1 - lbrk, r[j].y1 - r[j].y0,
-                bm->w, dst_x + lbrk, dst_y + r[j].y0, color2);
+                bm->stride, dst_x + lbrk, dst_y + r[j].y0, color2);
             if (!img) break;
             *tail = img;
             tail = &img->next;
@@ -419,8 +395,8 @@
     if (brk > b_x0) {           // draw left part
         if (brk > b_x1)
             brk = b_x1;
-        img = my_draw_bitmap(bm->buffer + bm->w * b_y0 + b_x0,
-                             brk - b_x0, b_y1 - b_y0, bm->w,
+        img = my_draw_bitmap(bm->buffer + bm->stride * b_y0 + b_x0,
+                             brk - b_x0, b_y1 - b_y0, bm->stride,
                              dst_x + b_x0, dst_y + b_y0, color);
         if (!img) return tail;
         *tail = img;
@@ -429,8 +405,8 @@
     if (brk < b_x1) {           // draw right part
         if (brk < b_x0)
             brk = b_x0;
-        img = my_draw_bitmap(bm->buffer + bm->w * b_y0 + brk,
-                             b_x1 - brk, b_y1 - b_y0, bm->w,
+        img = my_draw_bitmap(bm->buffer + bm->stride * b_y0 + brk,
+                             b_x1 - brk, b_y1 - b_y0, bm->stride,
                              dst_x + brk, dst_y + b_y0, color2);
         if (!img) return tail;
         *tail = img;
@@ -516,7 +492,7 @@
     hk.by = by;
     hk.as = as;
     hk.bs = bs;
-    hv = cache_find_composite(render_priv->cache.composite_cache, &hk);
+    hv = ass_cache_get(render_priv->cache.composite_cache, &hk);
     if (hv) {
         (*last_tail)->bitmap = hv->a;
         (*tail)->bitmap = hv->b;
@@ -539,7 +515,7 @@
     // Insert bitmaps into the cache
     chv.a = (*last_tail)->bitmap;
     chv.b = (*tail)->bitmap;
-    cache_add_composite(render_priv->cache.composite_cache, &hk, &chv);
+    ass_cache_put(render_priv->cache.composite_cache, &hk, &chv);
 }
 
 static void free_list_add(ASS_Renderer *render_priv, void *object)
@@ -564,32 +540,31 @@
 static void blend_vector_clip(ASS_Renderer *render_priv,
                               ASS_Image *head)
 {
-    FT_Glyph glyph;
-    FT_BitmapGlyph clip_bm;
+    FT_Outline *outline;
+    Bitmap *clip_bm = NULL;
     ASS_Image *cur;
     ASS_Drawing *drawing = render_priv->state.clip_drawing;
-    GlyphHashKey key;
-    GlyphHashValue *val;
+    BitmapHashKey key;
+    BitmapHashValue *val;
     int error;
 
     if (!drawing)
         return;
 
     // Try to get mask from cache
-    ass_drawing_hash(drawing);
     memset(&key, 0, sizeof(key));
-    key.ch = -2;
-    key.drawing_hash = drawing->hash;
-    val = cache_find_glyph(render_priv->cache.glyph_cache, &key);
+    key.type = BITMAP_CLIP;
+    key.u.clip.text = drawing->text;
+    val = ass_cache_get(render_priv->cache.bitmap_cache, &key);
 
     if (val) {
-        clip_bm = (FT_BitmapGlyph) val->glyph;
+        clip_bm = val->bm;
     } else {
-        GlyphHashValue v;
+        BitmapHashValue v;
 
         // Not found in cache, parse and rasterize it
-        glyph = (FT_Glyph) *ass_drawing_parse(drawing, 1);
-        if (!glyph) {
+        outline = ass_drawing_parse(drawing, 1);
+        if (!outline) {
             ass_msg(render_priv->library, MSGL_WARN,
                     "Clip vector parsing failed. Skipping.");
             goto blend_vector_error;
@@ -602,37 +577,27 @@
                 .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);
-        }
-
-        // Check glyph bounding box size
-        if (check_glyph_area(render_priv->library, glyph)) {
-            FT_Done_Glyph(glyph);
-            glyph = 0;
-            goto blend_vector_error;
+            FT_Outline_Translate(outline, trans.x, trans.y);
         }
 
         ass_msg(render_priv->library, MSGL_DBG2,
                 "Parsed vector clip: scales (%f, %f) string [%s]\n",
                 drawing->scale_x, drawing->scale_y, drawing->text);
 
-        error = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 1);
-        if (error) {
+        clip_bm = outline_to_bitmap(render_priv->library,
+                render_priv->ftlibrary, outline, 0);
+        if (clip_bm == NULL) {
             ass_msg(render_priv->library, MSGL_WARN,
                 "Clip vector rasterization failed: %d. Skipping.", error);
-            FT_Done_Glyph(glyph);
-            glyph = 0;
         }
 
-blend_vector_error:
-        clip_bm = (FT_BitmapGlyph) glyph;
-
         // Add to cache
         memset(&v, 0, sizeof(v));
-        v.glyph = glyph;
-        cache_add_glyph(render_priv->cache.glyph_cache, &key, &v);
+        key.u.clip.text = strdup(drawing->text);
+        v.bm = clip_bm;
+        ass_cache_put(render_priv->cache.bitmap_cache, &key, &v);
     }
+blend_vector_error:
 
     if (!clip_bm) goto blend_vector_exit;
 
@@ -645,17 +610,17 @@
         unsigned char *abuffer, *bbuffer, *nbuffer;
 
         abuffer = cur->bitmap;
-        bbuffer = clip_bm->bitmap.buffer;
+        bbuffer = clip_bm->buffer;
         ax = cur->dst_x;
         ay = cur->dst_y;
         aw = cur->w;
         ah = cur->h;
         as = cur->stride;
         bx = clip_bm->left;
-        by = -clip_bm->top;
-        bw = clip_bm->bitmap.width;
-        bh = clip_bm->bitmap.rows;
-        bs = clip_bm->bitmap.pitch;
+        by = clip_bm->top;
+        bw = clip_bm->w;
+        bh = clip_bm->h;
+        bs = clip_bm->stride;
 
         // Calculate overlap coordinates
         left = (ax > bx) ? ax : bx;
@@ -739,22 +704,31 @@
             || (info->shadow_x == 0 && info->shadow_y == 0) || info->skip)
             continue;
 
-        pen_x =
-            dst_x + (info->pos.x >> 6) +
-            (int) (info->shadow_x * render_priv->border_scale);
-        pen_y =
-            dst_y + (info->pos.y >> 6) +
-            (int) (info->shadow_y * render_priv->border_scale);
-        bm = info->bm_s;
+        while (info) {
+            if (!info->bm_s) {
+                info = info->next;
+                continue;
+            }
 
-        here_tail = tail;
-        tail =
-            render_glyph(render_priv, bm, pen_x, pen_y, info->c[3], 0,
-                         1000000, tail);
-        if (last_tail && tail != here_tail && ((info->c[3] & 0xff) > 0))
-            render_overlap(render_priv, last_tail, here_tail);
+            pen_x =
+                dst_x + (info->pos.x >> 6) +
+                (int) (info->shadow_x * render_priv->border_scale);
+            pen_y =
+                dst_y + (info->pos.y >> 6) +
+                (int) (info->shadow_y * render_priv->border_scale);
+            bm = info->bm_s;
 
-        last_tail = here_tail;
+            here_tail = tail;
+            tail =
+                render_glyph(render_priv, bm, pen_x, pen_y, info->c[3], 0,
+                        1000000, tail);
+
+            if (last_tail && tail != here_tail && ((info->c[3] & 0xff) > 0))
+                render_overlap(render_priv, last_tail, here_tail);
+            last_tail = here_tail;
+
+            info = info->next;
+        }
     }
 
     last_tail = 0;
@@ -764,22 +738,30 @@
             || info->skip)
             continue;
 
-        pen_x = dst_x + (info->pos.x >> 6);
-        pen_y = dst_y + (info->pos.y >> 6);
-        bm = info->bm_o;
+        while (info) {
+            if (!info->bm_o) {
+                info = info->next;
+                continue;
+            }
+
+            pen_x = dst_x + (info->pos.x >> 6);
+            pen_y = dst_y + (info->pos.y >> 6);
+            bm = info->bm_o;
 
-        if ((info->effect_type == EF_KARAOKE_KO)
-            && (info->effect_timing <= (info->bbox.xMax >> 6))) {
-            // do nothing
-        } else {
-            here_tail = tail;
-            tail =
-                render_glyph(render_priv, bm, pen_x, pen_y, info->c[2],
-                             0, 1000000, tail);
-            if (last_tail && tail != here_tail && ((info->c[2] & 0xff) > 0))
-                render_overlap(render_priv, last_tail, here_tail);
+            if ((info->effect_type == EF_KARAOKE_KO)
+                    && (info->effect_timing <= (info->bbox.xMax >> 6))) {
+                // do nothing
+            } else {
+                here_tail = tail;
+                tail =
+                    render_glyph(render_priv, bm, pen_x, pen_y, info->c[2],
+                            0, 1000000, tail);
+                if (last_tail && tail != here_tail && ((info->c[2] & 0xff) > 0))
+                    render_overlap(render_priv, last_tail, here_tail);
 
-            last_tail = here_tail;
+                last_tail = here_tail;
+            }
+            info = info->next;
         }
     }
 
@@ -789,28 +771,36 @@
             || info->skip)
             continue;
 
-        pen_x = dst_x + (info->pos.x >> 6);
-        pen_y = dst_y + (info->pos.y >> 6);
-        bm = info->bm;
+        while (info) {
+            if (!info->bm) {
+                info = info->next;
+                continue;
+            }
+
+            pen_x = dst_x + (info->pos.x >> 6);
+            pen_y = dst_y + (info->pos.y >> 6);
+            bm = info->bm;
 
-        if ((info->effect_type == EF_KARAOKE)
-            || (info->effect_type == EF_KARAOKE_KO)) {
-            if (info->effect_timing > (info->bbox.xMax >> 6))
+            if ((info->effect_type == EF_KARAOKE)
+                    || (info->effect_type == EF_KARAOKE_KO)) {
+                if (info->effect_timing > (info->bbox.xMax >> 6))
+                    tail =
+                        render_glyph(render_priv, bm, pen_x, pen_y,
+                                info->c[0], 0, 1000000, tail);
+                else
+                    tail =
+                        render_glyph(render_priv, bm, pen_x, pen_y,
+                                info->c[1], 0, 1000000, tail);
+            } else if (info->effect_type == EF_KARAOKE_KF) {
                 tail =
-                    render_glyph(render_priv, bm, pen_x, pen_y,
-                                 info->c[0], 0, 1000000, tail);
-            else
+                    render_glyph(render_priv, bm, pen_x, pen_y, info->c[0],
+                            info->c[1], info->effect_timing, tail);
+            } else
                 tail =
-                    render_glyph(render_priv, bm, pen_x, pen_y,
-                                 info->c[1], 0, 1000000, tail);
-        } else if (info->effect_type == EF_KARAOKE_KF) {
-            tail =
-                render_glyph(render_priv, bm, pen_x, pen_y, info->c[0],
-                             info->c[1], info->effect_timing, tail);
-        } else
-            tail =
-                render_glyph(render_priv, bm, pen_x, pen_y, info->c[0],
-                             0, 1000000, tail);
+                    render_glyph(render_priv, bm, pen_x, pen_y, info->c[0],
+                            0, 1000000, tail);
+            info = info->next;
+        }
     }
 
     *tail = 0;
@@ -819,23 +809,27 @@
     return head;
 }
 
-static void compute_string_bbox(TextInfo *info, DBBox *bbox)
+static void compute_string_bbox(TextInfo *text, DBBox *bbox)
 {
     int i;
 
-    if (info->length > 0) {
+    if (text->length > 0) {
         bbox->xMin = 32000;
         bbox->xMax = -32000;
-        bbox->yMin = -1 * info->lines[0].asc + d6_to_double(info->glyphs[0].pos.y);
-        bbox->yMax = info->height - info->lines[0].asc +
-                     d6_to_double(info->glyphs[0].pos.y);
+        bbox->yMin = -1 * text->lines[0].asc + d6_to_double(text->glyphs[0].pos.y);
+        bbox->yMax = text->height - text->lines[0].asc +
+                     d6_to_double(text->glyphs[0].pos.y);
 
-        for (i = 0; i < info->length; ++i) {
-            if (info->glyphs[i].skip) continue;
-            double s = d6_to_double(info->glyphs[i].pos.x);
-            double e = s + d6_to_double(info->glyphs[i].advance.x);
-            bbox->xMin = FFMIN(bbox->xMin, s);
-            bbox->xMax = FFMAX(bbox->xMax, e);
+        for (i = 0; i < text->length; ++i) {
+            GlyphInfo *info = text->glyphs + i;
+            if (info->skip) continue;
+            while (info) {
+                double s = d6_to_double(info->pos.x);
+                double e = s + d6_to_double(info->advance.x);
+                bbox->xMin = FFMIN(bbox->xMin, s);
+                bbox->xMax = FFMAX(bbox->xMax, e);
+                info = info->next;
+            }
         }
     } else
         bbox->xMin = bbox->xMax = bbox->yMin = bbox->yMax = 0.;
@@ -877,6 +871,7 @@
     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;
+    render_priv->state.font_encoding = render_priv->state.style->Encoding;
 }
 
 /**
@@ -909,10 +904,10 @@
     render_priv->state.effect_type = EF_NONE;
     render_priv->state.effect_timing = 0;
     render_priv->state.effect_skip_timing = 0;
+    render_priv->state.bm_run_id = 0;
     ass_drawing_free(render_priv->state.drawing);
-    render_priv->state.drawing = ass_drawing_new(render_priv->fontconfig_priv,
-                                                 render_priv->state.font,
-                                                 render_priv->ftlibrary);
+    render_priv->state.drawing = ass_drawing_new(render_priv->library,
+            render_priv->ftlibrary);
 
     apply_transition_effects(render_priv, event);
 }
@@ -930,30 +925,18 @@
  * 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)
+static void draw_opaque_box(ASS_Renderer *render_priv, int asc, int desc,
+                            FT_Outline *ol, FT_Vector advance, int sx, int sy)
 {
-    int asc = 0, desc = 0;
     int i;
-    int adv = d16_to_d6(glyph->advance.x);
+    int adv = advance.x;
     double scale_y = render_priv->state.scale_y;
     double scale_x = render_priv->state.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
@@ -971,10 +954,8 @@
         { .x = -sx,         .y = -desc - sy },
     };
 
-    FT_Outline_Done(render_priv->ftlibrary, &og->outline);
-    FT_Outline_New(render_priv->ftlibrary, 4, 1, &og->outline);
+    FT_Outline_New(render_priv->ftlibrary, 4, 1, ol);
 
-    ol = &og->outline;
     ol->n_points = ol->n_contours = 0;
     for (i = 0; i < 4; i++) {
         ol->points[ol->n_points] = points[i];
@@ -987,40 +968,53 @@
  * Stroke an outline glyph in x/y direction.  Applies various fixups to get
  * around limitations of the FreeType stroker.
  */
-static void stroke_outline_glyph(ASS_Renderer *render_priv,
-                                 FT_OutlineGlyph *glyph, int sx, int sy)
+static void stroke_outline(ASS_Renderer *render_priv, FT_Outline *outline,
+                           int sx, int sy)
 {
     if (sx <= 0 && sy <= 0)
         return;
 
-    fix_freetype_stroker(*glyph, sx, sy);
+    fix_freetype_stroker(outline, sx, sy);
 
     // Borders are equal; use the regular stroker
     if (sx == sy && render_priv->state.stroker) {
         int error;
-        error = FT_Glyph_StrokeBorder((FT_Glyph *) glyph,
-                                      render_priv->state.stroker, 0, 1);
-        if (error)
+        unsigned n_points, n_contours;
+
+        FT_StrokerBorder border = FT_Outline_GetOutsideBorder(outline);
+        error = FT_Stroker_ParseOutline(render_priv->state.stroker, outline, 0);
+        if (error) {
             ass_msg(render_priv->library, MSGL_WARN,
-                    "FT_Glyph_Stroke error: %d", error);
+                    "FT_Stroker_ParseOutline failed, error: %d", error);
+        }
+        error = FT_Stroker_GetBorderCounts(render_priv->state.stroker, border,
+                &n_points, &n_contours);
+        if (error) {
+            ass_msg(render_priv->library, MSGL_WARN,
+                    "FT_Stroker_GetBorderCounts failed, error: %d", error);
+        }
+        FT_Outline_Done(render_priv->ftlibrary, outline);
+        FT_Outline_New(render_priv->ftlibrary, n_points, n_contours, outline);
+        outline->n_points = outline->n_contours = 0;
+        FT_Stroker_ExportBorder(render_priv->state.stroker, border, outline);
 
     // "Stroke" with the outline emboldener in two passes.
     // The outlines look uglier, but the emboldening never adds any points
     } else {
         int i;
-        FT_Outline *ol = &(*glyph)->outline;
         FT_Outline nol;
-        FT_Outline_New(render_priv->ftlibrary, ol->n_points,
-                       ol->n_contours, &nol);
-        FT_Outline_Copy(ol, &nol);
 
-        FT_Outline_Embolden(ol, sx * 2);
-        FT_Outline_Translate(ol, -sx, -sx);
+        FT_Outline_New(render_priv->ftlibrary, outline->n_points,
+                       outline->n_contours, &nol);
+        FT_Outline_Copy(outline, &nol);
+
+        FT_Outline_Embolden(outline, sx * 2);
+        FT_Outline_Translate(outline, -sx, -sx);
         FT_Outline_Embolden(&nol, sy * 2);
         FT_Outline_Translate(&nol, -sy, -sy);
 
-        for (i = 0; i < ol->n_points; i++)
-            ol->points[i].y = nol.points[i].y;
+        for (i = 0; i < outline->n_points; i++)
+            outline->points[i].y = nol.points[i].y;
 
         FT_Outline_Done(render_priv->ftlibrary, &nol);
     }
@@ -1030,37 +1024,41 @@
  * \brief Prepare glyph hash
  */
 static void
-fill_glyph_hash(ASS_Renderer *priv, GlyphHashKey *key,
-                ASS_Drawing *drawing, uint32_t ch)
+fill_glyph_hash(ASS_Renderer *priv, OutlineHashKey *outline_key,
+                GlyphInfo *info)
 {
-    if (drawing->hash) {
-        key->scale_x = double_to_d16(priv->state.scale_x);
-        key->scale_y = double_to_d16(priv->state.scale_y);
-        key->outline.x = priv->state.border_x * 0xFFFF;
-        key->outline.y = priv->state.border_y * 0xFFFF;
+    if (info->drawing) {
+        DrawingHashKey *key = &outline_key->u.drawing;
+        outline_key->type = OUTLINE_DRAWING;
+        key->scale_x = double_to_d16(info->scale_x);
+        key->scale_y = double_to_d16(info->scale_y);
+        key->outline.x = double_to_d16(info->border_x);
+        key->outline.y = double_to_d16(info->border_y);
         key->border_style = priv->state.style->BorderStyle;
-        key->drawing_hash = drawing->hash;
-        // not very clean, but works
-        key->size = drawing->scale;
-        key->ch = -1;
+        key->hash = info->drawing->hash;
+        key->text = info->drawing->text;
+        key->pbo = info->drawing->pbo;
+        key->scale = info->drawing->scale;
     } else {
-        key->font = priv->state.font;
-        key->size = priv->state.font_size;
-        key->ch = ch;
-        key->bold = priv->state.bold;
-        key->italic = priv->state.italic;
-        key->scale_x = double_to_d16(priv->state.scale_x);
-        key->scale_y = double_to_d16(priv->state.scale_y);
-        key->outline.x = priv->state.border_x * 0xFFFF;
-        key->outline.y = priv->state.border_y * 0xFFFF;
-        key->flags = priv->state.flags;
+        GlyphHashKey *key = &outline_key->u.glyph;
+        outline_key->type = OUTLINE_GLYPH;
+        key->font = info->font;
+        key->size = info->font_size;
+        key->face_index = info->face_index;
+        key->glyph_index = info->glyph_index;
+        key->bold = info->bold;
+        key->italic = info->italic;
+        key->scale_x = double_to_d16(info->scale_x);
+        key->scale_y = double_to_d16(info->scale_y);
+        key->outline.x = double_to_d16(info->border_x);
+        key->outline.y = double_to_d16(info->border_y);
+        key->flags = info->flags;
         key->border_style = priv->state.style->BorderStyle;
     }
 }
 
 /**
  * \brief Get normal and outline (border) glyphs
- * \param symbol ucs4 char
  * \param info out: struct filled with extracted data
  * Tries to get both glyphs from cache.
  * If they can't be found, gets a glyph from font face, generates outline with FT_Stroker,
@@ -1068,80 +1066,101 @@
  * 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 *priv, GlyphInfo *info)
 {
-    GlyphHashValue *val;
-    GlyphHashKey key;
+    OutlineHashValue *val;
+    OutlineHashKey key;
 
-    memset(&key, 0, sizeof(key));
-    memset(info, 0, sizeof(GlyphInfo));
+    memset(&info->hash_key, 0, sizeof(key));
 
-    fill_glyph_hash(render_priv, &key, drawing, symbol);
-    val = cache_find_glyph(render_priv->cache.glyph_cache, &key);
-    if (val) {
-        info->glyph = val->glyph;
-        info->outline_glyph = val->outline_glyph;
-        info->bbox = val->bbox_scaled;
-        info->advance.x = val->advance.x;
-        info->advance.y = val->advance.y;
-        if (drawing->hash) {
-            drawing->asc = val->asc;
-            drawing->desc = val->desc;
-        }
-    } else {
-        GlyphHashValue v;
-        if (drawing->hash) {
+    fill_glyph_hash(priv, &key, info);
+    val = ass_cache_get(priv->cache.outline_cache, &key);
+
+    if (!val) {
+        OutlineHashValue v;
+        memset(&v, 0, sizeof(v));
+
+        if (info->drawing) {
+            ASS_Drawing *drawing = info->drawing;
+            ass_drawing_hash(drawing);
             if(!ass_drawing_parse(drawing, 0))
                 return;
-            info->glyph = (FT_Glyph) drawing->glyph;
+            outline_copy(priv->ftlibrary, &drawing->outline,
+                    &v.outline);
+            v.advance.x = drawing->advance.x;
+            v.advance.y = drawing->advance.y;
+            v.asc = drawing->asc;
+            v.desc = drawing->desc;
+            key.u.drawing.text = strdup(drawing->text);
         } else {
-            info->glyph =
-                ass_font_get_glyph(render_priv->fontconfig_priv,
-                                   render_priv->state.font, symbol,
-                                   render_priv->settings.hinting,
-                                   render_priv->state.flags);
+            ass_face_set_size(info->font->faces[info->face_index],
+                    info->font_size);
+            ass_font_set_transform(info->font, info->scale_x,
+                    info->scale_y, NULL);
+            FT_Glyph glyph =
+                ass_font_get_glyph(priv->fontconfig_priv, info->font,
+                        info->symbol, info->face_index, info->glyph_index,
+                        priv->settings.hinting, info->flags);
+            if (glyph != NULL) {
+                outline_copy(priv->ftlibrary,
+                        &((FT_OutlineGlyph)glyph)->outline, &v.outline);
+                if (priv->settings.shaper == ASS_SHAPING_SIMPLE) {
+                    v.advance.x = d16_to_d6(glyph->advance.x);
+                    v.advance.y = d16_to_d6(glyph->advance.y);
+                }
+                FT_Done_Glyph(glyph);
+                ass_font_get_asc_desc(info->font, info->symbol,
+                        &v.asc, &v.desc);
+                v.asc  *= info->scale_y;
+                v.desc *= info->scale_y;
+            }
         }
-        if (!info->glyph)
+
+        if (!v.outline)
             return;
 
-        info->advance.x = d16_to_d6(info->glyph->advance.x);
-        info->advance.y = d16_to_d6(info->glyph->advance.y);
-        FT_Glyph_Get_CBox(info->glyph, FT_GLYPH_BBOX_SUBPIXELS, &info->bbox);
+        FT_Outline_Get_CBox(v.outline, &v.bbox_scaled);
+
+        if (priv->state.style->BorderStyle == 3 &&
+                (info->border_x > 0 || info->border_y > 0)) {
+            FT_Vector advance;
+
+            v.border = calloc(1, sizeof(FT_Outline));
 
-        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)
-                   && key.scale_x && key.scale_y) {
+            if (priv->settings.shaper == ASS_SHAPING_SIMPLE || info->drawing)
+                advance = v.advance;
+            else
+                advance = info->advance;
 
-            FT_Glyph_Copy(info->glyph, &info->outline_glyph);
-            stroke_outline_glyph(render_priv,
-                                 (FT_OutlineGlyph *) &info->outline_glyph,
-                                 double_to_d6(render_priv->state.border_x *
-                                              render_priv->border_scale),
-                                 double_to_d6(render_priv->state.border_y *
-                                              render_priv->border_scale));
+            draw_opaque_box(priv, v.asc, v.desc, v.border, advance,
+                    double_to_d6(info->border_x * priv->border_scale),
+                    double_to_d6(info->border_y * priv->border_scale));
+
+        } else if ((info->border_x > 0 || info->border_y > 0)
+                && double_to_d6(info->scale_x) && double_to_d6(info->scale_y)) {
+
+            outline_copy(priv->ftlibrary, v.outline, &v.border);
+            stroke_outline(priv, v.border,
+                    double_to_d6(info->border_x * priv->border_scale),
+                    double_to_d6(info->border_y * priv->border_scale));
         }
 
-        memset(&v, 0, sizeof(v));
-        v.glyph = info->glyph;
-        v.outline_glyph = info->outline_glyph;
-        v.advance = info->advance;
-        v.bbox_scaled = info->bbox;
-        if (drawing->hash) {
-            v.asc = drawing->asc;
-            v.desc = drawing->desc;
-        }
-        cache_add_glyph(render_priv->cache.glyph_cache, &key, &v);
+        v.lib = priv->ftlibrary;
+        val = ass_cache_put(priv->cache.outline_cache, &key, &v);
     }
+
+    info->hash_key.u.outline.outline = val;
+    info->outline = val->outline;
+    info->border = val->border;
+    info->bbox = val->bbox_scaled;
+    if (info->drawing || priv->settings.shaper == ASS_SHAPING_SIMPLE) {
+        info->cluster_advance.x = info->advance.x = val->advance.x;
+        info->cluster_advance.y = info->advance.y = val->advance.y;
+    }
+    info->asc = val->asc;
+    info->desc = val->desc;
+
+    ass_drawing_free(info->drawing);
 }
 
 /**
@@ -1150,7 +1169,7 @@
  * onto the screen plane.
  */
 static void
-transform_3d_points(FT_Vector shift, FT_Glyph glyph, double frx, double fry,
+transform_3d_points(FT_Vector shift, FT_Outline *outline, double frx, double fry,
                     double frz, double fax, double fay, double scale,
                     int yshift)
 {
@@ -1160,7 +1179,6 @@
     double cx = cos(frx);
     double cy = cos(fry);
     double cz = cos(frz);
-    FT_Outline *outline = &((FT_OutlineGlyph) glyph)->outline;
     FT_Vector *p = outline->points;
     double x, y, z, xx, yy, zz;
     int i, dist;
@@ -1203,19 +1221,19 @@
  * Rotates both glyphs by frx, fry and frz. Shift vector is added before rotation and subtracted after it.
  */
 static void
-transform_3d(FT_Vector shift, FT_Glyph *glyph, FT_Glyph *glyph2,
+transform_3d(FT_Vector shift, FT_Outline *outline, FT_Outline *border,
              double frx, double fry, double frz, double fax, double fay,
              double scale, int yshift)
 {
     frx = -frx;
     frz = -frz;
     if (frx != 0. || fry != 0. || frz != 0. || fax != 0. || fay != 0.) {
-        if (glyph && *glyph)
-            transform_3d_points(shift, *glyph, frx, fry, frz,
+        if (outline)
+            transform_3d_points(shift, outline, frx, fry, frz,
                                 fax, fay, scale, yshift);
 
-        if (glyph2 && *glyph2)
-            transform_3d_points(shift, *glyph2, frx, fry, frz,
+        if (border)
+            transform_3d_points(shift, border, frx, fry, frz,
                                 fax, fay, scale, yshift);
     }
 }
@@ -1232,81 +1250,80 @@
 get_bitmap_glyph(ASS_Renderer *render_priv, GlyphInfo *info)
 {
     BitmapHashValue *val;
-    BitmapHashKey *key = &info->hash_key;
-
-    val = cache_find_bitmap(render_priv->cache.bitmap_cache, key);
+    OutlineBitmapHashKey *key = &info->hash_key.u.outline;
 
-    if (val) {
-        info->bm = val->bm;
-        info->bm_o = val->bm_o;
-        info->bm_s = val->bm_s;
-    } else {
+    if (!info->outline || info->symbol == '\n' || info->symbol == 0 || info->skip)
+        return;
+
+    val = ass_cache_get(render_priv->cache.bitmap_cache, &info->hash_key);
+
+    if (!val) {
         FT_Vector shift;
         BitmapHashValue hash_val;
         int error;
         double fax_scaled, fay_scaled;
-        info->bm = info->bm_o = info->bm_s = 0;
-        if (info->glyph && info->symbol != '\n' && info->symbol != 0
-            && !info->skip) {
-            FT_Glyph glyph;
-            FT_Glyph outline;
-            double scale_x = render_priv->font_scale_x;
+        FT_Outline *outline, *border;
+        double scale_x = render_priv->font_scale_x;
+
+        hash_val.bm = hash_val.bm_o = hash_val.bm_s = 0;
+
+        outline_copy(render_priv->ftlibrary, info->outline, &outline);
+        outline_copy(render_priv->ftlibrary, info->border, &border);
 
-            FT_Glyph_Copy(info->glyph, &glyph);
-            FT_Glyph_Copy(info->outline_glyph, &outline);
-            // calculating rotation shift vector (from rotation origin to the glyph basepoint)
-            shift.x = key->shift_x;
-            shift.y = key->shift_y;
-            fax_scaled = info->fax *
-                         render_priv->state.scale_x;
-            fay_scaled = info->fay * render_priv->state.scale_y;
-            // apply rotation
-            transform_3d(shift, &glyph, &outline,
-                         info->frx, info->fry, info->frz, fax_scaled,
-                         fay_scaled, render_priv->font_scale, info->asc);
+        // calculating rotation shift vector (from rotation origin to the glyph basepoint)
+        shift.x = key->shift_x;
+        shift.y = key->shift_y;
+        fax_scaled = info->fax * render_priv->state.scale_x;
+        fay_scaled = info->fay * render_priv->state.scale_y;
 
-            // PAR correction scaling
-            FT_Matrix m = { double_to_d16(scale_x), 0,
-                            0, double_to_d16(1.0) };
+        // apply rotation
+        transform_3d(shift, outline, border,
+                info->frx, info->fry, info->frz, fax_scaled,
+                fay_scaled, render_priv->font_scale, info->asc);
+
+        // PAR correction scaling
+        FT_Matrix m = { double_to_d16(scale_x), 0,
+            0, double_to_d16(1.0) };
 
-            // subpixel shift
-            if (glyph) {
-                FT_Outline *outl = &((FT_OutlineGlyph) glyph)->outline;
-                if (scale_x != 1.0)
-                    FT_Outline_Transform(outl, &m);
-                FT_Outline_Translate(outl, key->advance.x, -key->advance.y);
-            }
-            if (outline) {
-                FT_Outline *outl = &((FT_OutlineGlyph) outline)->outline;
-                if (scale_x != 1.0)
-                    FT_Outline_Transform(outl, &m);
-                FT_Outline_Translate(outl, key->advance.x, -key->advance.y);
-            }
-            // render glyph
-            error = glyph_to_bitmap(render_priv->library,
-                                    render_priv->synth_priv,
-                                    glyph, outline,
-                                    &info->bm, &info->bm_o,
-                                    &info->bm_s, info->be,
-                                    info->blur * render_priv->border_scale,
-                                    key->shadow_offset, key->border_style);
-            if (error)
-                info->symbol = 0;
+        // subpixel shift
+        if (outline) {
+            if (scale_x != 1.0)
+                FT_Outline_Transform(outline, &m);
+            FT_Outline_Translate(outline, key->advance.x, -key->advance.y);
+        }
+        if (border) {
+            if (scale_x != 1.0)
+                FT_Outline_Transform(border, &m);
+            FT_Outline_Translate(border, key->advance.x, -key->advance.y);
+        }
 
-            // add bitmaps to cache
-            hash_val.bm_o = info->bm_o;
-            hash_val.bm = info->bm;
-            hash_val.bm_s = info->bm_s;
-            cache_add_bitmap(render_priv->cache.bitmap_cache, key, &hash_val);
+        // render glyph
+        error = outline_to_bitmap3(render_priv->library,
+                render_priv->synth_priv,
+                render_priv->ftlibrary,
+                outline, border,
+                &hash_val.bm, &hash_val.bm_o,
+                &hash_val.bm_s, info->be,
+                info->blur * render_priv->border_scale,
+                key->shadow_offset,
+                render_priv->state.style->BorderStyle);
+        if (error)
+            info->symbol = 0;
 
-            FT_Done_Glyph(glyph);
-            FT_Done_Glyph(outline);
-        }
+        val = ass_cache_put(render_priv->cache.bitmap_cache, &info->hash_key,
+                &hash_val);
+
+        outline_free(render_priv->ftlibrary, outline);
+        outline_free(render_priv->ftlibrary, border);
     }
 
+    info->bm = val->bm;
+    info->bm_o = val->bm_o;
+    info->bm_s = val->bm_s;
+
     // VSFilter compatibility: invisible fill and no border?
     // In this case no shadow is supposed to be rendered.
-    if (!info->outline_glyph && (info->c[0] & 0xFF) == 0xFF)
+    if (!info->border && (info->c[0] & 0xFF) == 0xFF)
         info->bm_s = 0;
 }
 
@@ -1434,6 +1451,7 @@
     double pen_shift_x;
     double pen_shift_y;
     int cur_line;
+    int run_offset;
     TextInfo *text_info = &render_priv->text_info;
 
     last_space = -1;
@@ -1474,12 +1492,13 @@
                                            sizeof(LineInfo) *
                                            text_info->max_lines);
             }
-            if (lead < text_info->length)
+            if (lead < text_info->length) {
                 text_info->glyphs[lead].linebreak = break_type;
-            last_space = -1;
-            s1 = text_info->glyphs + lead;
-            s_offset = d6_to_double(s1->bbox.xMin + s1->pos.x);
-            text_info->n_lines++;
+                last_space = -1;
+                s1 = text_info->glyphs + lead;
+                s_offset = d6_to_double(s1->bbox.xMin + s1->pos.x);
+                text_info->n_lines++;
+            }
         }
     }
 #define DIFF(x,y) (((x) < (y)) ? (y - x) : (x - y))
@@ -1543,6 +1562,7 @@
     pen_shift_x = 0.;
     pen_shift_y = 0.;
     cur_line = 1;
+    run_offset = 0;
 
     i = 0;
     cur = text_info->glyphs + i;
@@ -1558,86 +1578,31 @@
             double height =
                 text_info->lines[cur_line - 1].desc +
                 text_info->lines[cur_line].asc;
+            text_info->lines[cur_line - 1].len = i -
+                text_info->lines[cur_line - 1].offset;
+            text_info->lines[cur_line].offset = i;
             cur_line++;
+            run_offset++;
             pen_shift_x = d6_to_double(-cur->pos.x);
             pen_shift_y += height + render_priv->settings.line_spacing;
             ass_msg(render_priv->library, MSGL_DBG2,
                    "shifting from %d to %d by (%f, %f)", i,
                    text_info->length - 1, pen_shift_x, pen_shift_y);
         }
+        cur->bm_run_id += run_offset;
         cur->pos.x += double_to_d6(pen_shift_x);
         cur->pos.y += double_to_d6(pen_shift_y);
     }
-}
-
-/**
- * \brief determine karaoke effects
- * Karaoke effects cannot be calculated during parse stage (get_next_char()),
- * so they are done in a separate step.
- * Parse stage: when karaoke style override is found, its parameters are stored in the next glyph's
- * (the first glyph of the karaoke word)'s effect_type and effect_timing.
- * This function:
- * 1. sets effect_type for all glyphs in the word (_karaoke_ word)
- * 2. sets effect_timing for all glyphs to x coordinate of the border line between the left and right karaoke parts
- * (left part is filled with PrimaryColour, right one - with SecondaryColour).
- */
-static void process_karaoke_effects(ASS_Renderer *render_priv)
-{
-    GlyphInfo *cur, *cur2;
-    GlyphInfo *s1, *e1;      // start and end of the current word
-    GlyphInfo *s2;           // start of the next word
-    int i;
-    int timing;                 // current timing
-    int tm_start, tm_end;       // timings at start and end of the current word
-    int tm_current;
-    double dt;
-    int x;
-    int x_start, x_end;
+    text_info->lines[cur_line - 1].len =
+        text_info->length - text_info->lines[cur_line - 1].offset;
 
-    tm_current = render_priv->time - render_priv->state.event->Start;
-    timing = 0;
-    s1 = s2 = 0;
-    for (i = 0; i <= render_priv->text_info.length; ++i) {
-        cur = render_priv->text_info.glyphs + i;
-        if ((i == render_priv->text_info.length)
-            || (cur->effect_type != EF_NONE)) {
-            s1 = s2;
-            s2 = cur;
-            if (s1) {
-                e1 = s2 - 1;
-                tm_start = timing + s1->effect_skip_timing;
-                tm_end = tm_start + s1->effect_timing;
-                timing = tm_end;
-                x_start = 1000000;
-                x_end = -1000000;
-                for (cur2 = s1; cur2 <= e1; ++cur2) {
-                    x_start = FFMIN(x_start, d6_to_int(cur2->bbox.xMin + cur2->pos.x));
-                    x_end = FFMAX(x_end, d6_to_int(cur2->bbox.xMax + cur2->pos.x));
-                }
-
-                dt = (tm_current - tm_start);
-                if ((s1->effect_type == EF_KARAOKE)
-                    || (s1->effect_type == EF_KARAOKE_KO)) {
-                    if (dt > 0)
-                        x = x_end + 1;
-                    else
-                        x = x_start;
-                } else if (s1->effect_type == EF_KARAOKE_KF) {
-                    dt /= (tm_end - tm_start);
-                    x = x_start + (x_end - x_start) * dt;
-                } else {
-                    ass_msg(render_priv->library, MSGL_ERR,
-                            "Unknown effect type");
-                    continue;
-                }
-
-                for (cur2 = s1; cur2 <= e1; ++cur2) {
-                    cur2->effect_type = s1->effect_type;
-                    cur2->effect_timing = x - d6_to_int(cur2->pos.x);
-                }
-            }
-        }
+#if 0
+    // print line info
+    for (i = 0; i < text_info->n_lines; i++) {
+        printf("line %d offset %d length %d\n", i, text_info->lines[i].offset,
+                text_info->lines[i].len);
     }
+#endif
 }
 
 /**
@@ -1680,38 +1645,22 @@
  * Prepare bitmap hash key of a glyph
  */
 static void
-fill_bitmap_hash(ASS_Renderer *priv, BitmapHashKey *hash_key,
-                 ASS_Drawing *drawing, FT_Vector pen, uint32_t code)
+fill_bitmap_hash(ASS_Renderer *priv, GlyphInfo *info,
+                 OutlineBitmapHashKey *hash_key)
 {
-    if (!drawing->hash) {
-        hash_key->font = priv->state.font;
-        hash_key->size = priv->state.font_size;
-        hash_key->bold = priv->state.bold;
-        hash_key->italic = priv->state.italic;
-    } else {
-        hash_key->drawing_hash = drawing->hash;
-        hash_key->size = drawing->scale;
-    }
-    hash_key->ch = code;
-    hash_key->outline.x = double_to_d16(priv->state.border_x);
-    hash_key->outline.y = double_to_d16(priv->state.border_y);
-    hash_key->scale_x = double_to_d16(priv->state.scale_x);
-    hash_key->scale_y = double_to_d16(priv->state.scale_y);
-    hash_key->frx = rot_key(priv->state.frx);
-    hash_key->fry = rot_key(priv->state.fry);
-    hash_key->frz = rot_key(priv->state.frz);
-    hash_key->fax = double_to_d16(priv->state.fax);
-    hash_key->fay = double_to_d16(priv->state.fay);
-    hash_key->be = priv->state.be;
-    hash_key->blur = priv->state.blur;
-    hash_key->border_style = priv->state.style->BorderStyle;
+    hash_key->frx = rot_key(info->frx);
+    hash_key->fry = rot_key(info->fry);
+    hash_key->frz = rot_key(info->frz);
+    hash_key->fax = double_to_d16(info->fax);
+    hash_key->fay = double_to_d16(info->fay);
+    hash_key->be = info->be;
+    hash_key->blur = info->blur;
     hash_key->shadow_offset.x = double_to_d6(
-            priv->state.shadow_x * priv->border_scale -
-            (int) (priv->state.shadow_x * priv->border_scale));
+            info->shadow_x * priv->border_scale -
+            (int) (info->shadow_x * priv->border_scale));
     hash_key->shadow_offset.y = double_to_d6(
-            priv->state.shadow_y * priv->border_scale -
-            (int) (priv->state.shadow_y * priv->border_scale));
-    hash_key->flags = priv->state.flags;
+            info->shadow_y * priv->border_scale -
+            (int) (info->shadow_y * priv->border_scale));
 }
 
 /**
@@ -1734,7 +1683,6 @@
     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;
@@ -1754,11 +1702,9 @@
 
     drawing = render_priv->state.drawing;
     text_info->length = 0;
-    pen.x = 0;
-    pen.y = 0;
-    previous = 0;
     num_glyphs = 0;
     p = event->Text;
+
     // Event parsing.
     while (1) {
         // get next char, executing style override
@@ -1769,15 +1715,26 @@
                 ass_drawing_add_char(drawing, (char) code);
         } while (code && render_priv->state.drawing_mode);      // skip everything in drawing mode
 
+        if (text_info->length >= text_info->max_glyphs) {
+            // Raise maximum number of glyphs
+            text_info->max_glyphs *= 2;
+            text_info->glyphs = glyphs =
+                realloc(text_info->glyphs,
+                        sizeof(GlyphInfo) * text_info->max_glyphs);
+        }
+
+        // Clear current GlyphInfo
+        memset(&glyphs[text_info->length], 0, sizeof(GlyphInfo));
+
         // Parse drawing
         if (drawing->i) {
             drawing->scale_x = render_priv->state.scale_x *
                                      render_priv->font_scale;
             drawing->scale_y = render_priv->state.scale_y *
                                      render_priv->font_scale;
-            ass_drawing_hash(drawing);
             p--;
-            code = -1;
+            code = 0xfffc; // object replacement character
+            glyphs[text_info->length].drawing = drawing;
         }
 
         // face could have been changed in get_next_char
@@ -1789,61 +1746,9 @@
         if (code == 0)
             break;
 
-        if (text_info->length >= text_info->max_glyphs) {
-            // Raise maximum number of glyphs
-            text_info->max_glyphs *= 2;
-            text_info->glyphs = glyphs =
-                realloc(text_info->glyphs,
-                        sizeof(GlyphInfo) * text_info->max_glyphs);
-        }
-
-        // Add kerning to pen
-        if (kern && previous && code && !drawing->hash) {
-            FT_Vector delta;
-            delta =
-                ass_font_get_kerning(render_priv->state.font, previous,
-                                     code);
-            pen.x += delta.x * render_priv->state.scale_x;
-            pen.y += delta.y * render_priv->state.scale_y;
-        }
-
-        ass_font_set_transform(render_priv->state.font,
-                               render_priv->state.scale_x,
-                               render_priv->state.scale_y, NULL);
-
-        get_outline_glyph(render_priv, code,
-                          glyphs + text_info->length, drawing);
-
-        // Add additional space after italic to non-italic style changes
-        if (text_info->length &&
-            glyphs[text_info->length - 1].hash_key.italic &&
-            !render_priv->state.italic) {
-            int back = text_info->length - 1;
-            GlyphInfo *og = &glyphs[back];
-            while (back && og->bbox.xMax - og->bbox.xMin == 0
-                   && og->hash_key.italic)
-                og = &glyphs[--back];
-            if (og->bbox.xMax > og->advance.x) {
-                // The FreeType oblique slants by 6/16
-                pen.x += og->bbox.yMax * 0.375;
-            }
-        }
-
-        glyphs[text_info->length].pos.x = pen.x;
-        glyphs[text_info->length].pos.y = pen.y;
-
-        pen.x += glyphs[text_info->length].advance.x;
-        pen.x += double_to_d6(render_priv->state.hspacing *
-                              render_priv->font_scale
-                              * render_priv->state.scale_x);
-        pen.y += glyphs[text_info->length].advance.y;
-        pen.y += (render_priv->state.fay * render_priv->state.scale_y) *
-                 glyphs[text_info->length].advance.x;
-
-        previous = code;
-
+        // Fill glyph information
         glyphs[text_info->length].symbol = code;
-        glyphs[text_info->length].linebreak = 0;
+        glyphs[text_info->length].font = render_priv->state.font;
         for (i = 0; i < 4; ++i) {
             uint32_t clr = render_priv->state.c[i];
             change_alpha(&clr,
@@ -1855,53 +1760,108 @@
             render_priv->state.effect_timing;
         glyphs[text_info->length].effect_skip_timing =
             render_priv->state.effect_skip_timing;
+        glyphs[text_info->length].font_size = ensure_font_size(render_priv,
+                    render_priv->state.font_size * render_priv->font_scale);
         glyphs[text_info->length].be = render_priv->state.be;
         glyphs[text_info->length].blur = render_priv->state.blur;
         glyphs[text_info->length].shadow_x = render_priv->state.shadow_x;
         glyphs[text_info->length].shadow_y = render_priv->state.shadow_y;
+        glyphs[text_info->length].scale_x= render_priv->state.scale_x;
+        glyphs[text_info->length].scale_y = render_priv->state.scale_y;
+        glyphs[text_info->length].border_x= render_priv->state.border_x;
+        glyphs[text_info->length].border_y = render_priv->state.border_y;
+        glyphs[text_info->length].bold = render_priv->state.bold;
+        glyphs[text_info->length].italic = render_priv->state.italic;
+        glyphs[text_info->length].flags = render_priv->state.flags;
         glyphs[text_info->length].frx = render_priv->state.frx;
         glyphs[text_info->length].fry = render_priv->state.fry;
         glyphs[text_info->length].frz = render_priv->state.frz;
         glyphs[text_info->length].fax = render_priv->state.fax;
         glyphs[text_info->length].fay = render_priv->state.fay;
-        if (drawing->hash) {
-            glyphs[text_info->length].asc = drawing->asc;
-            glyphs[text_info->length].desc = drawing->desc;
-        } else {
-            ass_font_get_asc_desc(render_priv->state.font, code,
-                                  &glyphs[text_info->length].asc,
-                                  &glyphs[text_info->length].desc);
+        glyphs[text_info->length].bm_run_id = render_priv->state.bm_run_id;
 
-            glyphs[text_info->length].asc *= render_priv->state.scale_y;
-            glyphs[text_info->length].desc *= render_priv->state.scale_y;
+        if (glyphs[text_info->length].drawing) {
+            drawing = render_priv->state.drawing =
+                ass_drawing_new(render_priv->library, render_priv->ftlibrary);
         }
 
-        // fill bitmap hash
-        fill_bitmap_hash(render_priv, &glyphs[text_info->length].hash_key,
-                         drawing, pen, code);
-
         text_info->length++;
 
         render_priv->state.effect_type = EF_NONE;
         render_priv->state.effect_timing = 0;
         render_priv->state.effect_skip_timing = 0;
 
-        if (drawing->hash) {
-            ass_drawing_free(drawing);
-            drawing = render_priv->state.drawing =
-                ass_drawing_new(render_priv->fontconfig_priv,
-                    render_priv->state.font,
-                    render_priv->ftlibrary);
-        }
     }
 
-
     if (text_info->length == 0) {
         // no valid symbols in the event; this can be smth like {comment}
         free_render_context(render_priv);
         return 1;
     }
 
+    // Find shape runs and shape text
+    ass_shaper_set_base_direction(render_priv->shaper,
+            resolve_base_direction(render_priv->state.font_encoding));
+    ass_shaper_find_runs(render_priv->shaper, render_priv, glyphs,
+            text_info->length);
+    ass_shaper_shape(render_priv->shaper, text_info);
+
+    // Retrieve glyphs
+    for (i = 0; i < text_info->length; i++) {
+        GlyphInfo *info = glyphs + i;
+        while (info) {
+            get_outline_glyph(render_priv, info);
+            info = info->next;
+        }
+        info = glyphs + i;
+
+        // Add additional space after italic to non-italic style changes
+        if (i && glyphs[i - 1].italic && !info->italic) {
+            int back = i - 1;
+            GlyphInfo *og = &glyphs[back];
+            while (back && og->bbox.xMax - og->bbox.xMin == 0
+                    && og->italic)
+                og = &glyphs[--back];
+            if (og->bbox.xMax > og->cluster_advance.x)
+                og->cluster_advance.x = og->bbox.xMax;
+        }
+
+        // add horizontal letter spacing
+        info->cluster_advance.x += double_to_d6(render_priv->state.hspacing *
+                render_priv->font_scale * info->scale_x);
+
+        // add displacement for vertical shearing
+        info->cluster_advance.y += (info->fay * info->scale_y) * info->cluster_advance.x;
+
+    }
+
+    // Preliminary layout (for line wrapping)
+    previous = 0;
+    pen.x = 0;
+    pen.y = 0;
+    for (i = 0; i < text_info->length; i++) {
+        GlyphInfo *info = glyphs + i;
+        FT_Vector cluster_pen = pen;
+        while (info) {
+            info->pos.x = cluster_pen.x;
+            info->pos.y = cluster_pen.y;
+
+            cluster_pen.x += info->advance.x;
+            cluster_pen.y += info->advance.y;
+
+            // fill bitmap hash
+            info->hash_key.type = BITMAP_OUTLINE;
+            fill_bitmap_hash(render_priv, info, &info->hash_key.u.outline);
+
+            info = info->next;
+        }
+        info = glyphs + i;
+        pen.x += info->cluster_advance.x;
+        pen.y += info->cluster_advance.y;
+        previous = info->symbol;
+    }
+
+
     // depends on glyph x coordinates being monotonous, so it should be done before line wrap
     process_karaoke_effects(render_priv);
 
@@ -1917,40 +1877,64 @@
     MarginV =
         (event->MarginV) ? event->MarginV : render_priv->state.style->MarginV;
 
-    if (render_priv->state.evt_type != EVENT_HSCROLL) {
-        double max_text_width;
+    // calculate max length of a line
+    double max_text_width =
+        x2scr(render_priv, render_priv->track->PlayResX - MarginR) -
+        x2scr(render_priv, MarginL);
 
-        // calculate max length of a line
-        max_text_width =
-            x2scr(render_priv,
-                  render_priv->track->PlayResX - MarginR) -
-            x2scr(render_priv, MarginL);
-
+    // wrap lines
+    if (render_priv->state.evt_type != EVENT_HSCROLL) {
         // rearrange text in several lines
         wrap_lines_smart(render_priv, max_text_width);
+    } else {
+        // no breaking or wrapping, everything in a single line
+        text_info->lines[0].offset = 0;
+        text_info->lines[0].len = text_info->length;
+        text_info->n_lines = 1;
+        measure_text(render_priv);
+    }
 
-        // align text
-        last_break = -1;
-        for (i = 1; i < text_info->length + 1; ++i) {   // (text_info->length + 1) is the end of the last line
-            if ((i == text_info->length)
-                || glyphs[i].linebreak) {
-                double width, shift = 0;
-                GlyphInfo *first_glyph =
-                    glyphs + last_break + 1;
-                GlyphInfo *last_glyph = glyphs + i - 1;
+    // Reorder text into visual order
+    FriBidiStrIndex *cmap = ass_shaper_reorder(render_priv->shaper, text_info);
 
-                while (first_glyph < last_glyph && first_glyph->skip)
-                    first_glyph++;
+    // Reposition according to the map
+    pen.x = 0;
+    pen.y = 0;
+    int lineno = 1;
+    for (i = 0; i < text_info->length; i++) {
+        GlyphInfo *info = glyphs + cmap[i];
+        if (glyphs[i].linebreak) {
+            pen.x = 0;
+            pen.y += double_to_d6(text_info->lines[lineno-1].desc);
+            pen.y += double_to_d6(text_info->lines[lineno].asc);
+            pen.y += double_to_d6(render_priv->settings.line_spacing);
+            lineno++;
+        }
+        if (info->skip) continue;
+        FT_Vector cluster_pen = pen;
+        while (info) {
+            info->pos.x = info->offset.x + cluster_pen.x;
+            info->pos.y = info->offset.y + cluster_pen.y;
+            cluster_pen.x += info->advance.x;
+            cluster_pen.y += info->advance.y;
+            info = info->next;
+        }
+        info = glyphs + cmap[i];
+        pen.x += info->cluster_advance.x;
+        pen.y += info->cluster_advance.y;
+    }
 
-                while ((last_glyph > first_glyph)
-                       && ((last_glyph->symbol == '\n')
-                           || (last_glyph->symbol == 0)
-                           || (last_glyph->skip)))
-                    last_glyph--;
-
-                width = d6_to_double(
-                    last_glyph->pos.x + last_glyph->advance.x -
-                    first_glyph->pos.x);
+    // align lines
+    if (render_priv->state.evt_type != EVENT_HSCROLL) {
+        last_break = -1;
+        double width = 0;
+        for (i = 0; i <= text_info->length; ++i) {   // (text_info->length + 1) is the end of the last line
+            if ((i == text_info->length) || glyphs[i].linebreak) {
+                // remove letter spacing (which is included in cluster_advance)
+                if (i > 0)
+                    width -= render_priv->state.hspacing * render_priv->font_scale *
+                        glyphs[i-1].scale_x;
+                double shift = 0;
                 if (halign == HALIGN_LEFT) {    // left aligned, no action
                     shift = 0;
                 } else if (halign == HALIGN_RIGHT) {    // right aligned
@@ -1959,13 +1943,20 @@
                     shift = (max_text_width - width) / 2.0;
                 }
                 for (j = last_break + 1; j < i; ++j) {
-                    glyphs[j].pos.x += double_to_d6(shift);
+                    GlyphInfo *info = glyphs + j;
+                    while (info) {
+                        info->pos.x += double_to_d6(shift);
+                        info = info->next;
+                    }
                 }
                 last_break = i - 1;
+                width = 0;
+            }
+            if (i < text_info->length && !glyphs[i].skip &&
+                    glyphs[i].symbol != '\n' && glyphs[i].symbol != 0) {
+                width += d6_to_double(glyphs[i].cluster_advance.x);
             }
         }
-    } else {                    // render_priv->state.evt_type == EVENT_HSCROLL
-        measure_text(render_priv);
     }
 
     // determing text bounding box
@@ -2091,32 +2082,38 @@
 
         for (i = 0; i < text_info->length; ++i) {
             GlyphInfo *info = glyphs + i;
+            while (info) {
+                OutlineBitmapHashKey *key = &info->hash_key.u.outline;
 
-            if (info->hash_key.frx || info->hash_key.fry
-                || info->hash_key.frz || info->hash_key.fax
-                || info->hash_key.fay) {
-                info->hash_key.shift_x = info->pos.x + double_to_d6(device_x - center.x);
-                info->hash_key.shift_y =
-                    -(info->pos.y + double_to_d6(device_y - center.y));
-            } else {
-                info->hash_key.shift_x = 0;
-                info->hash_key.shift_y = 0;
+                if (key->frx || key->fry || key->frz || key->fax || key->fay) {
+                    key->shift_x = info->pos.x + double_to_d6(device_x - center.x);
+                    key->shift_y = -(info->pos.y + double_to_d6(device_y - center.y));
+                } else {
+                    key->shift_x = 0;
+                    key->shift_y = 0;
+                }
+                info = info->next;
             }
         }
     }
 
     // convert glyphs to bitmaps
-    device_x *= render_priv->font_scale_x;
+    int left = render_priv->settings.left_margin;
+    device_x = (device_x - left) * render_priv->font_scale_x + left;
     for (i = 0; i < text_info->length; ++i) {
-        GlyphInfo *g = glyphs + i;
-        g->pos.x *= render_priv->font_scale_x;
-        g->hash_key.advance.x =
-            double_to_d6(device_x - (int) device_x +
-            d6_to_double(g->pos.x & SUBPIXEL_MASK)) & ~SUBPIXEL_ACCURACY;
-        g->hash_key.advance.y =
-            double_to_d6(device_y - (int) device_y +
-            d6_to_double(g->pos.y & SUBPIXEL_MASK)) & ~SUBPIXEL_ACCURACY;
-        get_bitmap_glyph(render_priv, glyphs + i);
+        GlyphInfo *info = glyphs + i;
+        while (info) {
+            OutlineBitmapHashKey *key = &info->hash_key.u.outline;
+            info->pos.x *= render_priv->font_scale_x;
+            key->advance.x =
+                double_to_d6(device_x - (int) device_x +
+                        d6_to_double(info->pos.x & SUBPIXEL_MASK)) & ~SUBPIXEL_ACCURACY;
+            key->advance.y =
+                double_to_d6(device_y - (int) device_y +
+                        d6_to_double(info->pos.y & SUBPIXEL_MASK)) & ~SUBPIXEL_ACCURACY;
+            get_bitmap_glyph(render_priv, info);
+            info = info->next;
+        }
     }
 
     memset(event_images, 0, sizeof(*event_images));
@@ -2131,6 +2128,7 @@
     event_images->event = event;
     event_images->imgs = render_text(render_priv, (int) device_x, (int) device_y);
 
+    ass_shaper_cleanup(render_priv->shaper, text_info);
     free_render_context(render_priv);
 
     return 0;
@@ -2154,24 +2152,16 @@
  */
 static void check_cache_limits(ASS_Renderer *priv, CacheStore *cache)
 {
-    if (cache->bitmap_cache->cache_size > cache->bitmap_max_size) {
-        ass_msg(priv->library, MSGL_V,
-                "Hitting hard bitmap cache limit (was: %ld bytes), "
-                "resetting.", (long) cache->bitmap_cache->cache_size);
-        cache->bitmap_cache = ass_bitmap_cache_reset(cache->bitmap_cache);
-        cache->composite_cache = ass_composite_cache_reset(
-            cache->composite_cache);
+    if (ass_cache_empty(cache->bitmap_cache, cache->bitmap_max_size)) {
+        ass_cache_empty(cache->composite_cache, 0);
         ass_free_images(priv->prev_images_root);
         priv->prev_images_root = 0;
     }
-
-    if (cache->glyph_cache->count > cache->glyph_max
-        || cache->glyph_cache->cache_size > cache->bitmap_max_size) {
-        ass_msg(priv->library, MSGL_V,
-            "Hitting hard glyph cache limit (was: %d glyphs, %ld bytes), "
-            "resetting.",
-            cache->glyph_cache->count, (long) cache->glyph_cache->cache_size);
-        cache->glyph_cache = ass_glyph_cache_reset(cache->glyph_cache);
+    if (ass_cache_empty(cache->outline_cache, cache->glyph_max)) {
+        ass_cache_empty(cache->bitmap_cache, 0);
+        ass_cache_empty(cache->composite_cache, 0);
+        ass_free_images(priv->prev_images_root);
+        priv->prev_images_root = 0;
     }
 }
 
@@ -2202,7 +2192,7 @@
     render_priv->track = track;
     render_priv->time = now;
 
-    ass_lazy_track_init(render_priv);
+    ass_lazy_track_init(render_priv->library, render_priv->track);
 
     render_priv->font_scale = settings_priv->font_size_coeff *
         render_priv->orig_height / render_priv->track->PlayResY;
@@ -2213,6 +2203,11 @@
     else
         render_priv->border_scale = 1.;
 
+    ass_shaper_set_kerning(render_priv->shaper, track->Kerning);
+    if (track->Language)
+        ass_shaper_set_language(render_priv->shaper, track->Language);
+    ass_shaper_set_level(render_priv->shaper, render_priv->settings.shaper);
+
     // PAR correction
     render_priv->font_scale_x = render_priv->settings.aspect /
                                 render_priv->settings.storage_aspect;
--- a/libass/ass_render.h	Sat Dec 03 21:33:28 2011 +0000
+++ b/libass/ass_render.h	Sat Dec 03 21:35:56 2011 +0000
@@ -27,6 +27,9 @@
 #include FT_GLYPH_H
 #include FT_SYNTHESIS_H
 
+// XXX: fix the inclusion mess so we can avoid doing this here
+typedef struct ass_shaper ASS_Shaper;
+
 #include "ass.h"
 #include "ass_font.h"
 #include "ass_bitmap.h"
@@ -73,6 +76,7 @@
     double aspect;              // frame aspect ratio, d_width / d_height.
     double storage_aspect;      // pixel ratio of the source image
     ASS_Hinting hinting;
+    ASS_ShapingLevel shaper;
 
     char *default_font;
     char *default_family;
@@ -96,19 +100,26 @@
 
 // describes a glyph
 // GlyphInfo and TextInfo are used for text centering and word-wrapping operations
-typedef struct {
+typedef struct glyph_info {
     unsigned symbol;
     unsigned skip;              // skip glyph when layouting text
-    FT_Glyph glyph;
-    FT_Glyph outline_glyph;
+    ASS_Font *font;
+    int face_index;
+    int glyph_index;
+    double font_size;
+    ASS_Drawing *drawing;
+    FT_Outline *outline;
+    FT_Outline *border;
     Bitmap *bm;                 // glyph bitmap
     Bitmap *bm_o;               // outline bitmap
     Bitmap *bm_s;               // shadow bitmap
     FT_BBox bbox;
     FT_Vector pos;
+    FT_Vector offset;
     char linebreak;             // the first (leading) glyph of some line ?
     uint32_t c[4];              // colors
     FT_Vector advance;          // 26.6
+    FT_Vector cluster_advance;
     Effect effect_type;
     int effect_timing;          // time duration of current karaoke word
     // after process_karaoke_effects: distance in pixels from the glyph origin.
@@ -121,12 +132,24 @@
     double shadow_y;
     double frx, fry, frz;       // rotation
     double fax, fay;            // text shearing
+    double scale_x, scale_y;
+    double border_x, border_y;
+    unsigned italic;
+    unsigned bold;
+    int flags;
+
+    int bm_run_id;
+    int shape_run_id;
 
     BitmapHashKey hash_key;
+
+    // next glyph in this cluster
+    struct glyph_info *next;
 } GlyphInfo;
 
 typedef struct {
     double asc, desc;
+    int offset, len;
 } LineInfo;
 
 typedef struct {
@@ -147,7 +170,6 @@
     int parsed_tags;
 
     ASS_Font *font;
-    char *font_path;
     double font_size;
     int flags;                  // decoration flags (underline/strike-through)
 
@@ -186,6 +208,9 @@
     int effect_timing;
     int effect_skip_timing;
 
+    // bitmap run id (used for final bitmap rendering)
+    int bm_run_id;
+
     enum {
         SCROLL_LR,              // left-to-right
         SCROLL_RL,
@@ -200,13 +225,14 @@
     unsigned italic;
     int treat_family_as_pattern;
     int wrap_style;
+    int font_encoding;
 } RenderContext;
 
 typedef struct {
-    Hashmap *font_cache;
-    Hashmap *glyph_cache;
-    Hashmap *bitmap_cache;
-    Hashmap *composite_cache;
+    Cache *font_cache;
+    Cache *outline_cache;
+    Cache *bitmap_cache;
+    Cache *composite_cache;
     size_t glyph_max;
     size_t bitmap_max_size;
 } CacheStore;
@@ -218,6 +244,7 @@
     ASS_Settings settings;
     int render_id;
     ASS_SynthPriv *synth_priv;
+    ASS_Shaper *shaper;
 
     ASS_Image *images_root;     // rendering result is stored here
     ASS_Image *prev_images_root;
@@ -265,4 +292,7 @@
 void reset_render_context(ASS_Renderer *render_priv);
 void ass_free_images(ASS_Image *img);
 
+// XXX: this is actually in ass.c, includes should be fixed later on
+void ass_lazy_track_init(ASS_Library *lib, ASS_Track *track);
+
 #endif /* LIBASS_RENDER_H */
--- a/libass/ass_render_api.c	Sat Dec 03 21:33:28 2011 +0000
+++ b/libass/ass_render_api.c	Sat Dec 03 21:35:56 2011 +0000
@@ -25,12 +25,9 @@
     ASS_Settings *settings = &priv->settings;
 
     priv->render_id++;
-    priv->cache.glyph_cache =
-        ass_glyph_cache_reset(priv->cache.glyph_cache);
-    priv->cache.bitmap_cache =
-        ass_bitmap_cache_reset(priv->cache.bitmap_cache);
-    priv->cache.composite_cache =
-        ass_composite_cache_reset(priv->cache.composite_cache);
+    ass_cache_empty(priv->cache.outline_cache, 0);
+    ass_cache_empty(priv->cache.bitmap_cache, 0);
+    ass_cache_empty(priv->cache.composite_cache, 0);
     ass_free_images(priv->prev_images_root);
     priv->prev_images_root = 0;
 
@@ -61,6 +58,17 @@
     }
 }
 
+void ass_set_shaper(ASS_Renderer *priv, ASS_ShapingLevel level)
+{
+#ifdef CONFIG_HARFBUZZ
+    // select the complex shaper for illegal values
+    if (level == ASS_SHAPING_SIMPLE || level == ASS_SHAPING_COMPLEX)
+        priv->settings.shaper = level;
+    else
+        priv->settings.shaper = ASS_SHAPING_COMPLEX;
+#endif
+}
+
 void ass_set_margins(ASS_Renderer *priv, int t, int b, int l, int r)
 {
     if (priv->settings.left_margin != l || priv->settings.right_margin != r ||
--- a/libass/ass_types.h	Sat Dec 03 21:33:28 2011 +0000
+++ b/libass/ass_types.h	Sat Dec 03 21:35:56 2011 +0000
@@ -112,6 +112,7 @@
     int WrapStyle;
     int ScaledBorderAndShadow;
     int Kerning;
+    char *Language;
 
     int default_style;      // index of default style
     char *name;             // file name in case of external subs, 0 for streams