changeset 36363:c3aaaf17c721

Update libass to latest git version.
author reimar
date Tue, 24 Sep 2013 20:50:02 +0000
parents 99708d402208
children 4f8cf378dba4
files libass/ass.c libass/ass.h libass/ass_bitmap.c libass/ass_bitmap.h libass/ass_font.c libass/ass_fontconfig.c libass/ass_parse.c libass/ass_render.c libass/ass_render.h libass/ass_render_api.c libass/ass_shaper.c libass/ass_types.h libass/ass_utils.c libass/ass_utils.h
diffstat 14 files changed, 434 insertions(+), 70 deletions(-) [+]
line wrap: on
line diff
--- a/libass/ass.c	Sun Sep 22 09:03:30 2013 +0000
+++ b/libass/ass.c	Tue Sep 24 20:50:02 2013 +0000
@@ -387,6 +387,8 @@
             track->ScaledBorderAndShadow = parse_bool(token);
         else if (!strcasecmp(*fs, "Kerning"))
             track->Kerning = parse_bool(token);
+        else if (!strcasecmp(*fs, "YCbCr Matrix"))
+            track->YCbCrMatrix = parse_ycbcr_matrix(token);
 
         dt = strrchr(*fs, '.');
         if (dt) {
@@ -424,6 +426,7 @@
                     FPVAL(ScaleY)
                     FPVAL(Outline)
                     FPVAL(Shadow)
+                    FPVAL(Blur)
                 }
             }
         }
@@ -514,7 +517,7 @@
             INTVAL(Underline)
             INTVAL(StrikeOut)
             FPVAL(Spacing)
-            INTVAL(Angle)
+            FPVAL(Angle)
             INTVAL(BorderStyle)
             INTVAL(Alignment)
             if (track->track_type == TRACK_TYPE_ASS)
@@ -573,6 +576,8 @@
         track->ScaledBorderAndShadow = parse_bool(str + 22);
     } else if (!strncmp(str, "Kerning:", 8)) {
         track->Kerning = parse_bool(str + 8);
+    } else if (!strncmp(str, "YCbCr Matrix:", 13)) {
+        track->YCbCrMatrix = parse_ycbcr_matrix(str + 13);
     } else if (!strncmp(str, "Language:", 9)) {
         char *p = str + 9;
         while (*p && isspace(*p)) p++;
@@ -587,10 +592,10 @@
 {
     track->parser_priv->state = PST_EVENTS;
     if (track->track_type == TRACK_TYPE_SSA)
-        track->event_format = strdup("Format: Marked, Start, End, Style, "
+        track->event_format = strdup("Marked, Start, End, Style, "
             "Name, MarginL, MarginR, MarginV, Effect, Text");
     else
-        track->event_format = strdup("Format: Layer, Start, End, Style, "
+        track->event_format = strdup("Layer, Start, End, Style, "
             "Actor, MarginL, MarginR, MarginV, Effect, Text");
     ass_msg(track->library, MSGL_V,
             "No event format found, using fallback");
--- a/libass/ass.h	Sun Sep 22 09:03:30 2013 +0000
+++ b/libass/ass.h	Tue Sep 24 20:50:02 2013 +0000
@@ -23,7 +23,7 @@
 #include <stdarg.h>
 #include "ass_types.h"
 
-#define LIBASS_VERSION 0x01010000
+#define LIBASS_VERSION 0x01020000
 
 /*
  * A linked list of images produced by an ass renderer.
@@ -44,6 +44,13 @@
     int dst_x, dst_y;           // Bitmap placement inside the video frame
 
     struct ass_image *next;   // Next image, or NULL
+
+    enum {
+        IMAGE_TYPE_CHARACTER,
+        IMAGE_TYPE_OUTLINE,
+        IMAGE_TYPE_SHADOW
+    } type;
+
 } ASS_Image;
 
 /*
@@ -153,6 +160,12 @@
 
 /**
  * \brief Set the frame size in pixels, including margins.
+ * The renderer will never return images that are outside of the frame area.
+ * The value set with this function can influence the pixel aspect ratio used
+ * for rendering. If the frame size doesn't equal to the video size, you may
+ * have to use ass_set_pixel_aspect().
+ * @see ass_set_pixel_aspect()
+ * @see ass_set_margins()
  * \param priv renderer handle
  * \param w width
  * \param h height
@@ -160,6 +173,19 @@
 void ass_set_frame_size(ASS_Renderer *priv, int w, int h);
 
 /**
+ * \brief Set the source image size in pixels.
+ * This is used to calculate the source aspect ratio and the blur scale.
+ * The source image size can be reset to default by setting w and h to 0.
+ * The value set with this function can influence the pixel aspect ratio used
+ * for rendering.
+ * @see ass_set_pixel_aspect()
+ * \param priv renderer handle
+ * \param w width
+ * \param h height
+ */
+void ass_set_storage_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
@@ -168,7 +194,26 @@
 
 /**
  * \brief Set frame margins.  These values may be negative if pan-and-scan
- * is used.
+ * is used. The margins are in pixels. Each value specifies the distance from
+ * the video rectangle to the renderer frame. If a given margin value is
+ * positive, there will be free space between renderer frame and video area.
+ * If a given margin value is negative, the frame is inside the video, i.e.
+ * the video has been cropped.
+ *
+ * The renderer will try to keep subtitles inside the frame area. If possible,
+ * text is layout so that it is inside the cropped area. Subtitle events
+ * that can't be moved are cropped against the frame area.
+ *
+ * ass_set_use_margins() can be used to allow libass to render subtitles into
+ * the empty areas if margins are positive, i.e. the video area is smaller than
+ * the frame. (Traditionally, this has been used to show subtitles in
+ * the bottom "black bar" between video bottom screen border when playing 16:9
+ * video on a 4:3 screen.)
+ *
+ * When using this function, it is recommended to calculate and set your own
+ * aspect ratio with ass_set_pixel_aspect(), as the defaults won't make any
+ * sense.
+ * @see ass_set_pixel_aspect()
  * \param priv renderer handle
  * \param t top margin
  * \param b bottom margin
@@ -185,7 +230,29 @@
 void ass_set_use_margins(ASS_Renderer *priv, int use);
 
 /**
+ * \brief Set pixel aspect ratio correction.
+ * This is the ratio of pixel width to pixel height.
+ *
+ * Generally, this is (s_w / s_h) / (d_w / d_h), where s_w and s_h is the
+ * video storage size, and d_w and d_h is the video display size. (Display
+ * and storage size can be different for anamorphic video, such as DVDs.)
+ *
+ * If the pixel aspect ratio is 0, or if the aspect ratio has never been set
+ * by calling this function, libass will calculate a default pixel aspect ratio
+ * out of values set with ass_set_frame_size() and ass_set_storage_size(). Note
+ * that this is useful only if the frame size corresponds to the video display
+ * size. Keep in mind that the margins set with ass_set_margins() are ignored
+ * for aspect ratio calculations as well.
+ * If the storage size has not been set, a pixel aspect ratio of 1 is assumed.
+ * \param priv renderer handle
+ * \param par pixel aspect ratio (1.0 means square pixels, 0 means default)
+ */
+void ass_set_pixel_aspect(ASS_Renderer *priv, double par);
+
+/**
  * \brief Set aspect ratio parameters.
+ * This calls ass_set_pixel_aspect(priv, dar / sar).
+ * @deprecated New code should use ass_set_pixel_aspect().
  * \param priv renderer handle
  * \param dar display aspect ratio (DAR), prescaled for output PAR
  * \param sar storage aspect ratio (SAR)
--- a/libass/ass_bitmap.c	Sun Sep 22 09:03:30 2013 +0000
+++ b/libass/ass_bitmap.c	Tue Sep 24 20:50:02 2013 +0000
@@ -456,7 +456,7 @@
                        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)
+                       int border_style, int border_visible)
 {
     blur_radius *= 2;
     int bbord = be > 0 ? sqrt(2 * be) : 0;
@@ -485,7 +485,7 @@
     while (be--) {
         if (*bm_o)
             be_blur(*bm_o);
-        else
+        if (!*bm_o || border_style == 3)
             be_blur(*bm_g);
     }
 
@@ -493,7 +493,7 @@
     if (blur_radius > 0.0) {
         if (*bm_o)
             resize_tmp(priv_blur, (*bm_o)->w, (*bm_o)->h);
-        else
+        if (!*bm_o || border_style == 3)
             resize_tmp(priv_blur, (*bm_g)->w, (*bm_g)->h);
         generate_tables(priv_blur, blur_radius);
         if (*bm_o)
@@ -501,7 +501,7 @@
                            (*bm_o)->w, (*bm_o)->h, (*bm_o)->stride,
                            (int *) priv_blur->gt2, priv_blur->g_r,
                            priv_blur->g_w);
-        else
+        if (!*bm_o || border_style == 3)
             ass_gauss_blur((*bm_g)->buffer, priv_blur->tmp,
                            (*bm_g)->w, (*bm_g)->h, (*bm_g)->stride,
                            (int *) priv_blur->gt2, priv_blur->g_r,
@@ -512,8 +512,11 @@
     if (*bm_o && border_style != 3) {
         *bm_s = copy_bitmap(*bm_o);
         fix_outline(*bm_g, *bm_o);
+    } else if (*bm_o && border_visible) {
+        *bm_s = copy_bitmap(*bm_o);
     } else if (*bm_o) {
-        *bm_s = copy_bitmap(*bm_o);
+        *bm_s = *bm_o;
+        *bm_o = 0;
     } else
         *bm_s = copy_bitmap(*bm_g);
 
--- a/libass/ass_bitmap.h	Sun Sep 22 09:03:30 2013 +0000
+++ b/libass/ass_bitmap.h	Tue Sep 24 20:50:02 2013 +0000
@@ -46,12 +46,13 @@
  * \param bm_o out: pointer to the bitmap of outline (border) glyph is returned here
  * \param bm_g out: pointer to the bitmap of glyph shadow is returned here
  * \param be 1 = produces blurred bitmaps, 0 = normal bitmaps
+ * \param border_visible whether border is visible if border_style is 3
  */
 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);
+                       int border_style, int border_visible);
 
 void ass_free_bitmap(Bitmap *bm);
 
--- a/libass/ass_font.c	Sun Sep 22 09:03:30 2013 +0000
+++ b/libass/ass_font.c	Tue Sep 24 20:50:02 2013 +0000
@@ -711,12 +711,12 @@
             /* "inside" contour but we can't find anything it could be
              * 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 = outline->points[start + j];
-                char temp2 = outline->tags[start + j];
-                outline->points[start + j] = outline->points[end - j];
+            for (j = 0; j < (end - start) / 2; j++) {
+                FT_Vector temp = outline->points[start + 1 + j];
+                char temp2 = outline->tags[start + 1 + j];
+                outline->points[start + 1 + j] = outline->points[end - j];
                 outline->points[end - j] = temp;
-                outline->tags[start + j] = outline->tags[end - j];
+                outline->tags[start + 1 + j] = outline->tags[end - j];
                 outline->tags[end - j] = temp2;
             }
             dir ^= 1;
--- a/libass/ass_fontconfig.c	Sun Sep 22 09:03:30 2013 +0000
+++ b/libass/ass_fontconfig.c	Tue Sep 24 20:50:02 2013 +0000
@@ -179,6 +179,14 @@
     rc = FcConfigSubstitute(priv->config, pat, FcMatchPattern);
     if (!rc)
         goto error;
+    /* Fontconfig defaults include a language setting, which it sets based on
+     * some environment variables or defaults to "en". Unset this as we don't
+     * know the real language, and because some some attached fonts lack
+     * non-ascii characters included in fontconfig's list of characters
+     * required for English support and therefore don't match the lang=en
+     * criterion.
+     */
+    FcPatternDel(pat, "lang");
 
     fsorted = FcFontSort(priv->config, pat, FcTrue, NULL, &result);
     ffullname = match_fullname(library, priv, family, bold, italic);
--- a/libass/ass_parse.c	Sun Sep 22 09:03:30 2013 +0000
+++ b/libass/ass_parse.c	Tue Sep 24 20:50:02 2013 +0000
@@ -311,7 +311,7 @@
         int x0, y0, x1, y1;
         int res = 1;
         char *start = p;
-        skipopt('(');
+        skip('(');
         res &= mystrtoi(&p, &x0);
         skipopt(',');
         res &= mystrtoi(&p, &y0);
@@ -418,6 +418,7 @@
         mystrtod(&p, &x2);
         skip(',');
         mystrtod(&p, &y2);
+        t1 = t2 = 0;
         if (*p == ',') {
             skip(',');
             mystrtoll(&p, &t1);
@@ -427,13 +428,20 @@
                    "movement6: (%f, %f) -> (%f, %f), (%" PRId64 " .. %"
                    PRId64 ")\n", x1, y1, x2, y2, (int64_t) t1,
                    (int64_t) t2);
-        } else {
+            // VSFilter
+            if (t1 > t2) {
+                double tmp = t2;
+                t2 = t1;
+                t1 = tmp;
+            }
+        }
+        if (t1 <= 0 && t2 <= 0) {
             t1 = 0;
             t2 = render_priv->state.event->Duration;
             ass_msg(render_priv->library, MSGL_DBG2,
                    "movement: (%f, %f) -> (%f, %f)", x1, y1, x2, y2);
         }
-        skip(')');
+        skipopt(')');
         delta_t = t2 - t1;
         t = render_priv->time - render_priv->state.event->Start;
         if (t < t1)
@@ -542,7 +550,7 @@
         mystrtod(&p, &v1);
         skip(',');
         mystrtod(&p, &v2);
-        skip(')');
+        skipopt(')');
         ass_msg(render_priv->library, MSGL_DBG2, "pos(%f, %f)", v1, v2);
         if (render_priv->state.evt_type == EVENT_POSITIONED) {
             ass_msg(render_priv->library, MSGL_V, "Subtitle has a new \\pos "
@@ -586,7 +594,7 @@
             skip(',');
             mystrtoll(&p, &t4);
         }
-        skip(')');
+        skipopt(')');
         if ((render_priv->state.parsed_tags & PARSED_FADE) == 0) {
             render_priv->state.fade =
                 interpolate_alpha(render_priv->time -
@@ -600,7 +608,7 @@
         mystrtoi(&p, &v1);
         skip(',');
         mystrtoi(&p, &v2);
-        skip(')');
+        skipopt(')');
         ass_msg(render_priv->library, MSGL_DBG2, "org(%d, %d)", v1, v2);
         if (!render_priv->state.have_origin) {
             render_priv->state.org_x = v1;
@@ -617,9 +625,8 @@
         double k;
         skip('(');
         for (cnt = 0; cnt < 3; ++cnt) {
-            if (*p == '\\')
+            if (!mystrtod(&p, &v[cnt]))
                 break;
-            mystrtod(&p, &v[cnt]);
             skip(',');
         }
         if (cnt == 3) {
@@ -654,15 +661,15 @@
             assert(delta_t != 0.);
             k = pow(((double) (t - t1)) / delta_t, v3);
         }
-        while (*p == '\\')
+        while (*p != ')' && *p != '}' && *p != '\0')
             p = parse_tag(render_priv, p, k);   // maybe k*pwr ? no, specs forbid nested \t's
         skip_to(')');           // in case there is some unknown tag or a comment
-        skip(')');
+        skipopt(')');
     } else if (mystrcmp(&p, "clip")) {
         char *start = p;
         int x0, y0, x1, y1;
         int res = 1;
-        skipopt('(');
+        skip('(');
         res &= mystrtoi(&p, &x0);
         skipopt(',');
         res &= mystrtoi(&p, &y0);
--- a/libass/ass_render.c	Sun Sep 22 09:03:30 2013 +0000
+++ b/libass/ass_render.c	Tue Sep 24 20:50:02 2013 +0000
@@ -240,7 +240,7 @@
 static ASS_Image **render_glyph_i(ASS_Renderer *render_priv,
                                   Bitmap *bm, int dst_x, int dst_y,
                                   uint32_t color, uint32_t color2, int brk,
-                                  ASS_Image **tail)
+                                  ASS_Image **tail, unsigned int type)
 {
     int i, j, x0, y0, x1, y1, cx0, cy0, cx1, cy1, sx, sy, zx, zy;
     Rect r[4];
@@ -308,6 +308,7 @@
                 lbrk - r[j].x0, r[j].y1 - r[j].y0,
                 bm->stride, dst_x + r[j].x0, dst_y + r[j].y0, color);
             if (!img) break;
+            img->type = type;
             *tail = img;
             tail = &img->next;
         }
@@ -317,6 +318,7 @@
                 r[j].x1 - lbrk, r[j].y1 - r[j].y0,
                 bm->stride, dst_x + lbrk, dst_y + r[j].y0, color2);
             if (!img) break;
+            img->type = type;
             *tail = img;
             tail = &img->next;
         }
@@ -339,12 +341,12 @@
  */
 static ASS_Image **
 render_glyph(ASS_Renderer *render_priv, Bitmap *bm, int dst_x, int dst_y,
-             uint32_t color, uint32_t color2, int brk, ASS_Image **tail)
+             uint32_t color, uint32_t color2, int brk, ASS_Image **tail, unsigned int type)
 {
     // Inverse clipping in use?
     if (render_priv->state.clip_mode)
         return render_glyph_i(render_priv, bm, dst_x, dst_y, color, color2,
-                              brk, tail);
+                              brk, tail, type);
 
     // brk is relative to dst_x
     // color = color left of brk
@@ -399,6 +401,7 @@
                              brk - b_x0, b_y1 - b_y0, bm->stride,
                              dst_x + b_x0, dst_y + b_y0, color);
         if (!img) return tail;
+        img->type = type;
         *tail = img;
         tail = &img->next;
     }
@@ -409,6 +412,7 @@
                              b_x1 - brk, b_y1 - b_y0, bm->stride,
                              dst_x + brk, dst_y + b_y0, color2);
         if (!img) return tail;
+        img->type = type;
         *tail = img;
         tail = &img->next;
     }
@@ -718,7 +722,7 @@
             here_tail = tail;
             tail =
                 render_glyph(render_priv, bm, pen_x, pen_y, info->c[3], 0,
-                        1000000, tail);
+                        1000000, tail, IMAGE_TYPE_SHADOW);
 
             if (last_tail && tail != here_tail && ((info->c[3] & 0xff) > 0))
                 render_overlap(render_priv, last_tail, here_tail);
@@ -752,7 +756,7 @@
                 here_tail = tail;
                 tail =
                     render_glyph(render_priv, bm, pen_x, pen_y, info->c[2],
-                            0, 1000000, tail);
+                            0, 1000000, tail, IMAGE_TYPE_OUTLINE);
                 if (last_tail && tail != here_tail && ((info->c[2] & 0xff) > 0))
                     render_overlap(render_priv, last_tail, here_tail);
 
@@ -783,19 +787,19 @@
                 if (info->effect_timing > (info->bbox.xMax >> 6))
                     tail =
                         render_glyph(render_priv, bm, pen_x, pen_y,
-                                info->c[0], 0, 1000000, tail);
+                                info->c[0], 0, 1000000, tail, IMAGE_TYPE_CHARACTER);
                 else
                     tail =
                         render_glyph(render_priv, bm, pen_x, pen_y,
-                                info->c[1], 0, 1000000, tail);
+                                info->c[1], 0, 1000000, tail, IMAGE_TYPE_CHARACTER);
             } 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);
+                            info->c[1], info->effect_timing, tail, IMAGE_TYPE_CHARACTER);
             } else
                 tail =
                     render_glyph(render_priv, bm, pen_x, pen_y, info->c[0],
-                            0, 1000000, tail);
+                            0, 1000000, tail, IMAGE_TYPE_CHARACTER);
             info = info->next;
         }
     }
@@ -866,7 +870,7 @@
     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.blur = style->Blur;
     render_priv->state.shadow_x = style->Shadow;
     render_priv->state.shadow_y = style->Shadow;
     render_priv->state.frx = render_priv->state.fry = 0.;
@@ -927,13 +931,14 @@
  * 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, int asc, int desc,
-                            FT_Outline *ol, FT_Vector advance, int sx, int sy)
+static void draw_opaque_box(ASS_Renderer *render_priv, GlyphInfo *info,
+                            int asc, int desc, FT_Outline *ol,
+                            FT_Vector advance, int sx, int sy)
 {
     int i;
     int adv = advance.x;
-    double scale_y = render_priv->state.scale_y;
-    double scale_x = render_priv->state.scale_x;
+    double scale_y = info->scale_y;
+    double scale_x = info->scale_x;
 
     // to avoid gaps
     sx = FFMAX(64, sx);
@@ -941,8 +946,7 @@
 
     // Emulate the WTFish behavior of VSFilter, i.e. double-scale
     // the sizes of the opaque box.
-    adv += double_to_d6(render_priv->state.hspacing * render_priv->font_scale
-                        * scale_x);
+    adv += double_to_d6(info->hspacing * render_priv->font_scale * scale_x);
     adv *= scale_x;
     sx *= scale_x;
     sy *= scale_y;
@@ -1127,8 +1131,7 @@
 
         FT_Outline_Get_CBox(v.outline, &v.bbox_scaled);
 
-        if (info->border_style == 3 &&
-                (info->border_x > 0 || info->border_y > 0)) {
+        if (info->border_style == 3) {
             FT_Vector advance;
 
             v.border = calloc(1, sizeof(FT_Outline));
@@ -1138,7 +1141,7 @@
             else
                 advance = info->advance;
 
-            draw_opaque_box(priv, v.asc, v.desc, v.border, advance,
+            draw_opaque_box(priv, info, 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));
 
@@ -1311,9 +1314,10 @@
                 outline, border,
                 &hash_val.bm, &hash_val.bm_o,
                 &hash_val.bm_s, info->be,
-                info->blur * render_priv->border_scale,
+                info->blur * render_priv->blur_scale,
                 key->shadow_offset,
-                info->border_style);
+                info->border_style,
+                info->border_x || info->border_y);
         if (error)
             info->symbol = 0;
 
@@ -2244,21 +2248,37 @@
 
     render_priv->font_scale = settings_priv->font_size_coeff *
         render_priv->orig_height / render_priv->track->PlayResY;
+    if (render_priv->storage_height)
+        render_priv->blur_scale = ((double) render_priv->orig_height) /
+            render_priv->storage_height;
+    else
+        render_priv->blur_scale = 1.;
     if (render_priv->track->ScaledBorderAndShadow)
         render_priv->border_scale =
             ((double) render_priv->orig_height) /
             render_priv->track->PlayResY;
     else
-        render_priv->border_scale = 1.;
+        render_priv->border_scale = render_priv->blur_scale;
+    render_priv->border_scale *= settings_priv->font_size_coeff;
 
     ass_shaper_set_kerning(render_priv->shaper, track->Kerning);
-    if (track->Language)
-        ass_shaper_set_language(render_priv->shaper, 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;
+    double par = render_priv->settings.par;
+    if (par == 0.) {
+        if (settings_priv->frame_width && settings_priv->frame_height &&
+            settings_priv->storage_width && settings_priv->storage_height) {
+            double dar = ((double) settings_priv->frame_width) /
+                         settings_priv->frame_height;
+            double sar = ((double) settings_priv->storage_width) /
+                         settings_priv->storage_height;
+            par = sar / dar;
+        } else
+            par = 1.0;
+    }
+    render_priv->font_scale_x = par;
 
     render_priv->prev_images_root = render_priv->images_root;
     render_priv->images_root = 0;
--- a/libass/ass_render.h	Sun Sep 22 09:03:30 2013 +0000
+++ b/libass/ass_render.h	Tue Sep 24 20:50:02 2013 +0000
@@ -26,6 +26,9 @@
 #include FT_STROKER_H
 #include FT_GLYPH_H
 #include FT_SYNTHESIS_H
+#ifdef CONFIG_HARFBUZZ
+#include "hb.h"
+#endif
 
 // XXX: fix the inclusion mess so we can avoid doing this here
 typedef struct ass_shaper ASS_Shaper;
@@ -65,6 +68,8 @@
 typedef struct {
     int frame_width;
     int frame_height;
+    int storage_width;          // width of the source image
+    int storage_height;         // height of the source image
     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)
@@ -74,8 +79,7 @@
     int right_margin;
     int use_margins;            // 0 - place all subtitles inside original frame
     // 1 - use margins for placing toptitles and subtitles
-    double aspect;              // frame aspect ratio, d_width / d_height.
-    double storage_aspect;      // pixel ratio of the source image
+    double par;                 // user defined pixel aspect ratio (0 = unset)
     ASS_Hinting hinting;
     ASS_ShapingLevel shaper;
 
@@ -107,6 +111,11 @@
     ASS_Font *font;
     int face_index;
     int glyph_index;
+#ifdef CONFIG_HARFBUZZ
+    hb_script_t script;
+#else
+    int script;
+#endif
     double font_size;
     ASS_Drawing *drawing;
     FT_Outline *outline;
@@ -264,11 +273,14 @@
     int orig_width;             // frame width ( = screen width - margins )
     int orig_height_nocrop;     // frame height ( = screen height - margins + cropheight)
     int orig_width_nocrop;      // frame width ( = screen width - margins + cropwidth)
+    int storage_height;         // video height before any rescaling
+    int storage_width;          // video width before any rescaling
     ASS_Track *track;
     long long time;             // frame's timestamp, ms
     double font_scale;
     double font_scale_x;        // x scale applied to all glyphs to preserve text aspect ratio
     double border_scale;
+    double blur_scale;
 
     RenderContext state;
     TextInfo text_info;
--- a/libass/ass_render_api.c	Sun Sep 22 09:03:30 2013 +0000
+++ b/libass/ass_render_api.c	Tue Sep 24 20:50:02 2013 +0000
@@ -43,6 +43,13 @@
     priv->orig_height_nocrop =
         settings->frame_height - FFMAX(settings->top_margin, 0) -
         FFMAX(settings->bottom_margin, 0);
+    if (settings->storage_height) {
+        priv->storage_width = settings->storage_width;
+        priv->storage_height = settings->storage_height;
+    } else {
+        priv->storage_width = priv->orig_width;
+        priv->storage_height = priv->orig_height;
+    }
 }
 
 void ass_set_frame_size(ASS_Renderer *priv, int w, int h)
@@ -50,10 +57,16 @@
     if (priv->settings.frame_width != w || priv->settings.frame_height != h) {
         priv->settings.frame_width = w;
         priv->settings.frame_height = h;
-        if (priv->settings.aspect == 0.) {
-            priv->settings.aspect = ((double) w) / h;
-            priv->settings.storage_aspect = ((double) w) / h;
-        }
+        ass_reconfigure(priv);
+    }
+}
+
+void ass_set_storage_size(ASS_Renderer *priv, int w, int h)
+{
+    if (priv->settings.storage_width != w ||
+        priv->settings.storage_height != h) {
+        priv->settings.storage_width = w;
+        priv->settings.storage_height = h;
         ass_reconfigure(priv);
     }
 }
@@ -88,9 +101,13 @@
 
 void ass_set_aspect_ratio(ASS_Renderer *priv, double dar, double sar)
 {
-    if (priv->settings.aspect != dar || priv->settings.storage_aspect != sar) {
-        priv->settings.aspect = dar;
-        priv->settings.storage_aspect = sar;
+    ass_set_pixel_aspect(priv, dar / sar);
+}
+
+void ass_set_pixel_aspect(ASS_Renderer *priv, double par)
+{
+    if (priv->settings.par != par) {
+        priv->settings.par = par;
         ass_reconfigure(priv);
     }
 }
--- a/libass/ass_shaper.c	Sun Sep 22 09:03:30 2013 +0000
+++ b/libass/ass_shaper.c	Tue Sep 24 20:50:02 2013 +0000
@@ -213,6 +213,12 @@
             return NULL;
 
         memcpy(&new_val.metrics, &face->glyph->metrics, sizeof(FT_Glyph_Metrics));
+
+        // if @font rendering is enabled and the glyph should be rotated,
+        // make cached_h_advance pick up the right advance later
+        if (metrics->vertical && glyph >= VERTICAL_LOWER_BOUND)
+            new_val.metrics.horiAdvance = new_val.metrics.vertAdvance;
+
         val = ass_cache_put(metrics->metrics_cache, &metrics->hash_key, &new_val);
     }
 
@@ -244,9 +250,6 @@
     if (!metrics)
         return 0;
 
-    if (metrics_priv->vertical && glyph > VERTICAL_LOWER_BOUND)
-        return metrics->metrics.vertAdvance;
-
     return metrics->metrics.horiAdvance;
 }
 
@@ -419,6 +422,107 @@
 }
 
 /**
+ * \brief Map script to default language.
+ *
+ * This maps a script to a language, if a script has a representative
+ * language it is typically used with. Otherwise, the invalid language
+ * is returned.
+ *
+ * The mapping is similar to Pango's pango-language.c.
+ *
+ * \param script script tag
+ * \return language tag
+ */
+static hb_language_t script_to_language(hb_script_t script)
+{
+    switch (script) {
+        // Unicode 1.1
+        case HB_SCRIPT_ARABIC: return hb_language_from_string("ar", -1); break;
+        case HB_SCRIPT_ARMENIAN: return hb_language_from_string("hy", -1); break;
+        case HB_SCRIPT_BENGALI: return hb_language_from_string("bn", -1); break;
+        case HB_SCRIPT_CANADIAN_ABORIGINAL: return hb_language_from_string("iu", -1); break;
+        case HB_SCRIPT_CHEROKEE: return hb_language_from_string("chr", -1); break;
+        case HB_SCRIPT_COPTIC: return hb_language_from_string("cop", -1); break;
+        case HB_SCRIPT_CYRILLIC: return hb_language_from_string("ru", -1); break;
+        case HB_SCRIPT_DEVANAGARI: return hb_language_from_string("hi", -1); break;
+        case HB_SCRIPT_GEORGIAN: return hb_language_from_string("ka", -1); break;
+        case HB_SCRIPT_GREEK: return hb_language_from_string("el", -1); break;
+        case HB_SCRIPT_GUJARATI: return hb_language_from_string("gu", -1); break;
+        case HB_SCRIPT_GURMUKHI: return hb_language_from_string("pa", -1); break;
+        case HB_SCRIPT_HANGUL: return hb_language_from_string("ko", -1); break;
+        case HB_SCRIPT_HEBREW: return hb_language_from_string("he", -1); break;
+        case HB_SCRIPT_HIRAGANA: return hb_language_from_string("ja", -1); break;
+        case HB_SCRIPT_KANNADA: return hb_language_from_string("kn", -1); break;
+        case HB_SCRIPT_KATAKANA: return hb_language_from_string("ja", -1); break;
+        case HB_SCRIPT_LAO: return hb_language_from_string("lo", -1); break;
+        case HB_SCRIPT_LATIN: return hb_language_from_string("en", -1); break;
+        case HB_SCRIPT_MALAYALAM: return hb_language_from_string("ml", -1); break;
+        case HB_SCRIPT_MONGOLIAN: return hb_language_from_string("mn", -1); break;
+        case HB_SCRIPT_ORIYA: return hb_language_from_string("or", -1); break;
+        case HB_SCRIPT_SYRIAC: return hb_language_from_string("syr", -1); break;
+        case HB_SCRIPT_TAMIL: return hb_language_from_string("ta", -1); break;
+        case HB_SCRIPT_TELUGU: return hb_language_from_string("te", -1); break;
+        case HB_SCRIPT_THAI: return hb_language_from_string("th", -1); break;
+
+        // Unicode 2.0
+        case HB_SCRIPT_TIBETAN: return hb_language_from_string("bo", -1); break;
+
+        // Unicode 3.0
+        case HB_SCRIPT_ETHIOPIC: return hb_language_from_string("am", -1); break;
+        case HB_SCRIPT_KHMER: return hb_language_from_string("km", -1); break;
+        case HB_SCRIPT_MYANMAR: return hb_language_from_string("my", -1); break;
+        case HB_SCRIPT_SINHALA: return hb_language_from_string("si", -1); break;
+        case HB_SCRIPT_THAANA: return hb_language_from_string("dv", -1); break;
+
+        // Unicode 3.2
+        case HB_SCRIPT_BUHID: return hb_language_from_string("bku", -1); break;
+        case HB_SCRIPT_HANUNOO: return hb_language_from_string("hnn", -1); break;
+        case HB_SCRIPT_TAGALOG: return hb_language_from_string("tl", -1); break;
+        case HB_SCRIPT_TAGBANWA: return hb_language_from_string("tbw", -1); break;
+
+        // Unicode 4.0
+        case HB_SCRIPT_UGARITIC: return hb_language_from_string("uga", -1); break;
+
+        // Unicode 4.1
+        case HB_SCRIPT_BUGINESE: return hb_language_from_string("bug", -1); break;
+        case HB_SCRIPT_OLD_PERSIAN: return hb_language_from_string("peo", -1); break;
+        case HB_SCRIPT_SYLOTI_NAGRI: return hb_language_from_string("syl", -1); break;
+
+        // Unicode 5.0
+        case HB_SCRIPT_NKO: return hb_language_from_string("nko", -1); break;
+
+        // no representative language exists
+        default: return HB_LANGUAGE_INVALID; break;
+    }
+}
+
+/**
+ * \brief Determine language to be used for shaping a run.
+ *
+ * \param shaper shaper instance
+ * \param script script tag associated with run
+ * \return language tag
+ */
+static hb_language_t
+hb_shaper_get_run_language(ASS_Shaper *shaper, hb_script_t script)
+{
+    hb_language_t lang;
+
+    // override set, use it
+    if (shaper->language != HB_LANGUAGE_INVALID)
+        return shaper->language;
+
+    // get default language for given script
+    lang = script_to_language(script);
+
+    // no dice, use system default
+    if (lang == HB_LANGUAGE_INVALID)
+        lang = hb_language_get_default();
+
+    return lang;
+}
+
+/**
  * \brief Shape event text with HarfBuzz. Full OpenType shaping.
  * \param glyphs glyph clusters
  * \param len number of clusters
@@ -440,6 +544,7 @@
         int k = i;
         int level = glyphs[i].shape_run_id;
         int direction = shaper->emblevels[k] % 2;
+        hb_script_t script = glyphs[i].script;
         while (i < (len - 1) && level == glyphs[i+1].shape_run_id)
             i++;
         runs[run].offset = k;
@@ -450,7 +555,9 @@
         hb_buffer_pre_allocate(runs[run].buf, i - k + 1);
         hb_buffer_set_direction(runs[run].buf, direction ? HB_DIRECTION_RTL :
                 HB_DIRECTION_LTR);
-        hb_buffer_set_language(runs[run].buf, shaper->language);
+        hb_buffer_set_language(runs[run].buf,
+                hb_shaper_get_run_language(shaper, script));
+        hb_buffer_set_script(runs[run].buf, script);
         hb_buffer_add_utf32(runs[run].buf, shaper->event_text + k, i - k + 1,
                 0, i - k + 1);
         hb_shape(runs[run].font, runs[run].buf, shaper->features,
@@ -503,6 +610,56 @@
     }
 
 }
+
+/**
+ * \brief Determine script property of all characters. Characters of script
+ * common and inherited get their script from their context.
+ *
+ */
+void ass_shaper_determine_script(ASS_Shaper *shaper, GlyphInfo *glyphs,
+                                  size_t len)
+{
+    int i;
+    int backwards_scan = 0;
+    hb_unicode_funcs_t *ufuncs = hb_unicode_funcs_get_default();
+    hb_script_t last_script = HB_SCRIPT_UNKNOWN;
+
+    // determine script (forward scan)
+    for (i = 0; i < len; i++) {
+        GlyphInfo *info = glyphs + i;
+        info->script = hb_unicode_script(ufuncs, info->symbol);
+
+        // common/inherit codepoints inherit script from context
+        if (info->script == HB_SCRIPT_COMMON ||
+                info->script == HB_SCRIPT_INHERITED) {
+            // unknown is not a valid context
+            if (last_script != HB_SCRIPT_UNKNOWN)
+                info->script = last_script;
+            else
+                // do a backwards scan to check if next codepoint
+                // contains a valid script for context
+                backwards_scan = 1;
+        } else {
+            last_script = info->script;
+        }
+    }
+
+    // determine script (backwards scan, if needed)
+    last_script = HB_SCRIPT_UNKNOWN;
+    for (i = len - 1; i >= 0 && backwards_scan; i--) {
+        GlyphInfo *info = glyphs + i;
+
+        // common/inherit codepoints inherit script from context
+        if (info->script == HB_SCRIPT_COMMON ||
+                info->script == HB_SCRIPT_INHERITED) {
+            // unknown script is not a valid context
+            if (last_script != HB_SCRIPT_UNKNOWN)
+                info->script = last_script;
+        } else {
+            last_script = info->script;
+        }
+    }
+}
 #endif
 
 #ifdef CONFIG_FRIBIDI
@@ -555,6 +712,11 @@
     int i;
     int shape_run = 0;
 
+#ifdef CONFIG_HARFBUZZ
+    ass_shaper_determine_script(shaper, glyphs, len);
+#endif
+
+    // find appropriate fonts for the shape runs
     for (i = 0; i < len; i++) {
         GlyphInfo *last = glyphs + i - 1;
         GlyphInfo *info = glyphs + i;
@@ -567,11 +729,11 @@
         // shape runs share the same font face and size
         if (i > 0 && (last->font != info->font ||
                     last->font_size != info->font_size ||
-                    last->face_index != info->face_index))
+                    last->face_index != info->face_index ||
+                    last->script != info->script))
             shape_run++;
         info->shape_run_id = shape_run;
     }
-
 }
 
 /**
@@ -591,7 +753,14 @@
 void ass_shaper_set_language(ASS_Shaper *shaper, const char *code)
 {
 #ifdef CONFIG_HARFBUZZ
-    shaper->language = hb_language_from_string(code, -1);
+    hb_language_t lang;
+
+    if (code)
+        lang = hb_language_from_string(code, -1);
+    else
+        lang = HB_LANGUAGE_INVALID;
+
+    shaper->language = lang;
 #endif
 }
 
--- a/libass/ass_types.h	Sun Sep 22 09:03:30 2013 +0000
+++ b/libass/ass_types.h	Tue Sep 24 20:50:02 2013 +0000
@@ -50,7 +50,7 @@
     double ScaleX;
     double ScaleY;
     double Spacing;
-    int Angle;
+    double Angle;
     int BorderStyle;
     double Outline;
     double Shadow;
@@ -60,6 +60,7 @@
     int MarginV;
     int Encoding;
     int treat_fontname_as_pattern;
+    double Blur;
 } ASS_Style;
 
 /*
@@ -113,6 +114,19 @@
     int ScaledBorderAndShadow;
     int Kerning;
     char *Language;
+    enum {
+        YCBCR_DEFAULT = 0,  // TV.601 on YCbCr video, None on RGB video
+        YCBCR_UNKNOWN,
+        YCBCR_NONE,         // untouched RGB values
+        YCBCR_BT601_TV,
+        YCBCR_BT601_PC,
+        YCBCR_BT709_TV,
+        YCBCR_BT709_PC,
+        YCBCR_SMPTE240M_TV,
+        YCBCR_SMPTE240M_PC,
+        YCBCR_FCC_TV,
+        YCBCR_FCC_PC
+    } YCbCrMatrix;
 
     int default_style;      // index of default style
     char *name;             // file name in case of external subs, 0 for streams
--- a/libass/ass_utils.c	Sun Sep 22 09:03:30 2013 +0000
+++ b/libass/ass_utils.c	Tue Sep 24 20:50:02 2013 +0000
@@ -122,6 +122,46 @@
     return 0;
 }
 
+int parse_ycbcr_matrix(char *str)
+{
+    while (*str == ' ' || *str == '\t')
+        str++;
+    if (*str == '\0')
+        return YCBCR_DEFAULT;
+
+    char *end = str + strlen(str);
+    while (end[-1] == ' ' || end[-1] == '\t')
+        end--;
+
+    // Trim a local copy of the input that we know is safe to
+    // modify. The buffer is larger than any valid string + NUL,
+    // so we can simply chop off the rest of the input.
+    char buffer[16];
+    size_t n = FFMIN(end - str, sizeof buffer - 1);
+    strncpy(buffer, str, n);
+    buffer[n] = '\0';
+
+    if (!strcasecmp(buffer, "none"))
+        return YCBCR_NONE;
+    if (!strcasecmp(buffer, "tv.601"))
+        return YCBCR_BT601_TV;
+    if (!strcasecmp(buffer, "pc.601"))
+        return YCBCR_BT601_PC;
+    if (!strcasecmp(buffer, "tv.709"))
+        return YCBCR_BT709_TV;
+    if (!strcasecmp(buffer, "pc.709"))
+        return YCBCR_BT709_PC;
+    if (!strcasecmp(buffer, "tv.240m"))
+        return YCBCR_SMPTE240M_TV;
+    if (!strcasecmp(buffer, "pc.240m"))
+        return YCBCR_SMPTE240M_PC;
+    if (!strcasecmp(buffer, "tv.fcc"))
+        return YCBCR_FCC_TV;
+    if (!strcasecmp(buffer, "pc.fcc"))
+        return YCBCR_FCC_PC;
+    return YCBCR_UNKNOWN;
+}
+
 void ass_msg(ASS_Library *priv, int lvl, char *fmt, ...)
 {
     va_list va;
--- a/libass/ass_utils.h	Sun Sep 22 09:03:30 2013 +0000
+++ b/libass/ass_utils.h	Tue Sep 24 20:50:02 2013 +0000
@@ -49,6 +49,7 @@
 int mystrtod(char **p, double *res);
 int strtocolor(ASS_Library *library, char **q, uint32_t *res, int hex);
 char parse_bool(char *str);
+int parse_ycbcr_matrix(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);