changeset 35262:49fc594fda43

Updated libass to 0.10.1 This closes #2099
author SubJunk
date Tue, 06 Nov 2012 05:41:14 +0000
parents 1c18199bbf7c
children a893e72567ca
files libass/ass.c libass/ass.h libass/ass_drawing.c libass/ass_parse.c libass/ass_parse.h libass/ass_render.c libass/ass_render.h libass/ass_render_api.c libass/ass_shaper.c libass/ass_utils.c libass/ass_utils.h
diffstat 11 files changed, 242 insertions(+), 138 deletions(-) [+]
line wrap: on
line diff
--- a/libass/ass.c	Mon Nov 05 21:41:39 2012 +0000
+++ b/libass/ass.c	Tue Nov 06 05:41:14 2012 +0000
@@ -191,30 +191,6 @@
     style->MarginL = style->MarginR = style->MarginV = 20;
 }
 
-/**
- * \brief find style by name
- * \param track track
- * \param name style name
- * \return index in track->styles
- * Returnes 0 if no styles found => expects at least 1 style.
- * Parsing code always adds "Default" style in the end.
- */
-static int lookup_style(ASS_Track *track, char *name)
-{
-    int i;
-    if (*name == '*')
-        ++name;                 // FIXME: what does '*' really mean ?
-    for (i = track->n_styles - 1; i >= 0; --i) {
-        if (strcmp(track->styles[i].Name, name) == 0)
-            return i;
-    }
-    i = track->default_style;
-    ass_msg(track->library, MSGL_WARN,
-            "[%p]: Warning: no style named '%s' found, using '%s'",
-            track, name, track->styles[i].Name);
-    return i;                   // use the first style
-}
-
 static uint32_t string2color(ASS_Library *library, char *p)
 {
     uint32_t tmp;
--- a/libass/ass.h	Mon Nov 05 21:41:39 2012 +0000
+++ b/libass/ass.h	Tue Nov 06 05:41:14 2012 +0000
@@ -23,7 +23,7 @@
 #include <stdarg.h>
 #include "ass_types.h"
 
-#define LIBASS_VERSION 0x01000000
+#define LIBASS_VERSION 0x01010000
 
 /*
  * A linked list of images produced by an ass renderer.
@@ -214,6 +214,14 @@
 void ass_set_line_spacing(ASS_Renderer *priv, double line_spacing);
 
 /**
+ * \brief Set vertical line position.
+ * \param priv renderer handle
+ * \param line_position vertical line position of subtitles in percent
+ * (0-100: 0 = on the bottom (default), 100 = on top)
+ */
+void ass_set_line_position(ASS_Renderer *priv, double line_position);
+
+/**
  * \brief Set font lookup defaults.
  * \param default_font path to default font to use. Must be supplied if
  * fontconfig is disabled or unavailable.
--- a/libass/ass_drawing.c	Mon Nov 05 21:41:39 2012 +0000
+++ b/libass/ass_drawing.c	Tue Nov 06 05:41:14 2012 +0000
@@ -87,6 +87,7 @@
 static void drawing_finish(ASS_Drawing *drawing, int raw_mode)
 {
     int i, offset;
+    double pbo;
     FT_BBox bbox = drawing->cbox;
     FT_Outline *ol = &drawing->outline;
 
@@ -103,11 +104,12 @@
 
     drawing->advance.x = bbox.xMax - bbox.xMin;
 
-    drawing->desc = double_to_d6(-drawing->pbo * drawing->scale_y);
+    pbo = drawing->pbo / (64.0 / (1 << (drawing->scale - 1)));
+    drawing->desc = double_to_d6(-pbo * drawing->scale_y);
     drawing->asc = bbox.yMax - bbox.yMin + drawing->desc;
 
     // Place it onto the baseline
-    offset = (bbox.yMax - bbox.yMin) + double_to_d6(-drawing->pbo *
+    offset = (bbox.yMax - bbox.yMin) + double_to_d6(-pbo *
                                                     drawing->scale_y);
     for (i = 0; i < ol->n_points; i++)
         ol->points[i].y += offset;
--- a/libass/ass_parse.c	Mon Nov 05 21:41:39 2012 +0000
+++ b/libass/ass_parse.c	Tue Nov 06 05:41:14 2012 +0000
@@ -105,28 +105,36 @@
 }
 
 /**
- * \brief Change border width
- * negative value resets border to style value
+ * \brief Calculate valid border size. Makes sure the border sizes make sense.
+ *
+ * \param priv renderer state object
+ * \param border_x requested x border size
+ * \param border_y requested y border size
  */
-void change_border(ASS_Renderer *render_priv, double border_x,
-                   double border_y)
+void calc_border(ASS_Renderer *priv, double border_x, double border_y)
 {
-    int bord;
-    if (!render_priv->state.font)
-        return;
-
     if (border_x < 0 && border_y < 0) {
-        if (render_priv->state.style->BorderStyle == 1 ||
-            render_priv->state.style->BorderStyle == 3)
-            border_x = border_y = render_priv->state.style->Outline;
+        if (priv->state.border_style == 1 ||
+            priv->state.border_style == 3)
+            border_x = border_y = priv->state.style->Outline;
         else
             border_x = border_y = 1.;
     }
 
-    render_priv->state.border_x = border_x;
-    render_priv->state.border_y = border_y;
+    priv->state.border_x = border_x;
+    priv->state.border_y = border_y;
+}
 
-    bord = 64 * border_x * render_priv->border_scale;
+/**
+ * \brief Change border width
+ *
+ * \param render_priv renderer state object
+ * \param info glyph state object
+ */
+void change_border(ASS_Renderer *render_priv, double border_x, double border_y)
+{
+    int bord = 64 * border_x * render_priv->border_scale;
+
     if (bord > 0 && border_x == border_y) {
         if (!render_priv->state.stroker) {
             int error;
@@ -138,11 +146,14 @@
                         "failed to get stroker");
                 render_priv->state.stroker = 0;
             }
+            render_priv->state.stroker_radius = -1.0;
         }
-        if (render_priv->state.stroker)
+        if (render_priv->state.stroker && render_priv->state.stroker_radius != bord) {
             FT_Stroker_Set(render_priv->state.stroker, bord,
                            FT_STROKER_LINECAP_ROUND,
                            FT_STROKER_LINEJOIN_ROUND, 0);
+            render_priv->state.stroker_radius = bord;
+        }
     } else {
         FT_Stroker_Done(render_priv->state.stroker);
         render_priv->state.stroker = 0;
@@ -242,7 +253,7 @@
  * \param p string to parse
  * \param pwr multiplier for some tag effects (comes from \t tags)
  */
-static char *parse_tag(ASS_Renderer *render_priv, char *p, double pwr)
+char *parse_tag(ASS_Renderer *render_priv, char *p, double pwr)
 {
     skip_to('\\');
     skip('\\');
@@ -256,7 +267,7 @@
             val = render_priv->state.border_x * (1 - pwr) + val * pwr;
         else
             val = -1.;
-        change_border(render_priv, val, render_priv->state.border_y);
+        calc_border(render_priv, val, render_priv->state.border_y);
         render_priv->state.bm_run_id++;
     } else if (mystrcmp(&p, "ybord")) {
         double val;
@@ -264,7 +275,8 @@
             val = render_priv->state.border_y * (1 - pwr) + val * pwr;
         else
             val = -1.;
-        change_border(render_priv, render_priv->state.border_x, val);
+        calc_border(render_priv, render_priv->state.border_x, val);
+        render_priv->state.bm_run_id++;
     } else if (mystrcmp(&p, "xshad")) {
         double val;
         if (mystrtod(&p, &val))
@@ -388,11 +400,10 @@
     } else if (mystrcmp(&p, "bord")) {
         double val;
         if (mystrtod(&p, &val)) {
-            if (render_priv->state.border_x == render_priv->state.border_y)
                 val = render_priv->state.border_x * (1 - pwr) + val * pwr;
         } else
             val = -1.;          // reset to default
-        change_border(render_priv, val, val);
+        calc_border(render_priv, val, val);
         render_priv->state.bm_run_id++;
     } else if (mystrcmp(&p, "move")) {
         double x1, x2, y1, y2;
@@ -730,7 +741,18 @@
         ass_msg(render_priv->library, MSGL_DBG2, "single c/a at %f: %c%c = %X",
                pwr, n, cmd, render_priv->state.c[cidx]);
     } else if (mystrcmp(&p, "r")) {
-        reset_render_context(render_priv);
+        char *start = p;
+        char *style;
+        skip_to('\\');
+        if (p > start) {
+            style = malloc(p - start + 1);
+            strncpy(style, start, p - start);
+            style[p - start] = '\0';
+            reset_render_context(render_priv,
+                    render_priv->track->styles + lookup_style(render_priv->track, style));
+            free(style);
+        } else
+            reset_render_context(render_priv, NULL);
     } else if (mystrcmp(&p, "be")) {
         int val;
         if (mystrtoi(&p, &val)) {
@@ -977,7 +999,7 @@
 
 
 /**
- * \brief Get next ucs4 char from string, parsing and executing style overrides
+ * \brief Get next ucs4 char from string, parsing UTF-8 and escapes
  * \param str string pointer
  * \return ucs4 code of the next char
  * On return str points to the unparsed part of the string
@@ -986,24 +1008,6 @@
 {
     char *p = *str;
     unsigned chr;
-    if (*p == '{') {            // '\0' goes here
-        p++;
-        while (1) {
-            p = parse_tag(render_priv, p, 1.);
-            if (*p == '}') {    // end of tag
-                p++;
-                if (*p == '{') {
-                    p++;
-                    continue;
-                } else
-                    break;
-            } else if (*p != '\\')
-                ass_msg(render_priv->library, MSGL_V,
-                        "Unable to parse: '%.30s'", p);
-            if (*p == 0)
-                break;
-        }
-    }
     if (*p == '\t') {
         ++p;
         *str = p;
--- a/libass/ass_parse.h	Mon Nov 05 21:41:39 2012 +0000
+++ b/libass/ass_parse.h	Tue Nov 06 05:41:14 2012 +0000
@@ -28,11 +28,13 @@
 
 void update_font(ASS_Renderer *render_priv);
 double ensure_font_size(ASS_Renderer *priv, double size);
+void calc_border(ASS_Renderer *priv, double border_x, double border_y);
 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);
+char *parse_tag(ASS_Renderer *render_priv, char *p, double pwr);
 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	Mon Nov 05 21:41:39 2012 +0000
+++ b/libass/ass_render.c	Tue Nov 06 05:41:14 2012 +0000
@@ -841,39 +841,44 @@
  * \brief partially reset render_context to style values
  * Works like {\r}: resets some style overrides
  */
-void reset_render_context(ASS_Renderer *render_priv)
+void reset_render_context(ASS_Renderer *render_priv, ASS_Style *style)
 {
-    render_priv->state.c[0] = render_priv->state.style->PrimaryColour;
-    render_priv->state.c[1] = render_priv->state.style->SecondaryColour;
-    render_priv->state.c[2] = render_priv->state.style->OutlineColour;
-    render_priv->state.c[3] = render_priv->state.style->BackColour;
+    if (!style)
+        style = render_priv->state.style;
+
+    render_priv->state.c[0] = style->PrimaryColour;
+    render_priv->state.c[1] = style->SecondaryColour;
+    render_priv->state.c[2] = style->OutlineColour;
+    render_priv->state.c[3] = style->BackColour;
     render_priv->state.flags =
-        (render_priv->state.style->Underline ? DECO_UNDERLINE : 0) |
-        (render_priv->state.style->StrikeOut ? DECO_STRIKETHROUGH : 0);
-    render_priv->state.font_size = render_priv->state.style->FontSize;
+        (style->Underline ? DECO_UNDERLINE : 0) |
+        (style->StrikeOut ? DECO_STRIKETHROUGH : 0);
+    render_priv->state.font_size = style->FontSize;
 
     free(render_priv->state.family);
     render_priv->state.family = NULL;
-    render_priv->state.family = strdup(render_priv->state.style->FontName);
+    render_priv->state.family = strdup(style->FontName);
     render_priv->state.treat_family_as_pattern =
-        render_priv->state.style->treat_fontname_as_pattern;
-    render_priv->state.bold = render_priv->state.style->Bold;
-    render_priv->state.italic = render_priv->state.style->Italic;
+        style->treat_fontname_as_pattern;
+    render_priv->state.bold = style->Bold;
+    render_priv->state.italic = style->Italic;
     update_font(render_priv);
 
-    change_border(render_priv, -1., -1.);
-    render_priv->state.scale_x = render_priv->state.style->ScaleX;
-    render_priv->state.scale_y = render_priv->state.style->ScaleY;
-    render_priv->state.hspacing = render_priv->state.style->Spacing;
+    render_priv->state.border_style = style->BorderStyle;
+    calc_border(render_priv, style->Outline, style->Outline);
+    change_border(render_priv, render_priv->state.border_x, render_priv->state.border_y);
+    render_priv->state.scale_x = style->ScaleX;
+    render_priv->state.scale_y = style->ScaleY;
+    render_priv->state.hspacing = style->Spacing;
     render_priv->state.be = 0;
     render_priv->state.blur = 0.0;
-    render_priv->state.shadow_x = render_priv->state.style->Shadow;
-    render_priv->state.shadow_y = render_priv->state.style->Shadow;
+    render_priv->state.shadow_x = style->Shadow;
+    render_priv->state.shadow_y = style->Shadow;
     render_priv->state.frx = render_priv->state.fry = 0.;
-    render_priv->state.frz = M_PI * render_priv->state.style->Angle / 180.;
+    render_priv->state.frz = M_PI * 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;
+    render_priv->state.font_encoding = style->Encoding;
 }
 
 /**
@@ -886,7 +891,7 @@
     render_priv->state.style = render_priv->track->styles + event->Style;
     render_priv->state.parsed_tags = 0;
 
-    reset_render_context(render_priv);
+    reset_render_context(render_priv, render_priv->state.style);
 
     render_priv->state.evt_type = EVENT_NORMAL;
     render_priv->state.alignment = render_priv->state.style->Alignment;
@@ -1036,7 +1041,7 @@
         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->border_style = info->border_style;
         key->hash = info->drawing->hash;
         key->text = info->drawing->text;
         key->pbo = info->drawing->pbo;
@@ -1055,7 +1060,7 @@
         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;
+        key->border_style = info->border_style;
     }
 }
 
@@ -1095,10 +1100,14 @@
             v.desc = drawing->desc;
             key.u.drawing.text = strdup(drawing->text);
         } else {
-            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);
+            // arbitrary, not too small to prevent grid fitting rounding effects
+            // XXX: this is a rather crude hack
+            const double ft_size = 256.0;
+            ass_face_set_size(info->font->faces[info->face_index], ft_size);
+            ass_font_set_transform(info->font,
+                info->scale_x * info->font_size / ft_size,
+                info->scale_y * info->font_size / ft_size,
+                NULL);
             FT_Glyph glyph =
                 ass_font_get_glyph(priv->fontconfig_priv, info->font,
                         info->symbol, info->face_index, info->glyph_index,
@@ -1113,8 +1122,8 @@
                 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;
+                v.asc  *= info->scale_y * info->font_size / ft_size;
+                v.desc *= info->scale_y * info->font_size / ft_size;
             }
         }
 
@@ -1123,7 +1132,7 @@
 
         FT_Outline_Get_CBox(v.outline, &v.bbox_scaled);
 
-        if (priv->state.style->BorderStyle == 3 &&
+        if (info->border_style == 3 &&
                 (info->border_x > 0 || info->border_y > 0)) {
             FT_Vector advance;
 
@@ -1141,6 +1150,7 @@
         } else if ((info->border_x > 0 || info->border_y > 0)
                 && double_to_d6(info->scale_x) && double_to_d6(info->scale_y)) {
 
+            change_border(priv, info->border_x, info->border_y);
             outline_copy(priv->ftlibrary, v.outline, &v.border);
             stroke_outline(priv, v.border,
                     double_to_d6(info->border_x * priv->border_scale),
@@ -1275,8 +1285,8 @@
         // 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;
+        fax_scaled = info->fax / info->scale_y * info->scale_x;
+        fay_scaled = info->fay / info->scale_x * info->scale_y;
 
         // apply rotation
         transform_3d(shift, outline, border,
@@ -1308,7 +1318,7 @@
                 &hash_val.bm_s, info->be,
                 info->blur * render_priv->border_scale,
                 key->shadow_offset,
-                render_priv->state.style->BorderStyle);
+                info->border_style);
         if (error)
             info->symbol = 0;
 
@@ -1707,15 +1717,43 @@
     num_glyphs = 0;
     p = event->Text;
 
+    int in_tag = 0;
+
     // Event parsing.
     while (1) {
         // get next char, executing style override
         // this affects render_context
         do {
-            code = get_next_char(render_priv, &p);
-            if (render_priv->state.drawing_mode && code)
-                ass_drawing_add_char(drawing, (char) code);
-        } while (code && render_priv->state.drawing_mode);      // skip everything in drawing mode
+            code = 0;
+            if (!in_tag && *p == '{') {            // '\0' goes here
+                p++;
+                in_tag = 1;
+            }
+            if (in_tag) {
+                int prev_drawing_mode = render_priv->state.drawing_mode;
+                p = parse_tag(render_priv, p, 1.);
+                if (*p == '}') {    // end of tag
+                    p++;
+                    in_tag = 0;
+                } else if (*p != '\\') {
+                    ass_msg(render_priv->library, MSGL_V,
+                            "Unable to parse: '%.30s'", p);
+                }
+                if (prev_drawing_mode && !render_priv->state.drawing_mode) {
+                    // Drawing mode was just disabled. We must exit and draw it
+                    // immediately, instead of letting further tags affect it.
+                    // See bug #47.
+                    break;
+                }
+            } else {
+                code = get_next_char(render_priv, &p);
+                if (code && render_priv->state.drawing_mode) {
+                    ass_drawing_add_char(drawing, (char) code);
+                    continue;   // skip everything in drawing mode
+                }
+                break;
+            }
+        } while (*p);
 
         if (text_info->length >= text_info->max_glyphs) {
             // Raise maximum number of glyphs
@@ -1734,7 +1772,6 @@
                                      render_priv->font_scale;
             drawing->scale_y = render_priv->state.scale_y *
                                      render_priv->font_scale;
-            p--;
             code = 0xfffc; // object replacement character
             glyphs[text_info->length].drawing = drawing;
         }
@@ -1762,16 +1799,18 @@
             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].font_size =
+                    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_style = render_priv->state.border_style;
         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].hspacing = render_priv->state.hspacing;
         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;
@@ -1829,11 +1868,11 @@
         }
 
         // add horizontal letter spacing
-        info->cluster_advance.x += double_to_d6(render_priv->state.hspacing *
+        info->cluster_advance.x += double_to_d6(info->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;
+        info->cluster_advance.y += (info->fay / info->scale_x * info->scale_y) * info->cluster_advance.x;
 
     }
 
@@ -1906,6 +1945,7 @@
     for (i = 0; i < text_info->length; i++) {
         GlyphInfo *info = glyphs + cmap[i];
         if (glyphs[i].linebreak) {
+            pen.y -= (info->fay / info->scale_x * info->scale_y) * pen.x;
             pen.x = 0;
             pen.y += double_to_d6(text_info->lines[lineno-1].desc);
             pen.y += double_to_d6(text_info->lines[lineno].asc);
@@ -1995,16 +2035,25 @@
                 y2scr(render_priv, render_priv->track->PlayResY / 2.0);
             device_y = scr_y - (bbox.yMax + bbox.yMin) / 2.0;
         } else {                // subtitle
-            double scr_y;
+            double scr_top, scr_bottom, scr_y0;
             if (valign != VALIGN_SUB)
                 ass_msg(render_priv->library, MSGL_V,
                        "Invalid valign, assuming 0 (subtitle)");
-            scr_y =
+            scr_bottom =
                 y2scr_sub(render_priv,
                           render_priv->track->PlayResY - MarginV);
-            device_y = scr_y;
+            scr_top = y2scr_top(render_priv, 0); //xxx not always 0?
+            device_y = scr_bottom + (scr_top - scr_bottom) *
+                       render_priv->settings.line_position / 100.0;
             device_y -= text_info->height;
             device_y += text_info->lines[0].asc;
+            // clip to top to avoid confusion if line_position is very high,
+            // turning the subtitle into a toptitle
+            // also, don't change behavior if line_position is not used
+            scr_y0 = scr_top + text_info->lines[0].asc;
+            if (device_y < scr_y0 && render_priv->settings.line_position > 0) {
+                device_y = scr_y0;
+            }
         }
     } else if (render_priv->state.evt_type == EVENT_VSCROLL) {
         if (render_priv->state.scroll_direction == SCROLL_TB)
@@ -2158,12 +2207,14 @@
         ass_cache_empty(cache->composite_cache, 0);
         ass_free_images(priv->prev_images_root);
         priv->prev_images_root = 0;
+        priv->cache_cleared = 1;
     }
     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;
+        priv->cache_cleared = 1;
     }
 }
 
@@ -2429,6 +2480,9 @@
     ASS_Image *img, *img2;
     int diff;
 
+    if (priv->cache_cleared)
+        return 2;
+
     img = priv->prev_images_root;
     img2 = priv->images_root;
     diff = 0;
@@ -2474,8 +2528,12 @@
 
     // init frame
     rc = ass_start_frame(priv, track, now);
-    if (rc != 0)
+    if (rc != 0) {
+        if (detect_change) {
+            *detect_change = 2;
+        }
         return 0;
+    }
 
     // render events separately
     cnt = 0;
@@ -2525,6 +2583,7 @@
     // free the previous image list
     ass_free_images(priv->prev_images_root);
     priv->prev_images_root = 0;
+    priv->cache_cleared = 0;
 
     return priv->images_root;
 }
--- a/libass/ass_render.h	Mon Nov 05 21:41:39 2012 +0000
+++ b/libass/ass_render.h	Tue Nov 06 05:41:14 2012 +0000
@@ -67,6 +67,7 @@
     int frame_height;
     double font_size_coeff;     // font size multiplier
     double line_spacing;        // additional line spacing (in frame pixels)
+    double line_position;       // vertical position for subtitles, 0-100 (0 = no change)
     int top_margin;             // height of top margin. Everything except toptitles is shifted down by top_margin.
     int bottom_margin;          // height of bottom margin. (frame_height - top_margin - bottom_margin) is original video height.
     int left_margin;
@@ -133,7 +134,9 @@
     double frx, fry, frz;       // rotation
     double fax, fay;            // text shearing
     double scale_x, scale_y;
+    int border_style;
     double border_x, border_y;
+    double hspacing;
     unsigned italic;
     unsigned bold;
     int flags;
@@ -174,6 +177,7 @@
     int flags;                  // decoration flags (underline/strike-through)
 
     FT_Stroker stroker;
+    int stroker_radius;         // last stroker radius, for caching stroker objects
     int alignment;              // alignment overrides go here; if zero, style value will be used
     double frx, fry, frz;
     double fax, fay;            // text shearing
@@ -188,6 +192,7 @@
     char have_origin;           // origin is explicitly defined; if 0, get_base_point() is used
     double scale_x, scale_y;
     double hspacing;            // distance between letters, in pixels
+    int border_style;
     double border_x;            // outline width
     double border_y;
     uint32_t c[4];              // colors(Primary, Secondary, so on) in RGBA
@@ -248,6 +253,7 @@
 
     ASS_Image *images_root;     // rendering result is stored here
     ASS_Image *prev_images_root;
+    int cache_cleared;
 
     EventImages *eimg;          // temporary buffer for sorting rendered events
     int eimg_size;              // allocated buffer size
@@ -289,7 +295,7 @@
     int ha, hb;                 // left and width
 } Segment;
 
-void reset_render_context(ASS_Renderer *render_priv);
+void reset_render_context(ASS_Renderer *render_priv, ASS_Style *style);
 void ass_free_images(ASS_Image *img);
 
 // XXX: this is actually in ass.c, includes should be fixed later on
--- a/libass/ass_render_api.c	Mon Nov 05 21:41:39 2012 +0000
+++ b/libass/ass_render_api.c	Tue Nov 06 05:41:14 2012 +0000
@@ -116,6 +116,14 @@
     priv->settings.line_spacing = line_spacing;
 }
 
+void ass_set_line_position(ASS_Renderer *priv, double line_position)
+{
+    if (priv->settings.line_position != line_position) {
+        priv->settings.line_position = line_position;
+        ass_reconfigure(priv);
+    }
+}
+
 void ass_set_fonts(ASS_Renderer *priv, const char *default_font,
                    const char *default_family, int fc, const char *config,
                    int update)
--- a/libass/ass_shaper.c	Mon Nov 05 21:41:39 2012 +0000
+++ b/libass/ass_shaper.c	Tue Nov 06 05:41:14 2012 +0000
@@ -401,7 +401,9 @@
                 font->faces[info->face_index], NULL);
     }
 
-    ass_face_set_size(font->faces[info->face_index], info->font_size);
+    // XXX: this is a rather crude hack
+    const double ft_size = 256.0;
+    ass_face_set_size(font->faces[info->face_index], ft_size);
     update_hb_size(hb_fonts[info->face_index], font->faces[info->face_index]);
 
     // update hash key for cached metrics
@@ -431,7 +433,7 @@
         hb_buffer_t *buf;
         hb_font_t *font;
     } runs[MAX_RUNS];
-
+    const double ft_size = 256.0;
 
     for (i = 0; i < len && run < MAX_RUNS; i++, run++) {
         // get length and level of the current run
@@ -484,10 +486,10 @@
             // set position and advance
             info->skip = 0;
             info->glyph_index = glyph_info[j].codepoint;
-            info->offset.x    = pos[j].x_offset * info->scale_x;
-            info->offset.y    = -pos[j].y_offset * info->scale_y;
-            info->advance.x   = pos[j].x_advance * info->scale_x;
-            info->advance.y   = -pos[j].y_advance * info->scale_y;
+            info->offset.x    = pos[j].x_offset * info->scale_x * (info->font_size / ft_size);
+            info->offset.y    = -pos[j].y_offset * info->scale_y * (info->font_size / ft_size);
+            info->advance.x   = pos[j].x_advance * info->scale_x * (info->font_size / ft_size);
+            info->advance.y   = -pos[j].y_advance * info->scale_y * (info->font_size / ft_size);
 
             // accumulate advance in the root glyph
             root->cluster_advance.x += info->advance.x;
@@ -602,6 +604,29 @@
 }
 
 /**
+  * \brief Remove all zero-width invisible characters from the text.
+  * \param text_info text
+  */
+static void ass_shaper_skip_characters(TextInfo *text_info)
+{
+    int i;
+    GlyphInfo *glyphs = text_info->glyphs;
+
+    for (i = 0; i < text_info->length; i++) {
+        // Skip direction override control characters
+        if ((glyphs[i].symbol <= 0x202e && glyphs[i].symbol >= 0x202a)
+                || (glyphs[i].symbol <= 0x200f && glyphs[i].symbol >= 0x200b)
+                || (glyphs[i].symbol <= 0x2063 && glyphs[i].symbol >= 0x2060)
+                || glyphs[i].symbol == 0xfeff
+                || glyphs[i].symbol == 0x00ad
+                || glyphs[i].symbol == 0x034f) {
+            glyphs[i].symbol = 0;
+            glyphs[i].skip++;
+        }
+    }
+}
+
+/**
  * \brief Shape an event's text. Calculates directional runs and shapes them.
  * \param text_info event's text
  */
@@ -639,27 +664,15 @@
 #ifdef CONFIG_HARFBUZZ
     switch (shaper->shaping_level) {
     case ASS_SHAPING_SIMPLE:
-        shape_fribidi(shaper, glyphs, text_info->length);
+        ass_shaper_skip_characters(text_info);
         break;
     case ASS_SHAPING_COMPLEX:
         shape_harfbuzz(shaper, glyphs, text_info->length);
         break;
     }
 #else
-        shape_fribidi(shaper, glyphs, text_info->length);
+        ass_shaper_skip_characters(text_info);
 #endif
-
-
-    // clean up
-    for (i = 0; i < text_info->length; i++) {
-        // Skip direction override control characters
-        // NOTE: Behdad said HarfBuzz is supposed to remove these, but this hasn't
-        // been implemented yet
-        if (glyphs[i].symbol <= 0x202F && glyphs[i].symbol >= 0x202a) {
-            glyphs[i].symbol = 0;
-            glyphs[i].skip++;
-        }
-    }
 #endif
 }
 
@@ -733,17 +746,18 @@
 }
 
 /**
- * \brief Resolve a Windows font encoding number to a suitable
+ * \brief Resolve a Windows font charset number to a suitable
  * base direction. 177 and 178 are Hebrew and Arabic respectively, and
- * they map to RTL. 1 is autodetection and is mapped to just that.
- * Everything else is mapped to LTR.
+ * they map to RTL. Everything else maps to LTR for compatibility
+ * reasons. The special value -1, which is not a legal Windows font charset
+ * number, can be used for autodetection.
  * \param enc Windows font encoding
  */
 FriBidiParType resolve_base_direction(int enc)
 {
 #ifdef CONFIG_FRIBIDI
     switch (enc) {
-        case 1:
+        case -1:
             return FRIBIDI_PAR_ON;
         case 177:
         case 178:
--- a/libass/ass_utils.c	Mon Nov 05 21:41:39 2012 +0000
+++ b/libass/ass_utils.c	Tue Nov 06 05:41:14 2012 +0000
@@ -160,6 +160,30 @@
     return c;
 }
 
+/**
+ * \brief find style by name
+ * \param track track
+ * \param name style name
+ * \return index in track->styles
+ * Returnes 0 if no styles found => expects at least 1 style.
+ * Parsing code always adds "Default" style in the end.
+ */
+int lookup_style(ASS_Track *track, char *name)
+{
+    int i;
+    if (*name == '*')
+        ++name;                 // FIXME: what does '*' really mean ?
+    for (i = track->n_styles - 1; i >= 0; --i) {
+        if (strcmp(track->styles[i].Name, name) == 0)
+            return i;
+    }
+    i = track->default_style;
+    ass_msg(track->library, MSGL_WARN,
+            "[%p]: Warning: no style named '%s' found, using '%s'",
+            track, name, track->styles[i].Name);
+    return i;                   // use the first style
+}
+
 #ifdef CONFIG_ENCA
 void *ass_guess_buffer_cp(ASS_Library *library, unsigned char *buffer,
                           int buflen, char *preferred_language,
--- a/libass/ass_utils.h	Mon Nov 05 21:41:39 2012 +0000
+++ b/libass/ass_utils.h	Tue Nov 06 05:41:14 2012 +0000
@@ -51,6 +51,7 @@
 char parse_bool(char *str);
 unsigned ass_utf8_get_char(char **str);
 void ass_msg(ASS_Library *priv, int lvl, char *fmt, ...);
+int lookup_style(ASS_Track *track, char *name);
 #ifdef CONFIG_ENCA
 void *ass_guess_buffer_cp(ASS_Library *library, unsigned char *buffer,
                           int buflen, char *preferred_language,