Import libass 0.9.10
authorgreg <greg@b3059339-0415-0410-9bf9-f77b7e298cf2>
Fri, 6 Aug 2010 21:13:41 +0000 (21:13 +0000)
committergreg <greg@b3059339-0415-0410-9bf9-f77b7e298cf2>
Fri, 6 Aug 2010 21:13:41 +0000 (21:13 +0000)
git-svn-id: svn://git.mplayerhq.hu/mplayer/trunk@31932 b3059339-0415-0410-9bf9-f77b7e298cf2

16 files changed:
Makefile
libass/ass.c
libass/ass.h
libass/ass_bitmap.c
libass/ass_bitmap.h
libass/ass_cache.c
libass/ass_drawing.c
libass/ass_drawing.h
libass/ass_font.c
libass/ass_font.h
libass/ass_fontconfig.c
libass/ass_parse.c
libass/ass_render.c
libass/ass_render.h
libass/ass_render_api.c [new file with mode: 0644]
libass/ass_strtod.c

index adfa5b7..3a2fb29 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -119,6 +119,7 @@ SRCS_COMMON-$(LIBASS_INTERNAL)       += libass/ass.c \
                                         libass/ass_library.c \
                                         libass/ass_parse.c \
                                         libass/ass_render.c \
+                                        libass/ass_render_api.c \
                                         libass/ass_strtod.c \
                                         libass/ass_utils.c \
 
index 1e8cddd..7dcde34 100644 (file)
@@ -38,6 +38,8 @@
 #include "ass_utils.h"
 #include "ass_library.h"
 
+#define ass_atof(STR) (ass_strtod((STR),NULL))
+
 typedef enum {
     PST_UNKNOWN = 0,
     PST_INFO,
@@ -250,7 +252,7 @@ static int numpad2align(int val)
                ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
 
 #define INTVAL(name) ANYVAL(name,atoi)
-#define FPVAL(name) ANYVAL(name,atof)
+#define FPVAL(name) ANYVAL(name,ass_atof)
 #define TIMEVAL(name) \
        } else if (strcasecmp(tname, #name) == 0) { \
                target->name = string2timecode(track->library, token); \
@@ -384,7 +386,7 @@ void ass_process_force_style(ASS_Track *track)
         else if (!strcasecmp(*fs, "PlayResY"))
             track->PlayResY = atoi(token);
         else if (!strcasecmp(*fs, "Timer"))
-            track->Timer = atof(token);
+            track->Timer = ass_atof(token);
         else if (!strcasecmp(*fs, "WrapStyle"))
             track->WrapStyle = atoi(token);
         else if (!strcasecmp(*fs, "ScaledBorderAndShadow"))
@@ -534,12 +536,6 @@ static int process_style(ASS_Track *track, char *str)
         style->Name = strdup("Default");
     if (!style->FontName)
         style->FontName = strdup("Arial");
-    // skip '@' at the start of the font name
-    if (*style->FontName == '@') {
-        p = style->FontName;
-        style->FontName = strdup(p + 1);
-        free(p);
-    }
     free(format);
     return 0;
 
@@ -568,7 +564,7 @@ static int process_info_line(ASS_Track *track, char *str)
     } else if (!strncmp(str, "PlayResY:", 9)) {
         track->PlayResY = atoi(str + 9);
     } else if (!strncmp(str, "Timer:", 6)) {
-        track->Timer = atof(str + 6);
+        track->Timer = ass_atof(str + 6);
     } else if (!strncmp(str, "WrapStyle:", 10)) {
         track->WrapStyle = atoi(str + 10);
     } else if (!strncmp(str, "ScaledBorderAndShadow:", 22)) {
@@ -618,7 +614,7 @@ static int process_events_line(ASS_Track *track, char *str)
 
         process_event_tail(track, event, str, 0);
     } else {
-        ass_msg(track->library, MSGL_V, "Not understood: '%s'", str);
+        ass_msg(track->library, MSGL_V, "Not understood: '%.30s'", str);
     }
     return 0;
 }
@@ -1033,14 +1029,6 @@ static char *read_file(ASS_Library *library, char *fname, size_t *bufsize)
     sz = ftell(fp);
     rewind(fp);
 
-    if (sz > 10 * 1024 * 1024) {
-        ass_msg(library, MSGL_INFO,
-               "ass_read_file(%s): Refusing to load subtitles "
-               "larger than 10MiB", fname);
-        fclose(fp);
-        return 0;
-    }
-
     ass_msg(library, MSGL_V, "File size: %ld", sz);
 
     buf = malloc(sz + 1);
index c1053b4..1d98298 100644 (file)
@@ -75,11 +75,13 @@ ASS_Library *ass_library_init(void);
 void ass_library_done(ASS_Library *priv);
 
 /**
- * \brief Set private font directory.
- * It is used for saving embedded fonts and also in font lookup.
+ * \brief Set additional fonts directory.
+ * Optional directory that will be scanned for fonts recursively.  The fonts
+ * found are used for font lookup.
+ * NOTE: A valid font directory is not needed to support embedded fonts.
  *
  * \param priv library handle
- * \param fonts_dir private directory for font extraction
+ * \param fonts_dir directory with additional fonts
  */
 void ass_set_fonts_dir(ASS_Library *priv, const char *fonts_dir);
 
@@ -203,6 +205,8 @@ void ass_set_line_spacing(ASS_Renderer *priv, double line_spacing);
  * if fontconfig is used.
  * \param update whether fontconfig cache should be built/updated now.  Only
  * relevant if fontconfig is used.
+ *
+ * NOTE: font lookup must be configured before an ASS_Renderer can be used.
  */
 void ass_set_fonts(ASS_Renderer *priv, const char *default_font,
                    const char *default_family, int fc, const char *config,
@@ -297,7 +301,8 @@ void ass_free_event(ASS_Track *track, int eid);
 void ass_process_data(ASS_Track *track, char *data, int size);
 
 /**
- * \brief Parse Codec Private section of subtitle stream.
+ * \brief Parse Codec Private section of the subtitle stream, in Matroska
+ * format.  See the Matroska specification for details.
  * \param track target track
  * \param data string to parse
  * \param size length of data
@@ -305,8 +310,8 @@ void ass_process_data(ASS_Track *track, char *data, int size);
 void ass_process_codec_private(ASS_Track *track, char *data, int size);
 
 /**
- * \brief Parse a chunk of subtitle stream data. In Matroska,
- * this contains exactly 1 event (or a commentary).
+ * \brief Parse a chunk of subtitle stream data. A chunk contains exactly one
+ * event in Matroska format.  See the Matroska specification for details.
  * \param track track
  * \param data string to parse
  * \param size length of data
index c7c039d..9211a7c 100644 (file)
@@ -139,8 +139,8 @@ void ass_synth_done(ASS_SynthPriv *priv)
 static Bitmap *alloc_bitmap(int w, int h)
 {
     Bitmap *bm;
-    bm = calloc(1, sizeof(Bitmap));
-    bm->buffer = malloc(w * h);
+    bm = malloc(sizeof(Bitmap));
+    bm->buffer = calloc(w, h);
     bm->w = w;
     bm->h = h;
     bm->left = bm->top = 0;
@@ -165,7 +165,7 @@ static Bitmap *copy_bitmap(const Bitmap *src)
     return dst;
 }
 
-static int check_glyph_area(ASS_Library *library, FT_Glyph glyph)
+int check_glyph_area(ASS_Library *library, FT_Glyph glyph)
 {
     FT_BBox bbox;
     long long dx, dy;
@@ -213,7 +213,6 @@ static Bitmap *glyph_to_bitmap_internal(ASS_Library *library,
     w = bit->width;
     h = bit->rows;
     bm = alloc_bitmap(w + 2 * bord, h + 2 * bord);
-    memset(bm->buffer, 0, bm->w * bm->h);
     bm->left = bg->left - bord;
     bm->top = -bg->top - bord;
 
index 338db01..7a61118 100644 (file)
@@ -53,5 +53,6 @@ int glyph_to_bitmap(ASS_Library *library, ASS_SynthPriv *priv_blur,
                     int border_style);
 
 void ass_free_bitmap(Bitmap *bm);
+int check_glyph_area(ASS_Library *library, FT_Glyph glyph);
 
 #endif                          /* LIBASS_BITMAP_H */
index 643d991..3ebf0cc 100644 (file)
@@ -156,6 +156,8 @@ static int font_compare(void *key1, void *key2, size_t key_size)
         return 0;
     if (a->treat_family_as_pattern != b->treat_family_as_pattern)
         return 0;
+    if (a->vertical != b->vertical)
+        return 0;
     return 1;
 }
 
@@ -286,6 +288,11 @@ static void glyph_hash_dtor(void *key, size_t key_size, void *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);
 }
 
index a3207c7..7833c10 100644 (file)
@@ -112,24 +112,12 @@ static void drawing_prepare(ASS_Drawing *drawing)
 static void drawing_finish(ASS_Drawing *drawing, int raw_mode)
 {
     int i, offset;
-    FT_BBox bbox;
+    FT_BBox bbox = drawing->cbox;
     FT_Outline *ol = &drawing->glyph->outline;
 
     // Close the last contour
     drawing_close_shape(drawing);
 
-#if 0
-    // Dump points
-    for (i = 0; i < ol->n_points; i++) {
-        printf("point (%d, %d)\n", (int) ol->points[i].x,
-               (int) ol->points[i].y);
-    }
-
-    // Dump contours
-    for (i = 0; i < ol->n_contours; i++)
-        printf("contour %d\n", ol->contours[i]);
-#endif
-
     ass_msg(drawing->library, MSGL_V,
             "Parsed drawing with %d points and %d contours", ol->n_points,
             ol->n_contours);
@@ -137,7 +125,6 @@ static void drawing_finish(ASS_Drawing *drawing, int raw_mode)
     if (raw_mode)
         return;
 
-    FT_Outline_Get_CBox(&drawing->glyph->outline, &bbox);
     drawing->glyph->root.advance.x = d6_to_d16(bbox.xMax - bbox.xMin);
 
     drawing->desc = double_to_d6(-drawing->pbo * drawing->scale_y);
@@ -231,15 +218,6 @@ static ASS_DrawingToken *drawing_tokenize(char *str)
         p++;
     }
 
-#if 0
-    // Check tokens
-    ASS_DrawingToken *t = root;
-    while(t) {
-        printf("token %d point (%d, %d)\n", t->type, t->point.x, t->point.y);
-        t = t->next;
-    }
-#endif
-
     return root;
 }
 
@@ -256,6 +234,19 @@ static void drawing_free_tokens(ASS_DrawingToken *token)
 }
 
 /*
+ * \brief Update drawing cbox
+ */
+static inline void update_cbox(ASS_Drawing *drawing, FT_Vector *point)
+{
+    FT_BBox *box = &drawing->cbox;
+
+    box->xMin = FFMIN(box->xMin, point->x);
+    box->xMax = FFMAX(box->xMax, point->x);
+    box->yMin = FFMIN(box->yMin, point->y);
+    box->yMax = FFMAX(box->yMax, point->y);
+}
+
+/*
  * \brief Translate and scale a point coordinate according to baseline
  * offset and scale.
  */
@@ -263,6 +254,8 @@ static inline void translate_point(ASS_Drawing *drawing, FT_Vector *point)
 {
     point->x = drawing->point_scale_x * point->x;
     point->y = drawing->point_scale_y * -point->y;
+
+    update_cbox(drawing, point);
 }
 
 /*
@@ -369,6 +362,8 @@ ASS_Drawing *ass_drawing_new(void *fontconfig_priv, ASS_Font *font,
     drawing = calloc(1, sizeof(*drawing));
     drawing->text = calloc(1, DRAWING_INITIAL_SIZE);
     drawing->size = DRAWING_INITIAL_SIZE;
+    drawing->cbox.xMin = drawing->cbox.yMin = INT_MAX;
+    drawing->cbox.xMax = drawing->cbox.yMax = INT_MIN;
 
     drawing->ftlibrary = lib;
     if (font) {
@@ -390,8 +385,6 @@ ASS_Drawing *ass_drawing_new(void *fontconfig_priv, ASS_Font *font,
 void ass_drawing_free(ASS_Drawing* drawing)
 {
     if (drawing) {
-        if (drawing->glyph)
-            FT_Done_Glyph((FT_Glyph) drawing->glyph);
         free(drawing->text);
     }
     free(drawing);
index 913588e..0223271 100644 (file)
@@ -65,6 +65,7 @@ typedef struct {
     int max_contours;
     double point_scale_x;
     double point_scale_y;
+    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,
index 7db1f07..6a1faab 100644 (file)
 #include "ass_fontconfig.h"
 #include "ass_utils.h"
 
+#define VERTICAL_LOWER_BOUND 0x02f1
+
 /**
- * Select Microfost Unicode CharMap, if the font has one.
+ * Select a good charmap, prefer Microsoft Unicode charmaps.
  * Otherwise, let FreeType decide.
  */
 static void charmap_magic(ASS_Library *library, FT_Face face)
 {
     int i;
+    int ms_cmap = -1;
+
+    // Search for a Microsoft Unicode cmap
     for (i = 0; i < face->num_charmaps; ++i) {
         FT_CharMap cmap = face->charmaps[i];
         unsigned pid = cmap->platform_id;
@@ -52,7 +57,15 @@ static void charmap_magic(ASS_Library *library, FT_Face face)
                 || eid == 10 /*full unicode */ )) {
             FT_Set_Charmap(face, cmap);
             return;
-        }
+        } else if (pid == 3 && ms_cmap < 0)
+            ms_cmap = i;
+    }
+
+    // Try the first Microsoft cmap if no Microsoft Unicode cmap was found
+    if (ms_cmap >= 0) {
+        FT_CharMap cmap = face->charmaps[ms_cmap];
+        FT_Set_Charmap(face, cmap);
+        return;
     }
 
     if (!face->charmap) {
@@ -67,17 +80,6 @@ static void charmap_magic(ASS_Library *library, FT_Face face)
     }
 }
 
-static void update_transform(ASS_Font *font)
-{
-    int i;
-    FT_Matrix m;
-    m.xx = double_to_d16(font->scale_x);
-    m.yy = double_to_d16(font->scale_y);
-    m.xy = m.yx = 0;
-    for (i = 0; i < font->n_faces; ++i)
-        FT_Set_Transform(font->faces[i], &m, &font->v);
-}
-
 /**
  * \brief find a memory font by name
  */
@@ -139,7 +141,7 @@ static int add_face(void *fc_priv, ASS_Font *font, uint32_t ch)
             FT_New_Memory_Face(font->ftlibrary,
                                (unsigned char *) font->library->
                                fontdata[mem_idx].data,
-                               font->library->fontdata[mem_idx].size, 0,
+                               font->library->fontdata[mem_idx].size, index,
                                &face);
         if (error) {
             ass_msg(font->library, MSGL_WARN,
@@ -160,7 +162,6 @@ static int add_face(void *fc_priv, ASS_Font *font, uint32_t ch)
     buggy_font_workaround(face);
 
     font->faces[font->n_faces++] = face;
-    update_transform(font);
     face_set_size(face, font->size);
     free(path);
     return font->n_faces - 1;
@@ -188,6 +189,7 @@ ASS_Font *ass_font_new(void *font_cache, ASS_Library *library,
     font.desc.treat_family_as_pattern = desc->treat_family_as_pattern;
     font.desc.bold = desc->bold;
     font.desc.italic = desc->italic;
+    font.desc.vertical = desc->vertical;
 
     font.scale_x = font.scale_y = 1.;
     font.v.x = font.v.y = 0;
@@ -213,7 +215,6 @@ void ass_font_set_transform(ASS_Font *font, double scale_x,
         font->v.x = v->x;
         font->v.y = v->y;
     }
-    update_transform(font);
 }
 
 static void face_set_size(FT_Face face, double size)
@@ -276,6 +277,9 @@ void ass_font_get_asc_desc(ASS_Font *font, uint32_t ch, int *asc,
                 *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;
         }
     }
@@ -414,6 +418,7 @@ FT_Glyph ass_font_get_glyph(void *fontconfig_priv, ASS_Font *font,
     FT_Glyph glyph;
     FT_Face face = 0;
     int flags = 0;
+    int vertical = font->desc.vertical;
 
     if (ch < 0x20)
         return 0;
@@ -441,6 +446,14 @@ FT_Glyph ass_font_get_glyph(void *fontconfig_priv, ASS_Font *font,
         if (face_idx >= 0) {
             face = font->faces[face_idx];
             index = FT_Get_Char_Index(face, ch);
+            if (index == 0 && face->num_charmaps > 0) {
+                ass_msg(font->library, MSGL_WARN,
+                    "Glyph 0x%X not found, falling back to first charmap", ch);
+                FT_CharMap cur = face->charmap;
+                FT_Set_Charmap(face, face->charmaps[0]);
+                index = FT_Get_Char_Index(face, ch);
+                FT_Set_Charmap(face, cur);
+            }
             if (index == 0) {
                 ass_msg(font->library, MSGL_ERR,
                         "Glyph 0x%X not found in font for (%s, %d, %d)",
@@ -451,22 +464,23 @@ FT_Glyph ass_font_get_glyph(void *fontconfig_priv, ASS_Font *font,
     }
 #endif
 
+    flags = FT_LOAD_NO_BITMAP | FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH
+            | FT_LOAD_IGNORE_TRANSFORM;
     switch (hinting) {
     case ASS_HINTING_NONE:
-        flags = FT_LOAD_NO_HINTING;
+        flags |= FT_LOAD_NO_HINTING;
         break;
     case ASS_HINTING_LIGHT:
-        flags = FT_LOAD_FORCE_AUTOHINT | FT_LOAD_TARGET_LIGHT;
+        flags |= FT_LOAD_FORCE_AUTOHINT | FT_LOAD_TARGET_LIGHT;
         break;
     case ASS_HINTING_NORMAL:
-        flags = FT_LOAD_FORCE_AUTOHINT;
+        flags |= FT_LOAD_FORCE_AUTOHINT;
         break;
     case ASS_HINTING_NATIVE:
-        flags = 0;
         break;
     }
 
-    error = FT_Load_Glyph(face, index, FT_LOAD_NO_BITMAP | flags);
+    error = FT_Load_Glyph(face, index, flags);
     if (error) {
         ass_msg(font->library, MSGL_WARN, "Error loading glyph, index %d",
                 index);
@@ -488,6 +502,24 @@ FT_Glyph ass_font_get_glyph(void *fontconfig_priv, ASS_Font *font,
         return 0;
     }
 
+    // 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 };
+        FT_Outline_Transform(&((FT_OutlineGlyph) glyph)->outline, &m);
+        FT_Outline_Translate(&((FT_OutlineGlyph) glyph)->outline,
+                             face->glyph->metrics.vertAdvance,
+                             0);
+        glyph->advance.x = face->glyph->linearVertAdvance;
+    }
+
+    // Apply scaling and shift
+    FT_Matrix scale = { double_to_d16(font->scale_x), 0, 0,
+                        double_to_d16(font->scale_y) };
+    FT_Outline *outl = &((FT_OutlineGlyph) glyph)->outline;
+    FT_Outline_Transform(outl, &scale);
+    FT_Outline_Translate(outl, font->v.x, font->v.y);
+    glyph->advance.x *= font->scale_x;
+
     ass_strike_outline_glyph(face, font, glyph, deco & DECO_UNDERLINE,
                              deco & DECO_STRIKETHROUGH);
 
@@ -502,6 +534,9 @@ FT_Vector ass_font_get_kerning(ASS_Font *font, uint32_t c1, uint32_t c2)
     FT_Vector v = { 0, 0 };
     int i;
 
+    if (font->desc.vertical)
+        return v;
+
     for (i = 0; i < font->n_faces; ++i) {
         FT_Face face = font->faces[i];
         int i1 = FT_Get_Char_Index(face, c1);
@@ -530,3 +565,130 @@ void ass_font_free(ASS_Font *font)
         free(font->desc.family);
     free(font);
 }
+
+/**
+ * \brief Calculate the cbox of a series of points
+ */
+static void
+get_contour_cbox(FT_BBox *box, FT_Vector *points, int start, int end)
+{
+    box->xMin = box->yMin = INT_MAX;
+    box->xMax = box->yMax = INT_MIN;
+    int i;
+
+    for (i = start; i <= end; i++) {
+        box->xMin = (points[i].x < box->xMin) ? points[i].x : box->xMin;
+        box->xMax = (points[i].x > box->xMax) ? points[i].x : box->xMax;
+        box->yMin = (points[i].y < box->yMin) ? points[i].y : box->yMin;
+        box->yMax = (points[i].y > box->yMax) ? points[i].y : box->yMax;
+    }
+}
+
+/**
+ * \brief Determine winding direction of a contour
+ * \return direction; 0 = clockwise
+ */
+static int get_contour_direction(FT_Vector *points, int start, int end)
+{
+    int i;
+    long long sum = 0;
+    int x = points[start].x;
+    int y = points[start].y;
+    for (i = start + 1; i <= end; i++) {
+        sum += x * (points[i].y - y) - y * (points[i].x - x);
+        x = points[i].x;
+        y = points[i].y;
+    }
+    sum += x * (points[start].y - y) - y * (points[start].x - x);
+    return sum > 0;
+}
+
+/**
+ * \brief Fix-up stroker result for huge borders by removing inside contours
+ * that would reverse in size
+ */
+void fix_freetype_stroker(FT_OutlineGlyph glyph, int border_x, int border_y)
+{
+    int nc = glyph->outline.n_contours;
+    int begin, stop;
+    char modified = 0;
+    char *valid_cont = malloc(nc);
+    int start = 0;
+    int end = -1;
+    FT_BBox *boxes = malloc(nc * sizeof(FT_BBox));
+    int i, j;
+    int inside_direction;
+
+    inside_direction = FT_Outline_Get_Orientation(&glyph->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);
+    }
+
+    // for each contour, check direction and whether it's "outside"
+    // or contained in another contour
+    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);
+        valid_cont[i] = 1;
+        if (dir == inside_direction) {
+            for (j = 0; j < nc; j++) {
+                if (i == j)
+                    continue;
+                if (boxes[i].xMin >= boxes[j].xMin &&
+                    boxes[i].xMax <= boxes[j].xMax &&
+                    boxes[i].yMin >= boxes[j].yMin &&
+                    boxes[i].yMax <= boxes[j].yMax)
+                    goto check_inside;
+            }
+            /* "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 = 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;
+            }
+            dir ^= 1;
+        }
+        check_inside:
+        if (dir == inside_direction) {
+            FT_BBox box;
+            get_contour_cbox(&box, glyph->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) {
+                valid_cont[i] = 0;
+                modified = 1;
+            }
+        }
+    }
+
+    // zero-out contours that can be removed; much simpler than copying
+    if (modified) {
+        for (i = 0; i < nc; i++) {
+            if (valid_cont[i])
+                continue;
+            begin = (i == 0) ? 0 : glyph->outline.contours[i - 1] + 1;
+            stop = glyph->outline.contours[i];
+            for (j = begin; j <= stop; j++) {
+                glyph->outline.points[j].x = 0;
+                glyph->outline.points[j].y = 0;
+                glyph->outline.tags[j] = 0;
+            }
+        }
+    }
+
+    free(boxes);
+    free(valid_cont);
+}
+
index ca0c213..91660bf 100644 (file)
@@ -36,6 +36,7 @@ typedef struct {
     unsigned bold;
     unsigned italic;
     int treat_family_as_pattern;
+    int vertical;               // @font vertical layout
 } ASS_FontDesc;
 
 typedef struct {
@@ -62,5 +63,6 @@ FT_Glyph ass_font_get_glyph(void *fontconfig_priv, ASS_Font *font,
                             uint32_t ch, ASS_Hinting hinting, int flags);
 FT_Vector ass_font_get_kerning(ASS_Font *font, uint32_t c1, uint32_t c2);
 void ass_font_free(ASS_Font *font);
+void fix_freetype_stroker(FT_OutlineGlyph glyph, int border_x, int border_y);
 
 #endif                          /* LIBASS_FONT_H */
index 2a43694..15490d5 100644 (file)
@@ -52,6 +52,61 @@ struct fc_instance {
 #ifdef CONFIG_FONTCONFIG
 
 /**
+ * \brief Case-insensitive match ASS/SSA font family against full name. (also
+ * known as "name for humans")
+ *
+ * \param lib library instance
+ * \param priv fontconfig instance
+ * \param family font fullname
+ * \param bold weight attribute
+ * \param italic italic attribute
+ * \return font set
+ */
+static FcFontSet *
+match_fullname(ASS_Library *lib, FCInstance *priv, const char *family,
+               unsigned bold, unsigned italic)
+{
+    FcFontSet *sets[2];
+    FcFontSet *result = FcFontSetCreate();
+    int nsets = 0;
+    int i, fi;
+
+    if ((sets[nsets] = FcConfigGetFonts(priv->config, FcSetSystem)))
+        nsets++;
+    if ((sets[nsets] = FcConfigGetFonts(priv->config, FcSetApplication)))
+        nsets++;
+
+    // Run over font sets and patterns and try to match against full name
+    for (i = 0; i < nsets; i++) {
+        FcFontSet *set = sets[i];
+        for (fi = 0; fi < set->nfont; fi++) {
+            FcPattern *pat = set->fonts[fi];
+            char *fullname;
+            int pi = 0, at;
+            FcBool ol;
+            while (FcPatternGetString(pat, FC_FULLNAME, pi++,
+                   (FcChar8 **) &fullname) == FcResultMatch) {
+                if (FcPatternGetBool(pat, FC_OUTLINE, 0, &ol) != FcResultMatch
+                    || ol != FcTrue)
+                    continue;
+                if (FcPatternGetInteger(pat, FC_SLANT, 0, &at) != FcResultMatch
+                    || at < italic)
+                    continue;
+                if (FcPatternGetInteger(pat, FC_WEIGHT, 0, &at) != FcResultMatch
+                    || at < bold)
+                    continue;
+                if (strcasecmp(fullname, family) == 0) {
+                    FcFontSetAdd(result, FcPatternDuplicate(pat));
+                    break;
+                }
+            }
+        }
+    }
+
+    return result;
+}
+
+/**
  * \brief Low-level font selection.
  * \param priv private data
  * \param family font family
@@ -62,7 +117,7 @@ struct fc_instance {
  * \param code: the character that should be present in the font, can be 0
  * \return font file path
 */
-static char *_select_font(ASS_Library *library, FCInstance *priv,
+static char *select_font(ASS_Library *library, FCInstance *priv,
                           const char *family, int treat_family_as_pattern,
                           unsigned bold, unsigned italic, int *index,
                           uint32_t code)
@@ -74,7 +129,7 @@ static char *_select_font(ASS_Library *library, FCInstance *priv,
     FcChar8 *r_family, *r_style, *r_file, *r_fullname;
     FcBool r_outline, r_embolden;
     FcCharSet *r_charset;
-    FcFontSet *fset = NULL;
+    FcFontSet *ffullname = NULL, *fsorted = NULL, *fset = NULL;
     int curf;
     char *retval = NULL;
     int family_cnt = 0;
@@ -126,10 +181,23 @@ static char *_select_font(ASS_Library *library, FCInstance *priv,
     if (!rc)
         goto error;
 
-    fset = FcFontSort(priv->config, pat, FcTrue, NULL, &result);
-    if (!fset)
+    fsorted = FcFontSort(priv->config, pat, FcTrue, NULL, &result);
+    ffullname = match_fullname(library, priv, family, bold, italic);
+    if (!fsorted || !ffullname)
         goto error;
 
+    fset = FcFontSetCreate();
+    for (curf = 0; curf < ffullname->nfont; ++curf) {
+        FcPattern *curp = ffullname->fonts[curf];
+        FcPatternReference(curp);
+        FcFontSetAdd(fset, curp);
+    }
+    for (curf = 0; curf < fsorted->nfont; ++curf) {
+        FcPattern *curp = fsorted->fonts[curf];
+        FcPatternReference(curp);
+        FcFontSetAdd(fset, curp);
+    }
+
     for (curf = 0; curf < fset->nfont; ++curf) {
         FcPattern *curp = fset->fonts[curf];
 
@@ -215,6 +283,10 @@ static char *_select_font(ASS_Library *library, FCInstance *priv,
         FcPatternDestroy(pat);
     if (rpat)
         FcPatternDestroy(rpat);
+    if (fsorted)
+        FcFontSetDestroy(fsorted);
+    if (ffullname)
+        FcFontSetDestroy(ffullname);
     if (fset)
         FcFontSetDestroy(fset);
     return retval;
@@ -244,11 +316,11 @@ char *fontconfig_select(ASS_Library *library, FCInstance *priv,
     }
     if (family && *family)
         res =
-            _select_font(library, priv, family, treat_family_as_pattern,
+            select_font(library, priv, family, treat_family_as_pattern,
                          bold, italic, index, code);
     if (!res && priv->family_default) {
         res =
-            _select_font(library, priv, priv->family_default, 0, bold,
+            select_font(library, priv, priv->family_default, 0, bold,
                          italic, index, code);
         if (res)
             ass_msg(library, MSGL_WARN, "fontconfig_select: Using default "
@@ -263,7 +335,7 @@ char *fontconfig_select(ASS_Library *library, FCInstance *priv,
                 res, *index);
     }
     if (!res) {
-        res = _select_font(library, priv, "Arial", 0, bold, italic,
+        res = select_font(library, priv, "Arial", 0, bold, italic,
                            index, code);
         if (res)
             ass_msg(library, MSGL_WARN, "fontconfig_select: Using 'Arial' "
@@ -283,8 +355,8 @@ char *fontconfig_select(ASS_Library *library, FCInstance *priv,
  * \param library library object
  * \param ftlibrary freetype library object
  * \param idx index of the processed font in library->fontdata
- * With FontConfig >= 2.4.2, builds a font pattern in memory via FT_New_Memory_Face/FcFreeTypeQueryFace.
- * With older FontConfig versions, save the font to ~/.mplayer/fonts.
+ *
+ * Builds a font pattern in memory via FT_New_Memory_Face/FcFreeTypeQueryFace.
 */
 static void process_fontdata(FCInstance *priv, ASS_Library *library,
                              FT_Library ftlibrary, int idx)
@@ -311,7 +383,7 @@ static void process_fontdata(FCInstance *priv, ASS_Library *library,
         num_faces = face->num_faces;
 
         pattern =
-            FcFreeTypeQueryFace(face, (unsigned char *) name, 0,
+            FcFreeTypeQueryFace(face, (unsigned char *) name, face_index,
                                 FcConfigGetBlanks(priv->config));
         if (!pattern) {
             ass_msg(library, MSGL_WARN, "%s failed", "FcFreeTypeQueryFace");
@@ -388,7 +460,7 @@ FCInstance *fontconfig_init(ASS_Library *library,
         process_fontdata(priv, library, ftlibrary, i);
 
     if (dir) {
-        ass_msg(library, MSGL_INFO, "Updating font cache");
+        ass_msg(library, MSGL_V, "Updating font cache");
 
         rc = FcConfigAppFontAddDir(priv->config, (const FcChar8 *) dir);
         if (!rc) {
index 0ccb5a2..654fa7f 100644 (file)
@@ -68,9 +68,15 @@ void update_font(ASS_Renderer *render_priv)
 {
     unsigned val;
     ASS_FontDesc desc;
-    desc.family = strdup(render_priv->state.family);
-    desc.treat_family_as_pattern =
-        render_priv->state.treat_family_as_pattern;
+    desc.treat_family_as_pattern = render_priv->state.treat_family_as_pattern;
+
+    if (render_priv->state.family[0] == '@') {
+        desc.vertical = 1;
+        desc.family = strdup(render_priv->state.family + 1);
+    } else {
+        desc.vertical = 0;
+        desc.family = strdup(render_priv->state.family);
+    }
 
     val = render_priv->state.bold;
     // 0 = normal, 1 = bold, >1 = exact weight
@@ -210,6 +216,7 @@ static char *parse_vector_clip(ASS_Renderer *render_priv, char *p)
     int res = 0;
     ASS_Drawing *drawing;
 
+    ass_drawing_free(render_priv->state.clip_drawing);
     render_priv->state.clip_drawing = ass_drawing_new(
         render_priv->fontconfig_priv,
         render_priv->state.font,
@@ -227,20 +234,6 @@ static char *parse_vector_clip(ASS_Renderer *render_priv, char *p)
     while (*p != ')' && *p != '}' && p != 0)
         ass_drawing_add_char(drawing, *p++);
     skipopt(')');
-    if (ass_drawing_parse(drawing, 1)) {
-        // We need to translate the clip according to screen borders
-        if (render_priv->settings.left_margin != 0 ||
-            render_priv->settings.top_margin != 0) {
-            FT_Vector trans = {
-                .x = int_to_d6(render_priv->settings.left_margin),
-                .y = -int_to_d6(render_priv->settings.top_margin),
-            };
-            FT_Outline_Translate(&drawing->glyph->outline, trans.x, trans.y);
-        }
-        ass_msg(render_priv->library, MSGL_DBG2,
-                "Parsed vector clip: scale %d, scales (%f, %f) string [%s]\n",
-                scale, drawing->scale_x, drawing->scale_y, drawing->text);
-    }
 
     return p;
 }
@@ -586,7 +579,7 @@ static char *parse_tag(ASS_Renderer *render_priv, char *p, double pwr)
         for (cnt = 0; cnt < 3; ++cnt) {
             if (*p == '\\')
                 break;
-            v[cnt] = strtod(p, &p);
+            mystrtod(&p, &v[cnt]);
             skip(',');
         }
         if (cnt == 3) {
@@ -836,7 +829,7 @@ void apply_transition_effects(ASS_Renderer *render_priv, ASS_Event *event)
     } else if (strncmp(event->Effect, "Scroll down;", 12) == 0) {
         render_priv->state.scroll_direction = SCROLL_TB;
     } else {
-        ass_msg(render_priv->library, MSGL_V,
+        ass_msg(render_priv->library, MSGL_DBG2,
                 "Unknown transition effect: '%s'", event->Effect);
         return;
     }
@@ -894,7 +887,7 @@ unsigned get_next_char(ASS_Renderer *render_priv, char **str)
                     break;
             } else if (*p != '\\')
                 ass_msg(render_priv->library, MSGL_V,
-                        "Unable to parse: '%s'", p);
+                        "Unable to parse: '%.30s'", p);
             if (*p == 0)
                 break;
         }
@@ -918,6 +911,14 @@ unsigned get_next_char(ASS_Renderer *render_priv, char **str)
             p += 2;
             *str = p;
             return NBSP;
+        } else if (p[1] == '{') {
+            p += 2;
+            *str = p;
+            return '{';
+        } else if (p[1] == '}') {
+            p += 2;
+            *str = p;
+            return '}';
         }
     }
     chr = ass_utf8_get_char((char **) &p);
index 6bc0c61..3e241c1 100644 (file)
 
 #include <assert.h>
 #include <math.h>
-#include <inttypes.h>
-#include <ft2build.h>
-#include FT_FREETYPE_H
-#include FT_STROKER_H
-#include FT_GLYPH_H
-#include FT_SYNTHESIS_H
-
-#include "ass.h"
-#include "ass_font.h"
-#include "ass_bitmap.h"
-#include "ass_cache.h"
-#include "ass_utils.h"
-#include "ass_fontconfig.h"
-#include "ass_library.h"
-#include "ass_drawing.h"
+
 #include "ass_render.h"
 #include "ass_parse.h"
 
 #define MAX_GLYPHS_INITIAL 1024
 #define MAX_LINES_INITIAL 64
 #define SUBPIXEL_MASK 63
-#define SUBPIXEL_ACCURACY 7    // d6 mask for subpixel accuracy adjustment
-#define GLYPH_CACHE_MAX 1000
-#define BITMAP_CACHE_MAX_SIZE 50 * 1048576
+#define SUBPIXEL_ACCURACY 7
 
 static void ass_lazy_track_init(ASS_Renderer *render_priv)
 {
@@ -119,27 +103,20 @@ ASS_Renderer *ass_renderer_init(ASS_Library *library)
 
     priv->text_info.max_glyphs = MAX_GLYPHS_INITIAL;
     priv->text_info.max_lines = MAX_LINES_INITIAL;
-    priv->text_info.glyphs =
-        calloc(MAX_GLYPHS_INITIAL, sizeof(GlyphInfo));
+    priv->text_info.glyphs = calloc(MAX_GLYPHS_INITIAL, sizeof(GlyphInfo));
     priv->text_info.lines = calloc(MAX_LINES_INITIAL, sizeof(LineInfo));
 
+    priv->settings.font_size_coeff = 1.;
+
   ass_init_exit:
     if (priv)
-        ass_msg(library, MSGL_INFO, "Init");
+        ass_msg(library, MSGL_V, "Init");
     else
         ass_msg(library, MSGL_ERR, "Init failed");
 
     return priv;
 }
 
-void ass_set_cache_limits(ASS_Renderer *render_priv, int glyph_max,
-                          int bitmap_max)
-{
-    render_priv->cache.glyph_max = glyph_max ? glyph_max : GLYPH_CACHE_MAX;
-    render_priv->cache.bitmap_max_size = bitmap_max ? 1048576 * bitmap_max :
-                                         BITMAP_CACHE_MAX_SIZE;
-}
-
 static void free_list_clear(ASS_Renderer *render_priv)
 {
     if (render_priv->free_head) {
@@ -154,8 +131,6 @@ static void free_list_clear(ASS_Renderer *render_priv)
     }
 }
 
-static void ass_free_images(ASS_Image *img);
-
 void ass_renderer_done(ASS_Renderer *render_priv)
 {
     ass_font_cache_done(render_priv->cache.font_cache);
@@ -196,21 +171,85 @@ static ASS_Image *my_draw_bitmap(unsigned char *bitmap, int bitmap_w,
                                  int bitmap_h, int stride, int dst_x,
                                  int dst_y, uint32_t color)
 {
-    ASS_Image *img = calloc(1, sizeof(ASS_Image));
+    ASS_Image *img = malloc(sizeof(ASS_Image));
 
-    img->w = bitmap_w;
-    img->h = bitmap_h;
-    img->stride = stride;
-    img->bitmap = bitmap;
-    img->color = color;
-    img->dst_x = dst_x;
-    img->dst_y = dst_y;
+    if (img) {
+        img->w = bitmap_w;
+        img->h = bitmap_h;
+        img->stride = stride;
+        img->bitmap = bitmap;
+        img->color = color;
+        img->dst_x = dst_x;
+        img->dst_y = dst_y;
+    }
 
     return img;
 }
 
-static double x2scr_pos(ASS_Renderer *render_priv, double x);
-static double y2scr_pos(ASS_Renderer *render_priv, double y);
+/**
+ * \brief Mapping between script and screen coordinates
+ */
+static double x2scr(ASS_Renderer *render_priv, double x)
+{
+    return x * render_priv->orig_width_nocrop / render_priv->font_scale_x /
+        render_priv->track->PlayResX +
+        FFMAX(render_priv->settings.left_margin, 0);
+}
+static double x2scr_pos(ASS_Renderer *render_priv, double x)
+{
+    return x * render_priv->orig_width / render_priv->font_scale_x / render_priv->track->PlayResX +
+        render_priv->settings.left_margin;
+}
+static double x2scr_scaled(ASS_Renderer *render_priv, double x)
+{
+    return x * render_priv->orig_width_nocrop /
+        render_priv->track->PlayResX +
+        FFMAX(render_priv->settings.left_margin, 0);
+}
+static double x2scr_pos_scaled(ASS_Renderer *render_priv, double x)
+{
+    return x * render_priv->orig_width / render_priv->track->PlayResX +
+        render_priv->settings.left_margin;
+}
+/**
+ * \brief Mapping between script and screen coordinates
+ */
+static double y2scr(ASS_Renderer *render_priv, double y)
+{
+    return y * render_priv->orig_height_nocrop /
+        render_priv->track->PlayResY +
+        FFMAX(render_priv->settings.top_margin, 0);
+}
+static double y2scr_pos(ASS_Renderer *render_priv, double y)
+{
+    return y * render_priv->orig_height / render_priv->track->PlayResY +
+        render_priv->settings.top_margin;
+}
+
+// the same for toptitles
+static double y2scr_top(ASS_Renderer *render_priv, double y)
+{
+    if (render_priv->settings.use_margins)
+        return y * render_priv->orig_height_nocrop /
+            render_priv->track->PlayResY;
+    else
+        return y * render_priv->orig_height_nocrop /
+            render_priv->track->PlayResY +
+            FFMAX(render_priv->settings.top_margin, 0);
+}
+// the same for subtitles
+static double y2scr_sub(ASS_Renderer *render_priv, double y)
+{
+    if (render_priv->settings.use_margins)
+        return y * render_priv->orig_height_nocrop /
+            render_priv->track->PlayResY +
+            FFMAX(render_priv->settings.top_margin, 0)
+            + FFMAX(render_priv->settings.bottom_margin, 0);
+    else
+        return y * render_priv->orig_height_nocrop /
+            render_priv->track->PlayResY +
+            FFMAX(render_priv->settings.top_margin, 0);
+}
 
 /*
  * \brief Convert bitmap glyphs into ASS_Image list with inverse clipping
@@ -238,9 +277,9 @@ static ASS_Image **render_glyph_i(ASS_Renderer *render_priv,
     dst_y += bm->top;
 
     // we still need to clip against screen boundaries
-    zx = x2scr_pos(render_priv, 0);
+    zx = x2scr_pos_scaled(render_priv, 0);
     zy = y2scr_pos(render_priv, 0);
-    sx = x2scr_pos(render_priv, render_priv->track->PlayResX);
+    sx = x2scr_pos_scaled(render_priv, render_priv->track->PlayResX);
     sy = y2scr_pos(render_priv, render_priv->track->PlayResY);
 
     x0 = 0;
@@ -295,6 +334,7 @@ static ASS_Image **render_glyph_i(ASS_Renderer *render_priv,
             img = my_draw_bitmap(bm->buffer + r[j].y0 * bm->w + r[j].x0,
                 lbrk - r[j].x0, r[j].y1 - r[j].y0,
                 bm->w, dst_x + r[j].x0, dst_y + r[j].y0, color);
+            if (!img) break;
             *tail = img;
             tail = &img->next;
         }
@@ -303,6 +343,7 @@ static ASS_Image **render_glyph_i(ASS_Renderer *render_priv,
             img = my_draw_bitmap(bm->buffer + r[j].y0 * bm->w + lbrk,
                 r[j].x1 - lbrk, r[j].y1 - r[j].y0,
                 bm->w, dst_x + lbrk, dst_y + r[j].y0, color2);
+            if (!img) break;
             *tail = img;
             tail = &img->next;
         }
@@ -384,6 +425,7 @@ render_glyph(ASS_Renderer *render_priv, Bitmap *bm, int dst_x, int dst_y,
         img = my_draw_bitmap(bm->buffer + bm->w * b_y0 + b_x0,
                              brk - b_x0, b_y1 - b_y0, bm->w,
                              dst_x + b_x0, dst_y + b_y0, color);
+        if (!img) return tail;
         *tail = img;
         tail = &img->next;
     }
@@ -393,6 +435,7 @@ render_glyph(ASS_Renderer *render_priv, Bitmap *bm, int dst_x, int dst_y,
         img = my_draw_bitmap(bm->buffer + bm->w * b_y0 + brk,
                              b_x1 - brk, b_y1 - b_y0, bm->w,
                              dst_x + brk, dst_y + b_y0, color2);
+        if (!img) return tail;
         *tail = img;
         tail = &img->next;
     }
@@ -464,7 +507,6 @@ render_overlap(ASS_Renderer *render_priv, ASS_Image **last_tail,
     cur_top = top - by;
 
     // Query cache
-    memset(&hk, 0, sizeof(hk));
     hk.a = (*last_tail)->bitmap;
     hk.b = (*tail)->bitmap;
     hk.aw = aw;
@@ -529,23 +571,73 @@ static void blend_vector_clip(ASS_Renderer *render_priv,
     FT_BitmapGlyph clip_bm;
     ASS_Image *cur;
     ASS_Drawing *drawing = render_priv->state.clip_drawing;
+    GlyphHashKey key;
+    GlyphHashValue *val;
     int error;
 
     if (!drawing)
         return;
 
-    // Rasterize it
-    FT_Glyph_Copy((FT_Glyph) drawing->glyph, &glyph);
-    error = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 1);
-    if (error) {
-        ass_msg(render_priv->library, MSGL_V,
-            "Clip vector rasterization failed: %d. Skipping.", error);
-        goto blend_vector_exit;
+    // 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);
+
+    if (val) {
+        clip_bm = (FT_BitmapGlyph) val->glyph;
+    } else {
+        GlyphHashValue v;
+
+        // Not found in cache, parse and rasterize it
+        glyph = (FT_Glyph) *ass_drawing_parse(drawing, 1);
+        if (!glyph) {
+            ass_msg(render_priv->library, MSGL_WARN,
+                    "Clip vector parsing failed. Skipping.");
+            goto blend_vector_error;
+        }
+
+        // We need to translate the clip according to screen borders
+        if (render_priv->settings.left_margin != 0 ||
+            render_priv->settings.top_margin != 0) {
+            FT_Vector trans = {
+                .x = int_to_d6(render_priv->settings.left_margin),
+                .y = -int_to_d6(render_priv->settings.top_margin),
+            };
+            FT_Outline_Translate(&drawing->glyph->outline,
+                                 trans.x, trans.y);
+        }
+
+        // Check glyph bounding box size
+        if (check_glyph_area(render_priv->library, glyph)) {
+            FT_Done_Glyph(glyph);
+            glyph = 0;
+            goto blend_vector_error;
+        }
+
+        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) {
+            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);
     }
-    clip_bm = (FT_BitmapGlyph) glyph;
-    clip_bm->top = -clip_bm->top;
 
-    assert(clip_bm->bitmap.pitch >= 0);
+    if (!clip_bm) goto blend_vector_exit;
 
     // Iterate through bitmaps and blend/clip them
     for (cur = head; cur; cur = cur->next) {
@@ -563,7 +655,7 @@ static void blend_vector_clip(ASS_Renderer *render_priv,
         ah = cur->h;
         as = cur->stride;
         bx = clip_bm->left;
-        by = clip_bm->top;
+        by = -clip_bm->top;
         bw = clip_bm->bitmap.width;
         bh = clip_bm->bitmap.rows;
         bs = clip_bm->bitmap.pitch;
@@ -589,6 +681,7 @@ static void blend_vector_clip(ASS_Renderer *render_priv,
 
             // Allocate new buffer and add to free list
             nbuffer = malloc(as * ah);
+            if (!nbuffer) goto blend_vector_exit;
             free_list_add(render_priv, nbuffer);
 
             // Blend together
@@ -609,6 +702,7 @@ static void blend_vector_clip(ASS_Renderer *render_priv,
 
             // Allocate new buffer and add to free list
             nbuffer = calloc(as, ah);
+            if (!nbuffer) goto blend_vector_exit;
             free_list_add(render_priv, nbuffer);
 
             // Blend together
@@ -622,8 +716,6 @@ static void blend_vector_clip(ASS_Renderer *render_priv,
         cur->bitmap = nbuffer;
     }
 
-    // Free clip vector and its bitmap, we don't need it anymore
-    FT_Done_Glyph(glyph);
 blend_vector_exit:
     ass_drawing_free(render_priv->state.clip_drawing);
     render_priv->state.clip_drawing = 0;
@@ -633,8 +725,7 @@ blend_vector_exit:
  * \brief Convert TextInfo struct to ASS_Image list
  * Splits glyphs in halves when needed (for \kf karaoke).
  */
-static ASS_Image *render_text(ASS_Renderer *render_priv, int dst_x,
-                                int dst_y)
+static ASS_Image *render_text(ASS_Renderer *render_priv, int dst_x, int dst_y)
 {
     int pen_x, pen_y;
     int i;
@@ -731,62 +822,6 @@ static ASS_Image *render_text(ASS_Renderer *render_priv, int dst_x,
     return head;
 }
 
-/**
- * \brief Mapping between script and screen coordinates
- */
-static double x2scr(ASS_Renderer *render_priv, double x)
-{
-    return x * render_priv->orig_width_nocrop /
-        render_priv->track->PlayResX +
-        FFMAX(render_priv->settings.left_margin, 0);
-}
-static double x2scr_pos(ASS_Renderer *render_priv, double x)
-{
-    return x * render_priv->orig_width / render_priv->track->PlayResX +
-        render_priv->settings.left_margin;
-}
-
-/**
- * \brief Mapping between script and screen coordinates
- */
-static double y2scr(ASS_Renderer *render_priv, double y)
-{
-    return y * render_priv->orig_height_nocrop /
-        render_priv->track->PlayResY +
-        FFMAX(render_priv->settings.top_margin, 0);
-}
-static double y2scr_pos(ASS_Renderer *render_priv, double y)
-{
-    return y * render_priv->orig_height / render_priv->track->PlayResY +
-        render_priv->settings.top_margin;
-}
-
-// the same for toptitles
-static double y2scr_top(ASS_Renderer *render_priv, double y)
-{
-    if (render_priv->settings.use_margins)
-        return y * render_priv->orig_height_nocrop /
-            render_priv->track->PlayResY;
-    else
-        return y * render_priv->orig_height_nocrop /
-            render_priv->track->PlayResY +
-            FFMAX(render_priv->settings.top_margin, 0);
-}
-
-// the same for subtitles
-static double y2scr_sub(ASS_Renderer *render_priv, double y)
-{
-    if (render_priv->settings.use_margins)
-        return y * render_priv->orig_height_nocrop /
-            render_priv->track->PlayResY +
-            FFMAX(render_priv->settings.top_margin,
-                  0) + FFMAX(render_priv->settings.bottom_margin, 0);
-    else
-        return y * render_priv->orig_height_nocrop /
-            render_priv->track->PlayResY +
-            FFMAX(render_priv->settings.top_margin, 0);
-}
-
 static void compute_string_bbox(TextInfo *info, DBBox *bbox)
 {
     int i;
@@ -845,8 +880,6 @@ void reset_render_context(ASS_Renderer *render_priv)
     render_priv->state.frz = M_PI * render_priv->state.style->Angle / 180.;
     render_priv->state.fax = render_priv->state.fay = 0.;
     render_priv->state.wrap_style = render_priv->track->WrapStyle;
-
-    // FIXME: does not reset unsupported attributes.
 }
 
 /**
@@ -878,11 +911,10 @@ init_render_context(ASS_Renderer *render_priv, ASS_Event *event)
     render_priv->state.effect_type = EF_NONE;
     render_priv->state.effect_timing = 0;
     render_priv->state.effect_skip_timing = 0;
-    render_priv->state.drawing =
-        ass_drawing_new(render_priv->fontconfig_priv,
-                        render_priv->state.font,
-                        render_priv->settings.hinting,
-                        render_priv->ftlibrary);
+    render_priv->state.drawing = ass_drawing_new(render_priv->fontconfig_priv,
+                                                 render_priv->state.font,
+                                                 render_priv->settings.hinting,
+                                                 render_priv->ftlibrary);
 
     apply_transition_effects(render_priv, event);
 }
@@ -896,88 +928,6 @@ static void free_render_context(ASS_Renderer *render_priv)
     render_priv->state.drawing = NULL;
 }
 
-// Calculate the cbox of a series of points
-static void
-get_contour_cbox(FT_BBox *box, FT_Vector *points, int start, int end)
-{
-    box->xMin = box->yMin = INT_MAX;
-    box->xMax = box->yMax = INT_MIN;
-    int i;
-
-    for (i = start; i < end; i++) {
-        box->xMin = (points[i].x < box->xMin) ? points[i].x : box->xMin;
-        box->xMax = (points[i].x > box->xMax) ? points[i].x : box->xMax;
-        box->yMin = (points[i].y < box->yMin) ? points[i].y : box->yMin;
-        box->yMax = (points[i].y > box->yMax) ? points[i].y : box->yMax;
-    }
-}
-
-/**
- * \brief Fix-up stroker result for huge borders by removing the contours from
- * the outline that are harmful.
-*/
-static void fix_freetype_stroker(FT_OutlineGlyph glyph, int border_x,
-                                 int border_y)
-{
-    int nc = glyph->outline.n_contours;
-    int begin, stop;
-    char modified = 0;
-    char *valid_cont;
-    int start = 0;
-    int end = -1;
-    FT_BBox *boxes = calloc(nc, sizeof(FT_BBox));
-    int i, j;
-
-    // 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);
-    }
-
-    // if a) contour's cbox is contained in another contours cbox
-    //    b) contour's height or width is smaller than the border*2
-    // the contour can be safely removed.
-    valid_cont = calloc(1, nc);
-    for (i = 0; i < nc; i++) {
-        valid_cont[i] = 1;
-        for (j = 0; j < nc; j++) {
-            if (i == j)
-                continue;
-            if (boxes[i].xMin >= boxes[j].xMin &&
-                boxes[i].xMax <= boxes[j].xMax &&
-                boxes[i].yMin >= boxes[j].yMin &&
-                boxes[i].yMax <= boxes[j].yMax) {
-                int width = boxes[i].xMax - boxes[i].xMin;
-                int height = boxes[i].yMax - boxes[i].yMin;
-                if (width < border_x * 2 || height < border_y * 2) {
-                    valid_cont[i] = 0;
-                    modified = 1;
-                    break;
-                }
-            }
-        }
-    }
-
-    // Zero-out contours that can be removed; much simpler than copying
-    if (modified) {
-        for (i = 0; i < nc; i++) {
-            if (valid_cont[i])
-                continue;
-            begin = (i == 0) ? 0 : glyph->outline.contours[i - 1] + 1;
-            stop = glyph->outline.contours[i];
-            for (j = begin; j <= stop; j++) {
-                glyph->outline.points[j].x = 0;
-                glyph->outline.points[j].y = 0;
-                glyph->outline.tags[j] = 0;
-            }
-        }
-    }
-
-    free(boxes);
-    free(valid_cont);
-}
-
 /*
  * Replace the outline of a glyph by a contour which makes up a simple
  * opaque rectangle.
@@ -989,8 +939,7 @@ static void draw_opaque_box(ASS_Renderer *render_priv, uint32_t ch,
     int i;
     int adv = d16_to_d6(glyph->advance.x);
     double scale_y = render_priv->state.scale_y;
-    double scale_x = render_priv->state.scale_x
-                     * render_priv->font_scale_x;
+    double scale_x = render_priv->state.scale_x;
     FT_OutlineGlyph og = (FT_OutlineGlyph) glyph;
     FT_Outline *ol;
 
@@ -1080,6 +1029,38 @@ static void stroke_outline_glyph(ASS_Renderer *render_priv,
 }
 
 /**
+ * \brief Prepare glyph hash
+ */
+static void
+fill_glyph_hash(ASS_Renderer *priv, GlyphHashKey *key,
+                ASS_Drawing *drawing, uint32_t ch)
+{
+    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;
+        key->border_style = priv->state.style->BorderStyle;
+        key->drawing_hash = drawing->hash;
+        // not very clean, but works
+        key->size = drawing->scale;
+        key->ch = -1;
+    } 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;
+        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
@@ -1094,35 +1075,15 @@ get_outline_glyph(ASS_Renderer *render_priv, int symbol, GlyphInfo *info,
 {
     GlyphHashValue *val;
     GlyphHashKey key;
-    memset(&key, 0, sizeof(key));
 
-    if (drawing->hash) {
-        key.scale_x = double_to_d16(render_priv->state.scale_x);
-        key.scale_y = double_to_d16(render_priv->state.scale_y);
-        key.outline.x = render_priv->state.border_x * 0xFFFF;
-        key.outline.y = render_priv->state.border_y * 0xFFFF;
-        key.border_style = render_priv->state.style->BorderStyle;
-        key.drawing_hash = drawing->hash;
-    } else {
-        key.font = render_priv->state.font;
-        key.size = render_priv->state.font_size;
-        key.ch = symbol;
-        key.bold = render_priv->state.bold;
-        key.italic = render_priv->state.italic;
-        key.scale_x = double_to_d16(render_priv->state.scale_x);
-        key.scale_y = double_to_d16(render_priv->state.scale_y);
-        key.outline.x = render_priv->state.border_x * 0xFFFF;
-        key.outline.y = render_priv->state.border_y * 0xFFFF;
-        key.flags = render_priv->state.flags;
-        key.border_style = render_priv->state.style->BorderStyle;
-    }
+    memset(&key, 0, sizeof(key));
     memset(info, 0, sizeof(GlyphInfo));
 
+    fill_glyph_hash(render_priv, &key, drawing, symbol);
     val = cache_find_glyph(render_priv->cache.glyph_cache, &key);
     if (val) {
-        FT_Glyph_Copy(val->glyph, &info->glyph);
-        if (val->outline_glyph)
-            FT_Glyph_Copy(val->outline_glyph, &info->outline_glyph);
+        info->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;
@@ -1135,7 +1096,7 @@ get_outline_glyph(ASS_Renderer *render_priv, int symbol, GlyphInfo *info,
         if (drawing->hash) {
             if(!ass_drawing_parse(drawing, 0))
                 return;
-            FT_Glyph_Copy((FT_Glyph) drawing->glyph, &info->glyph);
+            info->glyph = (FT_Glyph) drawing->glyph;
         } else {
             info->glyph =
                 ass_font_get_glyph(render_priv->fontconfig_priv,
@@ -1145,6 +1106,7 @@ get_outline_glyph(ASS_Renderer *render_priv, int symbol, GlyphInfo *info,
         }
         if (!info->glyph)
             return;
+
         info->advance.x = d16_to_d6(info->glyph->advance.x);
         info->advance.y = d16_to_d6(info->glyph->advance.y);
         FT_Glyph_Get_CBox(info->glyph, FT_GLYPH_BBOX_SUBPIXELS, &info->bbox);
@@ -1158,8 +1120,9 @@ get_outline_glyph(ASS_Renderer *render_priv, int symbol, GlyphInfo *info,
                                          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) {
+        } else if ((render_priv->state.border_x > 0
+                    || render_priv->state.border_y > 0)
+                   && key.scale_x && key.scale_y) {
 
             FT_Glyph_Copy(info->glyph, &info->outline_glyph);
             stroke_outline_glyph(render_priv,
@@ -1171,9 +1134,8 @@ get_outline_glyph(ASS_Renderer *render_priv, int symbol, GlyphInfo *info,
         }
 
         memset(&v, 0, sizeof(v));
-        FT_Glyph_Copy(info->glyph, &v.glyph);
-        if (info->outline_glyph)
-            FT_Glyph_Copy(info->outline_glyph, &v.outline_glyph);
+        v.glyph = info->glyph;
+        v.outline_glyph = info->outline_glyph;
         v.advance = info->advance;
         v.bbox_scaled = info->bbox;
         if (drawing->hash) {
@@ -1184,10 +1146,81 @@ get_outline_glyph(ASS_Renderer *render_priv, int symbol, GlyphInfo *info,
     }
 }
 
-static void transform_3d(FT_Vector shift, FT_Glyph *glyph,
-                         FT_Glyph *glyph2, double frx, double fry,
-                         double frz, double fax, double fay, double scale,
-                         int yshift);
+/**
+ * \brief Apply transformation to outline points of a glyph
+ * Applies rotations given by frx, fry and frz and projects the points back
+ * onto the screen plane.
+ */
+static void
+transform_3d_points(FT_Vector shift, FT_Glyph glyph, double frx, double fry,
+                    double frz, double fax, double fay, double scale,
+                    int yshift)
+{
+    double sx = sin(frx);
+    double sy = sin(fry);
+    double sz = sin(frz);
+    double cx = cos(frx);
+    double cy = cos(fry);
+    double cz = cos(frz);
+    FT_Outline *outline = &((FT_OutlineGlyph) glyph)->outline;
+    FT_Vector *p = outline->points;
+    double x, y, z, xx, yy, zz;
+    int i, dist;
+
+    dist = 20000 * scale;
+    for (i = 0; i < outline->n_points; i++) {
+        x = (double) p[i].x + shift.x + (fax * (yshift - p[i].y));
+        y = (double) p[i].y + shift.y + (-fay * p[i].x);
+        z = 0.;
+
+        xx = x * cz + y * sz;
+        yy = -(x * sz - y * cz);
+        zz = z;
+
+        x = xx;
+        y = yy * cx + zz * sx;
+        z = yy * sx - zz * cx;
+
+        xx = x * cy + z * sy;
+        yy = y;
+        zz = x * sy - z * cy;
+
+        zz = FFMAX(zz, 1000 - dist);
+
+        x = (xx * dist) / (zz + dist);
+        y = (yy * dist) / (zz + dist);
+        p[i].x = x - shift.x + 0.5;
+        p[i].y = y - shift.y + 0.5;
+    }
+}
+
+/**
+ * \brief Apply 3d transformation to several objects
+ * \param shift FreeType vector
+ * \param glyph FreeType glyph
+ * \param glyph2 FreeType glyph
+ * \param frx x-axis rotation angle
+ * \param fry y-axis rotation angle
+ * \param frz z-axis rotation angle
+ * Rotates both glyphs by frx, fry and frz. Shift vector is added before rotation and subtracted after it.
+ */
+static void
+transform_3d(FT_Vector shift, FT_Glyph *glyph, FT_Glyph *glyph2,
+             double frx, double fry, double frz, 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,
+                                fax, fay, scale, yshift);
+
+        if (glyph2 && *glyph2)
+            transform_3d_points(shift, *glyph2, frx, fry, frz,
+                                fax, fay, scale, yshift);
+    }
+}
 
 /**
  * \brief Get bitmaps for a glyph
@@ -1217,38 +1250,48 @@ get_bitmap_glyph(ASS_Renderer *render_priv, GlyphInfo *info)
         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_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 = info->hash_key.shift_x;
-            shift.y = info->hash_key.shift_y;
-            fax_scaled = info->fax * render_priv->font_scale_x *
+            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, &info->glyph, &info->outline_glyph,
+            transform_3d(shift, &glyph, &outline,
                          info->frx, info->fry, info->frz, fax_scaled,
                          fay_scaled, render_priv->font_scale, info->asc);
 
-            // subpixel shift
-            if (info->glyph)
-                FT_Outline_Translate(
-                    &((FT_OutlineGlyph) info->glyph)->outline,
-                    info->hash_key.advance.x,
-                    -info->hash_key.advance.y);
-            if (info->outline_glyph)
-                FT_Outline_Translate(
-                    &((FT_OutlineGlyph) info->outline_glyph)->outline,
-                    info->hash_key.advance.x,
-                    -info->hash_key.advance.y);
+            // 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,
-                                    info->glyph, info->outline_glyph,
+                                    glyph, outline,
                                     &info->bm, &info->bm_o,
                                     &info->bm_s, info->be,
                                     info->blur * render_priv->border_scale,
-                                    info->hash_key.shadow_offset,
-                                    info->hash_key.border_style);
+                                    key->shadow_offset, key->border_style);
             if (error)
                 info->symbol = 0;
 
@@ -1256,15 +1299,12 @@ get_bitmap_glyph(ASS_Renderer *render_priv, GlyphInfo *info)
             hash_val.bm_o = info->bm_o;
             hash_val.bm = info->bm;
             hash_val.bm_s = info->bm_s;
-            cache_add_bitmap(render_priv->cache.bitmap_cache,
-                             &(info->hash_key), &hash_val);
+            cache_add_bitmap(render_priv->cache.bitmap_cache, key, &hash_val);
+
+            FT_Done_Glyph(glyph);
+            FT_Done_Glyph(outline);
         }
     }
-    // deallocate glyphs
-    if (info->glyph)
-        FT_Done_Glyph(info->glyph);
-    if (info->outline_glyph)
-        FT_Done_Glyph(info->outline_glyph);
 }
 
 /**
@@ -1377,7 +1417,7 @@ static void trim_whitespace(ASS_Renderer *render_priv)
  * the difference in lengths between this two lines.
  * The result may not be optimal, but usually is good enough.
  *
- * FIXME: implement style 0 and 3 correctly, add support for style 1
+ * FIXME: implement style 0 and 3 correctly
  */
 static void
 wrap_lines_smart(ASS_Renderer *render_priv, double max_text_width)
@@ -1646,82 +1686,43 @@ static void get_base_point(DBBox *bbox, int alignment, double *bx, double *by)
 }
 
 /**
- * \brief Apply transformation to outline points of a glyph
- * Applies rotations given by frx, fry and frz and projects the points back
- * onto the screen plane.
- */
-static void
-transform_3d_points(FT_Vector shift, FT_Glyph glyph, double frx, double fry,
-                    double frz, double fax, double fay, double scale,
-                    int yshift)
-{
-    double sx = sin(frx);
-    double sy = sin(fry);
-    double sz = sin(frz);
-    double cx = cos(frx);
-    double cy = cos(fry);
-    double cz = cos(frz);
-    FT_Outline *outline = &((FT_OutlineGlyph) glyph)->outline;
-    FT_Vector *p = outline->points;
-    double x, y, z, xx, yy, zz;
-    int i, dist;
-
-    dist = 20000 * scale;
-    for (i = 0; i < outline->n_points; i++) {
-        x = (double) p[i].x + shift.x + (fax * (yshift - p[i].y));
-        y = (double) p[i].y + shift.y + (-fay * p[i].x);
-        z = 0.;
-
-        xx = x * cz + y * sz;
-        yy = -(x * sz - y * cz);
-        zz = z;
-
-        x = xx;
-        y = yy * cx + zz * sx;
-        z = yy * sx - zz * cx;
-
-        xx = x * cy + z * sy;
-        yy = y;
-        zz = x * sy - z * cy;
-
-        zz = FFMAX(zz, 1000 - dist);
-
-        x = (xx * dist) / (zz + dist);
-        y = (yy * dist) / (zz + dist);
-        p[i].x = x - shift.x + 0.5;
-        p[i].y = y - shift.y + 0.5;
-    }
-}
-
-/**
- * \brief Apply 3d transformation to several objects
- * \param shift FreeType vector
- * \param glyph FreeType glyph
- * \param glyph2 FreeType glyph
- * \param frx x-axis rotation angle
- * \param fry y-axis rotation angle
- * \param frz z-axis rotation angle
- * Rotates both glyphs by frx, fry and frz. Shift vector is added before rotation and subtracted after it.
+ * Prepare bitmap hash key of a glyph
  */
 static void
-transform_3d(FT_Vector shift, FT_Glyph *glyph, FT_Glyph *glyph2,
-             double frx, double fry, double frz, double fax, double fay,
-             double scale, int yshift)
+fill_bitmap_hash(ASS_Renderer *priv, BitmapHashKey *hash_key,
+                 ASS_Drawing *drawing, FT_Vector pen, uint32_t code)
 {
-    frx = -frx;
-    frz = -frz;
-    if (frx != 0. || fry != 0. || frz != 0. || fax != 0. || fay != 0.) {
-        if (glyph && *glyph)
-            transform_3d_points(shift, *glyph, frx, fry, frz,
-                                fax, fay, scale, yshift);
-
-        if (glyph2 && *glyph2)
-            transform_3d_points(shift, *glyph2, frx, fry, frz,
-                                fax, fay, scale, yshift);
-    }
+    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->shadow_offset.x = double_to_d6(
+            priv->state.shadow_x * priv->border_scale -
+            (int) (priv->state.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;
 }
 
-
 /**
  * \brief Main ass rendering function, glues everything together
  * \param event event to render
@@ -1746,6 +1747,7 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event,
     double device_x = 0;
     double device_y = 0;
     TextInfo *text_info = &render_priv->text_info;
+    GlyphInfo *glyphs = render_priv->text_info.glyphs;
     ASS_Drawing *drawing;
 
     if (event->Style >= render_priv->track->n_styles) {
@@ -1779,7 +1781,6 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event,
         // Parse drawing
         if (drawing->i) {
             drawing->scale_x = render_priv->state.scale_x *
-                                     render_priv->font_scale_x *
                                      render_priv->font_scale;
             drawing->scale_y = render_priv->state.scale_y *
                                      render_priv->font_scale;
@@ -1800,7 +1801,7 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event,
         if (text_info->length >= text_info->max_glyphs) {
             // Raise maximum number of glyphs
             text_info->max_glyphs *= 2;
-            text_info->glyphs =
+            text_info->glyphs = glyphs =
                 realloc(text_info->glyphs,
                         sizeof(GlyphInfo) * text_info->max_glyphs);
         }
@@ -1811,139 +1812,82 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event,
             delta =
                 ass_font_get_kerning(render_priv->state.font, previous,
                                      code);
-            pen.x += delta.x * render_priv->state.scale_x
-                     * render_priv->font_scale_x;
-            pen.y += delta.y * render_priv->state.scale_y
-                     * render_priv->font_scale_x;
+            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->font_scale_x,
+                               render_priv->state.scale_x,
                                render_priv->state.scale_y, NULL);
 
         get_outline_glyph(render_priv, code,
-                          text_info->glyphs + text_info->length, drawing);
+                          glyphs + text_info->length, drawing);
 
         // Add additional space after italic to non-italic style changes
         if (text_info->length &&
-            text_info->glyphs[text_info->length - 1].hash_key.italic &&
+            glyphs[text_info->length - 1].hash_key.italic &&
             !render_priv->state.italic) {
             int back = text_info->length - 1;
-            GlyphInfo *og = &text_info->glyphs[back];
+            GlyphInfo *og = &glyphs[back];
             while (back && og->bbox.xMax - og->bbox.xMin == 0
                    && og->hash_key.italic)
-                og = &text_info->glyphs[--back];
+                og = &glyphs[--back];
             if (og->bbox.xMax > og->advance.x) {
                 // The FreeType oblique slants by 6/16
                 pen.x += og->bbox.yMax * 0.375;
             }
         }
 
-        text_info->glyphs[text_info->length].pos.x = pen.x;
-        text_info->glyphs[text_info->length].pos.y = pen.y;
+        glyphs[text_info->length].pos.x = pen.x;
+        glyphs[text_info->length].pos.y = pen.y;
 
-        pen.x += text_info->glyphs[text_info->length].advance.x;
+        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 += text_info->glyphs[text_info->length].advance.y;
+        pen.y += glyphs[text_info->length].advance.y;
         pen.y += (render_priv->state.fay * render_priv->state.scale_y) *
-                 text_info->glyphs[text_info->length].advance.x;
+                 glyphs[text_info->length].advance.x;
 
         previous = code;
 
-        text_info->glyphs[text_info->length].symbol = code;
-        text_info->glyphs[text_info->length].linebreak = 0;
+        glyphs[text_info->length].symbol = code;
+        glyphs[text_info->length].linebreak = 0;
         for (i = 0; i < 4; ++i) {
             uint32_t clr = render_priv->state.c[i];
             change_alpha(&clr,
                          mult_alpha(_a(clr), render_priv->state.fade), 1.);
-            text_info->glyphs[text_info->length].c[i] = clr;
+            glyphs[text_info->length].c[i] = clr;
         }
-        text_info->glyphs[text_info->length].effect_type =
-            render_priv->state.effect_type;
-        text_info->glyphs[text_info->length].effect_timing =
+        glyphs[text_info->length].effect_type = render_priv->state.effect_type;
+        glyphs[text_info->length].effect_timing =
             render_priv->state.effect_timing;
-        text_info->glyphs[text_info->length].effect_skip_timing =
+        glyphs[text_info->length].effect_skip_timing =
             render_priv->state.effect_skip_timing;
-        text_info->glyphs[text_info->length].be = render_priv->state.be;
-        text_info->glyphs[text_info->length].blur = render_priv->state.blur;
-        text_info->glyphs[text_info->length].shadow_x =
-            render_priv->state.shadow_x;
-        text_info->glyphs[text_info->length].shadow_y =
-            render_priv->state.shadow_y;
-        text_info->glyphs[text_info->length].frx = render_priv->state.frx;
-        text_info->glyphs[text_info->length].fry = render_priv->state.fry;
-        text_info->glyphs[text_info->length].frz = render_priv->state.frz;
-        text_info->glyphs[text_info->length].fax = render_priv->state.fax;
-        text_info->glyphs[text_info->length].fay = render_priv->state.fay;
+        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].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) {
-            text_info->glyphs[text_info->length].asc = drawing->asc;
-            text_info->glyphs[text_info->length].desc = drawing->desc;
+            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,
-                                  &text_info->glyphs[text_info->length].asc,
-                                  &text_info->glyphs[text_info->length].desc);
+                                  &glyphs[text_info->length].asc,
+                                  &glyphs[text_info->length].desc);
 
-            text_info->glyphs[text_info->length].asc *=
-                render_priv->state.scale_y;
-            text_info->glyphs[text_info->length].desc *=
-                render_priv->state.scale_y;
+            glyphs[text_info->length].asc *= render_priv->state.scale_y;
+            glyphs[text_info->length].desc *= render_priv->state.scale_y;
         }
 
-        // fill bitmap_hash_key
-        if (!drawing->hash) {
-            text_info->glyphs[text_info->length].hash_key.font =
-                render_priv->state.font;
-            text_info->glyphs[text_info->length].hash_key.size =
-                render_priv->state.font_size;
-            text_info->glyphs[text_info->length].hash_key.bold =
-                render_priv->state.bold;
-            text_info->glyphs[text_info->length].hash_key.italic =
-                render_priv->state.italic;
-        } else
-            text_info->glyphs[text_info->length].hash_key.drawing_hash =
-                drawing->hash;
-        text_info->glyphs[text_info->length].hash_key.ch = code;
-        text_info->glyphs[text_info->length].hash_key.outline.x =
-            double_to_d16(render_priv->state.border_x);
-        text_info->glyphs[text_info->length].hash_key.outline.y =
-            double_to_d16(render_priv->state.border_y);
-        text_info->glyphs[text_info->length].hash_key.scale_x =
-            double_to_d16(render_priv->state.scale_x);
-        text_info->glyphs[text_info->length].hash_key.scale_y =
-            double_to_d16(render_priv->state.scale_y);
-        text_info->glyphs[text_info->length].hash_key.frx =
-            rot_key(render_priv->state.frx);
-        text_info->glyphs[text_info->length].hash_key.fry =
-            rot_key(render_priv->state.fry);
-        text_info->glyphs[text_info->length].hash_key.frz =
-            rot_key(render_priv->state.frz);
-        text_info->glyphs[text_info->length].hash_key.fax =
-            double_to_d16(render_priv->state.fax);
-        text_info->glyphs[text_info->length].hash_key.fay =
-            double_to_d16(render_priv->state.fay);
-        text_info->glyphs[text_info->length].hash_key.advance.x = pen.x;
-        text_info->glyphs[text_info->length].hash_key.advance.y = pen.y;
-        text_info->glyphs[text_info->length].hash_key.be =
-            render_priv->state.be;
-        text_info->glyphs[text_info->length].hash_key.blur =
-            render_priv->state.blur;
-        text_info->glyphs[text_info->length].hash_key.border_style =
-            render_priv->state.style->BorderStyle;
-        text_info->glyphs[text_info->length].hash_key.shadow_offset.x =
-            double_to_d6(
-                render_priv->state.shadow_x * render_priv->border_scale -
-                (int) (render_priv->state.shadow_x *
-                render_priv->border_scale));
-        text_info->glyphs[text_info->length].hash_key.shadow_offset.y =
-            double_to_d6(
-                render_priv->state.shadow_y * render_priv->border_scale -
-                (int) (render_priv->state.shadow_y *
-                render_priv->border_scale));
-        text_info->glyphs[text_info->length].hash_key.flags =
-            render_priv->state.flags;
+        // fill bitmap hash
+        fill_bitmap_hash(render_priv, &glyphs[text_info->length].hash_key,
+                         drawing, pen, code);
 
         text_info->length++;
 
@@ -1967,6 +1911,7 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event,
         free_render_context(render_priv);
         return 1;
     }
+
     // depends on glyph x coordinates being monotonous, so it should be done before line wrap
     process_karaoke_effects(render_priv);
 
@@ -1976,14 +1921,11 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event,
     valign = alignment & 12;
 
     MarginL =
-        (event->MarginL) ? event->MarginL : render_priv->state.style->
-        MarginL;
+        (event->MarginL) ? event->MarginL : render_priv->state.style->MarginL;
     MarginR =
-        (event->MarginR) ? event->MarginR : render_priv->state.style->
-        MarginR;
+        (event->MarginR) ? event->MarginR : render_priv->state.style->MarginR;
     MarginV =
-        (event->MarginV) ? event->MarginV : render_priv->state.style->
-        MarginV;
+        (event->MarginV) ? event->MarginV : render_priv->state.style->MarginV;
 
     if (render_priv->state.evt_type != EVENT_HSCROLL) {
         double max_text_width;
@@ -2001,11 +1943,11 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event,
         last_break = -1;
         for (i = 1; i < text_info->length + 1; ++i) {   // (text_info->length + 1) is the end of the last line
             if ((i == text_info->length)
-                || text_info->glyphs[i].linebreak) {
+                || glyphs[i].linebreak) {
                 double width, shift = 0;
                 GlyphInfo *first_glyph =
-                    text_info->glyphs + last_break + 1;
-                GlyphInfo *last_glyph = text_info->glyphs + i - 1;
+                    glyphs + last_break + 1;
+                GlyphInfo *last_glyph = glyphs + i - 1;
 
                 while (first_glyph < last_glyph && first_glyph->skip)
                     first_glyph++;
@@ -2027,7 +1969,7 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event,
                     shift = (max_text_width - width) / 2.0;
                 }
                 for (j = last_break + 1; j < i; ++j) {
-                    text_info->glyphs[j].pos.x += double_to_d6(shift);
+                    glyphs[j].pos.x += double_to_d6(shift);
                 }
                 last_break = i - 1;
             }
@@ -2057,6 +1999,7 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event,
                       render_priv->state.scroll_shift) - (bbox.xMax -
                                                           bbox.xMin);
     }
+
     // y coordinate for everything except positioned events
     if (render_priv->state.evt_type == EVENT_NORMAL ||
         render_priv->state.evt_type == EVENT_HSCROLL) {
@@ -2072,7 +2015,7 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event,
             double scr_y;
             if (valign != VALIGN_SUB)
                 ass_msg(render_priv->library, MSGL_V,
-                       "Invalid valign, supposing 0 (subtitle)");
+                       "Invalid valign, assuming 0 (subtitle)");
             scr_y =
                 y2scr_sub(render_priv,
                           render_priv->track->PlayResY - MarginV);
@@ -2093,6 +2036,7 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event,
                       render_priv->state.clip_y1 -
                       render_priv->state.scroll_shift);
     }
+
     // positioned events are totally different
     if (render_priv->state.evt_type == EVENT_POSITIONED) {
         double base_x = 0;
@@ -2105,14 +2049,15 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event,
         device_y =
             y2scr_pos(render_priv, render_priv->state.pos_y) - base_y;
     }
+
     // fix clip coordinates (they depend on alignment)
     if (render_priv->state.evt_type == EVENT_NORMAL ||
         render_priv->state.evt_type == EVENT_HSCROLL ||
         render_priv->state.evt_type == EVENT_VSCROLL) {
         render_priv->state.clip_x0 =
-            x2scr(render_priv, render_priv->state.clip_x0);
+            x2scr_scaled(render_priv, render_priv->state.clip_x0);
         render_priv->state.clip_x1 =
-            x2scr(render_priv, render_priv->state.clip_x1);
+            x2scr_scaled(render_priv, render_priv->state.clip_x1);
         if (valign == VALIGN_TOP) {
             render_priv->state.clip_y0 =
                 y2scr_top(render_priv, render_priv->state.clip_y0);
@@ -2131,14 +2076,15 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event,
         }
     } else if (render_priv->state.evt_type == EVENT_POSITIONED) {
         render_priv->state.clip_x0 =
-            x2scr_pos(render_priv, render_priv->state.clip_x0);
+            x2scr_pos_scaled(render_priv, render_priv->state.clip_x0);
         render_priv->state.clip_x1 =
-            x2scr_pos(render_priv, render_priv->state.clip_x1);
+            x2scr_pos_scaled(render_priv, render_priv->state.clip_x1);
         render_priv->state.clip_y0 =
             y2scr_pos(render_priv, render_priv->state.clip_y0);
         render_priv->state.clip_y1 =
             y2scr_pos(render_priv, render_priv->state.clip_y1);
     }
+
     // calculate rotation parameters
     {
         DVector center;
@@ -2154,7 +2100,7 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event,
         }
 
         for (i = 0; i < text_info->length; ++i) {
-            GlyphInfo *info = text_info->glyphs + i;
+            GlyphInfo *info = glyphs + i;
 
             if (info->hash_key.frx || info->hash_key.fry
                 || info->hash_key.frz || info->hash_key.fax
@@ -2170,22 +2116,26 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event,
     }
 
     // convert glyphs to bitmaps
+    device_x *= render_priv->font_scale_x;
     for (i = 0; i < text_info->length; ++i) {
-        GlyphInfo *g = text_info->glyphs + 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, text_info->glyphs + i);
+        get_bitmap_glyph(render_priv, glyphs + i);
     }
 
     memset(event_images, 0, sizeof(*event_images));
     event_images->top = device_y - text_info->lines[0].asc;
     event_images->height = text_info->height;
-    event_images->left = device_x + bbox.xMin + 0.5;
-    event_images->width = bbox.xMax - bbox.xMin + 0.5;
+    event_images->left =
+        (device_x + bbox.xMin * render_priv->font_scale_x) + 0.5;
+    event_images->width =
+        (bbox.xMax - bbox.xMin) * render_priv->font_scale_x + 0.5;
     event_images->detect_collisions = render_priv->state.detect_collisions;
     event_images->shift_direction = (valign == VALIGN_TOP) ? 1 : -1;
     event_images->event = event;
@@ -2200,7 +2150,7 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event,
  * \brief deallocate image list
  * \param img list pointer
  */
-static void ass_free_images(ASS_Image *img)
+void ass_free_images(ASS_Image *img)
 {
     while (img) {
         ASS_Image *next = img->next;
@@ -2209,103 +2159,32 @@ static void ass_free_images(ASS_Image *img)
     }
 }
 
-static void ass_reconfigure(ASS_Renderer *priv)
-{
-    priv->render_id++;
-    priv->cache.glyph_cache =
-        ass_glyph_cache_reset(priv->cache.glyph_cache);
-    priv->cache.bitmap_cache =
-        ass_bitmap_cache_reset(priv->cache.bitmap_cache);
-    priv->cache.composite_cache =
-        ass_composite_cache_reset(priv->cache.composite_cache);
-    ass_free_images(priv->prev_images_root);
-    priv->prev_images_root = 0;
-}
-
-void ass_set_frame_size(ASS_Renderer *priv, int w, int h)
-{
-    if (priv->settings.frame_width != w || priv->settings.frame_height != h) {
-        priv->settings.frame_width = w;
-        priv->settings.frame_height = h;
-        if (priv->settings.aspect == 0.) {
-            priv->settings.aspect = ((double) w) / h;
-            priv->settings.storage_aspect = ((double) w) / h;
-        }
-        ass_reconfigure(priv);
-    }
-}
-
-void ass_set_margins(ASS_Renderer *priv, int t, int b, int l, int r)
-{
-    if (priv->settings.left_margin != l ||
-        priv->settings.right_margin != r ||
-        priv->settings.top_margin != t
-        || priv->settings.bottom_margin != b) {
-        priv->settings.left_margin = l;
-        priv->settings.right_margin = r;
-        priv->settings.top_margin = t;
-        priv->settings.bottom_margin = b;
-        ass_reconfigure(priv);
-    }
-}
-
-void ass_set_use_margins(ASS_Renderer *priv, int use)
-{
-    priv->settings.use_margins = use;
-}
-
-void ass_set_aspect_ratio(ASS_Renderer *priv, double dar, double sar)
+/**
+ * \brief Check cache limits and reset cache if they are exceeded
+ */
+static void check_cache_limits(ASS_Renderer *priv, CacheStore *cache)
 {
-    if (priv->settings.aspect != dar || priv->settings.storage_aspect != sar) {
-        priv->settings.aspect = dar;
-        priv->settings.storage_aspect = sar;
-        ass_reconfigure(priv);
+    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);
+        ass_free_images(priv->prev_images_root);
+        priv->prev_images_root = 0;
     }
-}
 
-void ass_set_font_scale(ASS_Renderer *priv, double font_scale)
-{
-    if (priv->settings.font_size_coeff != font_scale) {
-        priv->settings.font_size_coeff = font_scale;
-        ass_reconfigure(priv);
-    }
-}
-
-void ass_set_hinting(ASS_Renderer *priv, ASS_Hinting ht)
-{
-    if (priv->settings.hinting != ht) {
-        priv->settings.hinting = ht;
-        ass_reconfigure(priv);
+    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);
     }
 }
 
-void ass_set_line_spacing(ASS_Renderer *priv, double line_spacing)
-{
-    priv->settings.line_spacing = line_spacing;
-}
-
-void ass_set_fonts(ASS_Renderer *priv, const char *default_font,
-                   const char *default_family, int fc, const char *config,
-                   int update)
-{
-    free(priv->settings.default_font);
-    free(priv->settings.default_family);
-    priv->settings.default_font = default_font ? strdup(default_font) : 0;
-    priv->settings.default_family =
-        default_family ? strdup(default_family) : 0;
-
-    if (priv->fontconfig_priv)
-        fontconfig_done(priv->fontconfig_priv);
-    priv->fontconfig_priv =
-        fontconfig_init(priv->library, priv->ftlibrary, default_family,
-                        default_font, fc, config, update);
-}
-
-int ass_fonts_update(ASS_Renderer *render_priv)
-{
-    return fontconfig_update(render_priv->fontconfig_priv);
-}
-
 /**
  * \brief Start a new frame
  */
@@ -2314,7 +2193,6 @@ ass_start_frame(ASS_Renderer *render_priv, ASS_Track *track,
                 long long now)
 {
     ASS_Settings *settings_priv = &render_priv->settings;
-    CacheStore *cache = &render_priv->cache;
 
     if (!render_priv->settings.frame_width
         && !render_priv->settings.frame_height)
@@ -2323,27 +2201,14 @@ ass_start_frame(ASS_Renderer *render_priv, ASS_Track *track,
     if (render_priv->library != track->library)
         return 1;
 
+    if (!render_priv->fontconfig_priv)
+        return 1;
+
     free_list_clear(render_priv);
 
     if (track->n_events == 0)
         return 1;               // nothing to do
 
-    render_priv->width = settings_priv->frame_width;
-    render_priv->height = settings_priv->frame_height;
-    render_priv->orig_width =
-        settings_priv->frame_width - settings_priv->left_margin -
-        settings_priv->right_margin;
-    render_priv->orig_height =
-        settings_priv->frame_height - settings_priv->top_margin -
-        settings_priv->bottom_margin;
-    render_priv->orig_width_nocrop =
-        settings_priv->frame_width - FFMAX(settings_priv->left_margin,
-                                           0) -
-        FFMAX(settings_priv->right_margin, 0);
-    render_priv->orig_height_nocrop =
-        settings_priv->frame_height - FFMAX(settings_priv->top_margin,
-                                            0) -
-        FFMAX(settings_priv->bottom_margin, 0);
     render_priv->track = track;
     render_priv->time = now;
 
@@ -2365,23 +2230,7 @@ ass_start_frame(ASS_Renderer *render_priv, ASS_Track *track,
     render_priv->prev_images_root = render_priv->images_root;
     render_priv->images_root = 0;
 
-    if (cache->bitmap_cache->cache_size > cache->bitmap_max_size) {
-        ass_msg(render_priv->library, MSGL_V,
-                "Hitting hard bitmap cache limit (was: %ld bytes), "
-                "resetting.", (long) cache->bitmap_cache->cache_size);
-        cache->bitmap_cache = ass_bitmap_cache_reset(cache->bitmap_cache);
-        cache->composite_cache = ass_composite_cache_reset(
-            cache->composite_cache);
-        ass_free_images(render_priv->prev_images_root);
-        render_priv->prev_images_root = 0;
-    }
-
-    if (cache->glyph_cache->count > cache->glyph_max) {
-        ass_msg(render_priv->library, MSGL_V,
-            "Hitting hard glyph cache limit (was: %ld glyphs), resetting.",
-            (long) cache->glyph_cache->count);
-        cache->glyph_cache = ass_glyph_cache_reset(cache->glyph_cache);
-    }
+    check_cache_limits(render_priv, &render_priv->cache);
 
     return 0;
 }
@@ -2505,7 +2354,7 @@ fix_collisions(ASS_Renderer *render_priv, EventImages *imgs, int cnt)
             s.hb = priv->left + priv->width;
             if (priv->height != imgs[i].height) {       // no, it's not
                 ass_msg(render_priv->library, MSGL_WARN,
-                        "Warning! Event height has changed");
+                        "Event height has changed");
                 priv->top = 0;
                 priv->height = 0;
                 priv->left = 0;
index 6d9db23..b039f48 100644 (file)
@@ -38,6 +38,9 @@
 #include "ass_library.h"
 #include "ass_drawing.h"
 
+#define GLYPH_CACHE_MAX 1000
+#define BITMAP_CACHE_MAX_SIZE 30 * 1048576
+
 typedef struct {
     double xMin;
     double xMax;
@@ -258,5 +261,6 @@ typedef struct {
 } Segment;
 
 void reset_render_context(ASS_Renderer *render_priv);
+void ass_free_images(ASS_Image *img);
 
 #endif /* LIBASS_RENDER_H */
diff --git a/libass/ass_render_api.c b/libass/ass_render_api.c
new file mode 100644 (file)
index 0000000..058e6c3
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
+ * Copyright (C) 2010 Grigori Goronzy <greg@geekmind.org>
+ *
+ * This file is part of libass.
+ *
+ * libass is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * libass is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with libass; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ass_render.h"
+
+static void ass_reconfigure(ASS_Renderer *priv)
+{
+    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_free_images(priv->prev_images_root);
+    priv->prev_images_root = 0;
+
+    priv->width = settings->frame_width;
+    priv->height = settings->frame_height;
+    priv->orig_width = settings->frame_width - settings->left_margin -
+        settings->right_margin;
+    priv->orig_height = settings->frame_height - settings->top_margin -
+        settings->bottom_margin;
+    priv->orig_width_nocrop =
+        settings->frame_width - FFMAX(settings->left_margin, 0) -
+        FFMAX(settings->right_margin, 0);
+    priv->orig_height_nocrop =
+        settings->frame_height - FFMAX(settings->top_margin, 0) -
+        FFMAX(settings->bottom_margin, 0);
+}
+
+void ass_set_frame_size(ASS_Renderer *priv, int w, int h)
+{
+    if (priv->settings.frame_width != w || priv->settings.frame_height != h) {
+        priv->settings.frame_width = w;
+        priv->settings.frame_height = h;
+        if (priv->settings.aspect == 0.) {
+            priv->settings.aspect = ((double) w) / h;
+            priv->settings.storage_aspect = ((double) w) / h;
+        }
+        ass_reconfigure(priv);
+    }
+}
+
+void ass_set_margins(ASS_Renderer *priv, int t, int b, int l, int r)
+{
+    if (priv->settings.left_margin != l || priv->settings.right_margin != r ||
+        priv->settings.top_margin != t || priv->settings.bottom_margin != b) {
+        priv->settings.left_margin = l;
+        priv->settings.right_margin = r;
+        priv->settings.top_margin = t;
+        priv->settings.bottom_margin = b;
+        ass_reconfigure(priv);
+    }
+}
+
+void ass_set_use_margins(ASS_Renderer *priv, int use)
+{
+    priv->settings.use_margins = use;
+}
+
+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_reconfigure(priv);
+    }
+}
+
+void ass_set_font_scale(ASS_Renderer *priv, double font_scale)
+{
+    if (priv->settings.font_size_coeff != font_scale) {
+        priv->settings.font_size_coeff = font_scale;
+        ass_reconfigure(priv);
+    }
+}
+
+void ass_set_hinting(ASS_Renderer *priv, ASS_Hinting ht)
+{
+    if (priv->settings.hinting != ht) {
+        priv->settings.hinting = ht;
+        ass_reconfigure(priv);
+    }
+}
+
+void ass_set_line_spacing(ASS_Renderer *priv, double line_spacing)
+{
+    priv->settings.line_spacing = line_spacing;
+}
+
+void ass_set_fonts(ASS_Renderer *priv, const char *default_font,
+                   const char *default_family, int fc, const char *config,
+                   int update)
+{
+    free(priv->settings.default_font);
+    free(priv->settings.default_family);
+    priv->settings.default_font = default_font ? strdup(default_font) : 0;
+    priv->settings.default_family =
+        default_family ? strdup(default_family) : 0;
+
+    if (priv->fontconfig_priv)
+        fontconfig_done(priv->fontconfig_priv);
+    priv->fontconfig_priv =
+        fontconfig_init(priv->library, priv->ftlibrary, default_family,
+                        default_font, fc, config, update);
+}
+
+int ass_fonts_update(ASS_Renderer *render_priv)
+{
+    return fontconfig_update(render_priv->fontconfig_priv);
+}
+
+void ass_set_cache_limits(ASS_Renderer *render_priv, int glyph_max,
+                          int bitmap_max)
+{
+    render_priv->cache.glyph_max = glyph_max ? glyph_max : GLYPH_CACHE_MAX;
+    render_priv->cache.bitmap_max_size = bitmap_max ? 1048576 * bitmap_max :
+                                         BITMAP_CACHE_MAX_SIZE;
+}
index 7b73630..f55b37a 100644 (file)
 #include <ctype.h>
 #include <errno.h>
 
+const
 static int maxExponent = 511;   /* Largest possible base 10 exponent.  Any
                                  * exponent larger than this will already
                                  * produce underflow or overflow, so there's
                                  * no need to worry about additional digits.
                                  */
 
+const
 static double powersOf10[] = {  /* Table giving binary powers of 10.  Entry */
     10.,                        /* is 10^2^i.  Used to convert decimal */
     100.,                       /* exponents into floating-point numbers. */
@@ -224,7 +226,7 @@ ass_strtod(string, endPtr)
         errno = ERANGE;
     }
     dblExp = 1.0;
-    for (d = powersOf10; exp != 0; exp >>= 1, d += 1) {
+    for (d = (double *) powersOf10; exp != 0; exp >>= 1, d += 1) {
         if (exp & 01) {
             dblExp *= *d;
         }