whitespace cosmetics: Remove all trailing whitespace.
[mplayer.git] / libass / ass_render.c
index b62ba5a..f13f766 100644 (file)
@@ -1,22 +1,24 @@
 // -*- c-basic-offset: 8; indent-tabs-mode: t -*-
 // vim:ts=8:sw=8:noet:ai:
 /*
-  Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
-
-  This program 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.
-
-  This program 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 this program; if not, write to the Free Software
-  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-*/
+ * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
+ *
+ * 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_fontconfig.h"
 #include "ass_library.h"
 
-#define MAX_GLYPHS 1000
-#define MAX_LINES 100
+#define MAX_GLYPHS 3000
+#define MAX_LINES 300
+#define BLUR_MAX_RADIUS 50.0
+#define MAX_BE 100
+#define ROUND(x) ((int) ((x) + .5))
+#define SUBPIXEL_MASK 56       // d6 bitmask for subpixel accuracy adjustment
 
 static int last_render_id = 0;
 
@@ -56,6 +62,7 @@ typedef struct ass_settings_s {
        int use_margins; // 0 - place all subtitles inside original frame
                         // 1 - use margins for placing toptitles and subtitles
        double aspect; // frame aspect ratio, d_width / d_height.
+       ass_hinting_t hinting;
 
        char* default_font;
        char* default_family;
@@ -109,9 +116,10 @@ typedef struct glyph_info_s {
        int asc, desc; // font max ascender and descender
 //     int height;
        int be; // blur edges
-       int shadow;
+       double blur; // gaussian blur
+       double shadow;
        double frx, fry, frz; // rotation
-       
+
        bitmap_hash_key_t hash_key;
 } glyph_info_t;
 
@@ -133,11 +141,11 @@ typedef struct text_info_s {
 typedef struct render_context_s {
        ass_event_t* event;
        ass_style_t* style;
-       
+
        ass_font_t* font;
        char* font_path;
-       int font_size;
-       
+       double font_size;
+
        FT_Stroker stroker;
        int alignment; // alignment overrides go here; if zero, style value will be used
        double frx, fry, frz;
@@ -146,8 +154,8 @@ typedef struct render_context_s {
                EVENT_HSCROLL, // "Banner" transition effect, text_width is unlimited
                EVENT_VSCROLL // "Scroll up", "Scroll down" transition effects
                } evt_type;
-       int pos_x, pos_y; // position
-       int org_x, org_y; // origin
+       double pos_x, pos_y; // position
+       double org_x, org_y; // origin
        char have_origin; // origin is explicitly defined; if 0, get_base_point() is used
        double scale_x, scale_y;
        double hspacing; // distance between letters, in pixels
@@ -157,7 +165,9 @@ typedef struct render_context_s {
        char detect_collisions;
        uint32_t fade; // alpha from \fad
        char be; // blur edges
-       int shadow;
+       double blur; // gaussian blur
+       double shadow;
+       int drawing_mode; // not implemented; when != 0 text is discarded, except for style override tags
 
        effect_t effect_type;
        int effect_timing;
@@ -174,7 +184,8 @@ typedef struct render_context_s {
        char* family;
        unsigned bold;
        unsigned italic;
-       
+       int treat_family_as_pattern;
+
 } render_context_t;
 
 // frame-global data
@@ -183,6 +194,8 @@ typedef struct frame_context_s {
        int width, height; // screen dimensions
        int orig_height; // frame height ( = screen height - margins )
        int orig_width; // frame width ( = screen width - margins )
+       int orig_height_nocrop; // frame height ( = screen height - margins + cropheight)
+       int orig_width_nocrop; // frame width ( = screen width - margins + cropwidth)
        ass_track_t* track;
        long long time; // frame's timestamp, ms
        double font_scale;
@@ -213,9 +226,15 @@ static void ass_lazy_track_init(void)
        } else {
                double orig_aspect = (global_settings->aspect * frame_context.height * frame_context.orig_width) /
                        frame_context.orig_height / frame_context.width;
-               if (!track->PlayResY) {
+               if (!track->PlayResY && track->PlayResX == 1280) {
+                       track->PlayResY = 1024;
+                       mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_PlayResYUndefinedSettingY, track->PlayResY);
+               } else if (!track->PlayResY) {
                        track->PlayResY = track->PlayResX / orig_aspect + .5;
                        mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_PlayResYUndefinedSettingY, track->PlayResY);
+               } else if (!track->PlayResX && track->PlayResY == 1024) {
+                       track->PlayResX = 1280;
+                       mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_PlayResXUndefinedSettingX, track->PlayResX);
                } else if (!track->PlayResX) {
                        track->PlayResX = track->PlayResY * orig_aspect + .5;
                        mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_PlayResXUndefinedSettingX, track->PlayResX);
@@ -228,35 +247,43 @@ ass_renderer_t* ass_renderer_init(ass_library_t* library)
        int error;
        FT_Library ft;
        ass_renderer_t* priv = 0;
-       
+       int vmajor, vminor, vpatch;
+
        memset(&render_context, 0, sizeof(render_context));
        memset(&frame_context, 0, sizeof(frame_context));
        memset(&text_info, 0, sizeof(text_info));
 
        error = FT_Init_FreeType( &ft );
-       if ( error ) { 
+       if ( error ) {
                mp_msg(MSGT_ASS, MSGL_FATAL, MSGTR_LIBASS_FT_Init_FreeTypeFailed);
                goto ass_init_exit;
        }
 
+       FT_Library_Version(ft, &vmajor, &vminor, &vpatch);
+       mp_msg(MSGT_ASS, MSGL_V, "FreeType library version: %d.%d.%d\n",
+              vmajor, vminor, vpatch);
+       mp_msg(MSGT_ASS, MSGL_V, "FreeType headers version: %d.%d.%d\n",
+              FREETYPE_MAJOR, FREETYPE_MINOR, FREETYPE_PATCH);
+
        priv = calloc(1, sizeof(ass_renderer_t));
        if (!priv) {
                FT_Done_FreeType(ft);
                goto ass_init_exit;
        }
 
-       priv->synth_priv = ass_synth_init();
+       priv->synth_priv = ass_synth_init(BLUR_MAX_RADIUS);
 
        priv->library = library;
        priv->ftlibrary = ft;
        // images_root and related stuff is zero-filled in calloc
-       
+
        ass_font_cache_init();
        ass_bitmap_cache_init();
+       ass_composite_cache_init();
        ass_glyph_cache_init();
 
        text_info.glyphs = calloc(MAX_GLYPHS, sizeof(glyph_info_t));
-       
+
 ass_init_exit:
        if (priv) mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_Init);
        else mp_msg(MSGT_ASS, MSGL_ERR, MSGTR_LIBASS_InitFailed);
@@ -268,6 +295,7 @@ void ass_renderer_done(ass_renderer_t* priv)
 {
        ass_font_cache_done();
        ass_bitmap_cache_done();
+       ass_composite_cache_done();
        ass_glyph_cache_done();
        if (render_context.stroker) {
                FT_Stroker_Done(render_context.stroker);
@@ -288,7 +316,7 @@ void ass_renderer_done(ass_renderer_t* priv)
 static ass_image_t* 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_t* img = calloc(1, sizeof(ass_image_t));
-       
+
        img->w = bitmap_w;
        img->h = bitmap_h;
        img->stride = stride;
@@ -325,7 +353,7 @@ static ass_image_t** render_glyph(bitmap_t* bm, int dst_x, int dst_y, uint32_t c
        dst_x += bm->left;
        dst_y += bm->top;
        brk -= bm->left;
-       
+
        // clipping
        clip_x0 = render_context.clip_x0;
        clip_y0 = render_context.clip_y0;
@@ -335,7 +363,7 @@ static ass_image_t** render_glyph(bitmap_t* bm, int dst_x, int dst_y, uint32_t c
        b_y0 = 0;
        b_x1 = bm->w;
        b_y1 = bm->h;
-       
+
        tmp = dst_x - clip_x0;
        if (tmp < 0) {
                mp_msg(MSGT_ASS, MSGL_DBG2, "clip left\n");
@@ -356,13 +384,13 @@ static ass_image_t** render_glyph(bitmap_t* bm, int dst_x, int dst_y, uint32_t c
                mp_msg(MSGT_ASS, MSGL_DBG2, "clip bottom\n");
                b_y1 = bm->h + tmp;
        }
-       
+
        if ((b_y0 >= b_y1) || (b_x0 >= b_x1))
                return tail;
 
        if (brk > b_x0) { // draw left part
                if (brk > b_x1) brk = b_x1;
-               img = my_draw_bitmap(bm->buffer + bm->w * b_y0 + b_x0, 
+               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);
                *tail = img;
@@ -370,7 +398,7 @@ static ass_image_t** render_glyph(bitmap_t* bm, int dst_x, int dst_y, uint32_t c
        }
        if (brk < b_x1) { // draw right part
                if (brk < b_x0) brk = b_x0;
-               img = my_draw_bitmap(bm->buffer + bm->w * b_y0 + brk, 
+               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);
                *tail = img;
@@ -380,60 +408,131 @@ static ass_image_t** render_glyph(bitmap_t* bm, int dst_x, int dst_y, uint32_t c
 }
 
 /**
- * \brief Render text_info_t struct into ass_image_t list
- * Rasterize glyphs and put them in glyph cache.
+ * \brief Calculate overlapping area of two consecutive bitmaps and in case they
+ * overlap, composite them together
+ * Mainly useful for translucent glyphs and especially borders, to avoid the
+ * luminance adding up where they overlap (which looks ugly)
+ */
+static void render_overlap(ass_image_t** last_tail, ass_image_t** tail, bitmap_hash_key_t *last_hash, bitmap_hash_key_t* hash) {
+       int left, top, bottom, right;
+       int old_left, old_top, w, h, cur_left, cur_top;
+       int x, y, opos, cpos;
+       char m;
+       composite_hash_key_t hk;
+       composite_hash_val_t *hv;
+       composite_hash_key_t *nhk;
+       int ax = (*last_tail)->dst_x;
+       int ay = (*last_tail)->dst_y;
+       int aw = (*last_tail)->w;
+       int as = (*last_tail)->stride;
+       int ah = (*last_tail)->h;
+       int bx = (*tail)->dst_x;
+       int by = (*tail)->dst_y;
+       int bw = (*tail)->w;
+       int bs = (*tail)->stride;
+       int bh = (*tail)->h;
+       unsigned char* a;
+       unsigned char* b;
+
+       if ((*last_tail)->bitmap == (*tail)->bitmap)
+               return;
+
+       if ((*last_tail)->color != (*tail)->color)
+               return;
+
+       // Calculate overlap coordinates
+       left = (ax > bx) ? ax : bx;
+       top = (ay > by) ? ay : by;
+       right = ((ax+aw) < (bx+bw)) ? (ax+aw) : (bx+bw);
+       bottom = ((ay+ah) < (by+bh)) ? (ay+ah) : (by+bh);
+       if ((right <= left) || (bottom <= top))
+               return;
+       old_left = left-ax;
+       old_top = top-ay;
+       w = right-left;
+       h = bottom-top;
+       cur_left = left-bx;
+       cur_top = top-by;
+
+       // Query cache
+       memset(&hk, 0, sizeof(hk));
+       memcpy(&hk.a, last_hash, sizeof(*last_hash));
+       memcpy(&hk.b, hash, sizeof(*hash));
+       hk.aw = aw;
+       hk.ah = ah;
+       hk.bw = bw;
+       hk.bh = bh;
+       hk.ax = ax;
+       hk.ay = ay;
+       hk.bx = bx;
+       hk.by = by;
+       hv = cache_find_composite(&hk);
+       if (hv) {
+               (*last_tail)->bitmap = hv->a;
+               (*tail)->bitmap = hv->b;
+               return;
+       }
+
+       // Allocate new bitmaps and copy over data
+       a = (*last_tail)->bitmap;
+       b = (*tail)->bitmap;
+       (*last_tail)->bitmap = malloc(as*ah);
+       (*tail)->bitmap = malloc(bs*bh);
+       memcpy((*last_tail)->bitmap, a, as*ah);
+       memcpy((*tail)->bitmap, b, bs*bh);
+
+       // Composite overlapping area
+       for (y=0; y<h; y++)
+               for (x=0; x<w; x++) {
+                       opos = (old_top+y)*(as) + (old_left+x);
+                       cpos = (cur_top+y)*(bs) + (cur_left+x);
+                       m = (a[opos] > b[cpos]) ? a[opos] : b[cpos];
+                       (*last_tail)->bitmap[opos] = 0;
+                       (*tail)->bitmap[cpos] = m;
+               }
+
+       // Insert bitmaps into the cache
+       nhk = calloc(1, sizeof(*nhk));
+       memcpy(nhk, &hk, sizeof(*nhk));
+       hv = calloc(1, sizeof(*hv));
+       hv->a = (*last_tail)->bitmap;
+       hv->b = (*tail)->bitmap;
+       cache_add_composite(nhk, hv);
+}
+
+/**
+ * \brief Convert text_info_t struct to ass_image_t list
+ * Splits glyphs in halves when needed (for \kf karaoke).
  */
 static ass_image_t* render_text(text_info_t* text_info, int dst_x, int dst_y)
 {
        int pen_x, pen_y;
-       int i, error;
+       int i;
        bitmap_t* bm;
-       bitmap_hash_val_t hash_val;
        ass_image_t* head;
        ass_image_t** tail = &head;
-
-       for (i = 0; i < text_info->length; ++i) {
-               if (text_info->glyphs[i].glyph) {
-                       if ((text_info->glyphs[i].symbol == '\n') || (text_info->glyphs[i].symbol == 0))
-                               continue;
-                       error = glyph_to_bitmap(ass_renderer->synth_priv,
-                                       text_info->glyphs[i].glyph, text_info->glyphs[i].outline_glyph,
-                                       &text_info->glyphs[i].bm, &text_info->glyphs[i].bm_o,
-                                       &text_info->glyphs[i].bm_s, text_info->glyphs[i].be);
-                       if (error)
-                               text_info->glyphs[i].symbol = 0;
-                       FT_Done_Glyph(text_info->glyphs[i].glyph);
-                       if (text_info->glyphs[i].outline_glyph)
-                               FT_Done_Glyph(text_info->glyphs[i].outline_glyph);
-
-                       // cache
-                       if (text_info->glyphs[i].hash_key.frx == 0 &&
-                           text_info->glyphs[i].hash_key.fry == 0 &&
-                           text_info->glyphs[i].hash_key.frz == 0) {
-                               hash_val.bbox_scaled = text_info->glyphs[i].bbox;
-                               hash_val.bm_o = text_info->glyphs[i].bm_o;
-                               hash_val.bm = text_info->glyphs[i].bm;
-                               hash_val.bm_s = text_info->glyphs[i].bm_s;
-                               hash_val.advance.x = text_info->glyphs[i].advance.x;
-                               hash_val.advance.y = text_info->glyphs[i].advance.y;
-                               cache_add_bitmap(&(text_info->glyphs[i].hash_key), &hash_val);
-                       }
-
-               }
-       }
+       ass_image_t** last_tail = 0;
+       ass_image_t** here_tail = 0;
+       bitmap_hash_key_t* last_hash = 0;
 
        for (i = 0; i < text_info->length; ++i) {
                glyph_info_t* info = text_info->glyphs + i;
                if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm_s || (info->shadow == 0))
                        continue;
 
-               pen_x = dst_x + info->pos.x + info->shadow;
-               pen_y = dst_y + info->pos.y + info->shadow;
+               pen_x = dst_x + info->pos.x + ROUND(info->shadow * frame_context.border_scale);
+               pen_y = dst_y + info->pos.y + ROUND(info->shadow * frame_context.border_scale);
                bm = info->bm_s;
 
+               here_tail = tail;
                tail = render_glyph(bm, pen_x, pen_y, info->c[3], 0, 1000000, tail);
+               if (last_tail && tail != here_tail && ((info->c[3] & 0xff) > 0))
+                       render_overlap(last_tail, here_tail, last_hash, &info->hash_key);
+               last_tail = here_tail;
+               last_hash = &info->hash_key;
        }
 
+       last_tail = 0;
        for (i = 0; i < text_info->length; ++i) {
                glyph_info_t* info = text_info->glyphs + i;
                if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm_o)
@@ -442,11 +541,17 @@ static ass_image_t* render_text(text_info_t* text_info, int dst_x, int dst_y)
                pen_x = dst_x + info->pos.x;
                pen_y = dst_y + info->pos.y;
                bm = info->bm_o;
-               
+
                if ((info->effect_type == EF_KARAOKE_KO) && (info->effect_timing <= info->bbox.xMax)) {
                        // do nothing
-               } else
+               } else {
+                       here_tail = tail;
                        tail = render_glyph(bm, pen_x, pen_y, info->c[2], 0, 1000000, tail);
+                       if (last_tail && tail != here_tail && ((info->c[2] & 0xff) > 0))
+                               render_overlap(last_tail, here_tail, last_hash, &info->hash_key);
+                       last_tail = here_tail;
+                       last_hash = &info->hash_key;
+               }
        }
        for (i = 0; i < text_info->length; ++i) {
                glyph_info_t* info = text_info->glyphs + i;
@@ -475,35 +580,49 @@ static ass_image_t* render_text(text_info_t* text_info, int dst_x, int dst_y)
 /**
  * \brief Mapping between script and screen coordinates
  */
-static int x2scr(int x) {
-       return x*frame_context.orig_width / frame_context.track->PlayResX + global_settings->left_margin;
+static int x2scr(double x) {
+       return x*frame_context.orig_width_nocrop / frame_context.track->PlayResX +
+               FFMAX(global_settings->left_margin, 0);
+}
+static double x2scr_pos(double x) {
+       return x*frame_context.orig_width / frame_context.track->PlayResX +
+               global_settings->left_margin;
 }
 /**
  * \brief Mapping between script and screen coordinates
  */
-static int y2scr(int y) {
-       return y * frame_context.orig_height / frame_context.track->PlayResY + global_settings->top_margin;
+static double y2scr(double y) {
+       return y * frame_context.orig_height_nocrop / frame_context.track->PlayResY +
+               FFMAX(global_settings->top_margin, 0);
+}
+static double y2scr_pos(double y) {
+       return y * frame_context.orig_height / frame_context.track->PlayResY +
+               global_settings->top_margin;
 }
+
 // the same for toptitles
-static int y2scr_top(int y) {
+static int y2scr_top(double y) {
        if (global_settings->use_margins)
-               return y * frame_context.orig_height / frame_context.track->PlayResY;
+               return y * frame_context.orig_height_nocrop / frame_context.track->PlayResY;
        else
-               return y * frame_context.orig_height / frame_context.track->PlayResY + global_settings->top_margin;
+               return y * frame_context.orig_height_nocrop / frame_context.track->PlayResY +
+                       FFMAX(global_settings->top_margin, 0);
 }
 // the same for subtitles
-static int y2scr_sub(int y) {
+static int y2scr_sub(double y) {
        if (global_settings->use_margins)
-               return y * frame_context.orig_height / frame_context.track->PlayResY +
-                      global_settings->top_margin + global_settings->bottom_margin;
+               return y * frame_context.orig_height_nocrop / frame_context.track->PlayResY +
+                       FFMAX(global_settings->top_margin, 0) +
+                       FFMAX(global_settings->bottom_margin, 0);
        else
-               return y * frame_context.orig_height / frame_context.track->PlayResY + global_settings->top_margin;
+               return y * frame_context.orig_height_nocrop / frame_context.track->PlayResY +
+                       FFMAX(global_settings->top_margin, 0);
 }
 
 static void compute_string_bbox( text_info_t* info, FT_BBox *abbox ) {
        FT_BBox bbox;
        int i;
-       
+
        if (text_info.length > 0) {
                bbox.xMin = 32000;
                bbox.xMax = -32000;
@@ -536,9 +655,7 @@ static inline int mystrcmp(char** p, const char* sample) {
                return 0;
 }
 
-double ass_internal_font_size_coeff = 0.8;
-
-static void change_font_size(int sz)
+static void change_font_size(double sz)
 {
        double size = sz * frame_context.font_scale;
 
@@ -561,6 +678,7 @@ static void update_font(void)
        ass_renderer_t* priv = frame_context.ass_priv;
        ass_font_desc_t desc;
        desc.family = strdup(render_context.family);
+       desc.treat_family_as_pattern = render_context.treat_family_as_pattern;
 
        val = render_context.bold;
        // 0 = normal, 1 = bold, >1 = exact weight
@@ -574,7 +692,8 @@ static void update_font(void)
        desc.italic = val;
 
        render_context.font = ass_font_new(priv->library, priv->ftlibrary, priv->fontconfig_priv, &desc);
-       
+       free(desc.family);
+
        if (render_context.font)
                change_font_size(render_context.font_size);
 }
@@ -589,12 +708,9 @@ static void change_border(double border)
        if (!render_context.font) return;
 
        if (border < 0) {
-               if (render_context.style->BorderStyle == 1) {
-                       if (render_context.style->Outline == 0 && render_context.style->Shadow > 0)
-                               border = 1.;
-                       else
-                               border = render_context.style->Outline;
-               } else
+               if (render_context.style->BorderStyle == 1)
+                       border = render_context.style->Outline;
+               else
                        border = 1.;
        }
        render_context.border = border;
@@ -663,7 +779,7 @@ static uint32_t mult_alpha(uint32_t a, uint32_t b)
  * \brief Calculate alpha value by piecewise linear function
  * Used for \fad, \fade implementation.
  */
-static unsigned interpolate_alpha(long long now, 
+static unsigned interpolate_alpha(long long now,
                long long t1, long long t2, long long t3, long long t4,
                unsigned a1, unsigned a2, unsigned a3)
 {
@@ -694,15 +810,62 @@ static void reset_render_context(void);
  * \param pwr multiplier for some tag effects (comes from \t tags)
  */
 static char* parse_tag(char* p, double pwr) {
-#define skip_all(x) if (*p == (x)) ++p; else { \
-       while ((*p != (x)) && (*p != '}') && (*p != 0)) {++p;} }
+#define skip_to(x) while ((*p != (x)) && (*p != '}') && (*p != 0)) { ++p;}
 #define skip(x) if (*p == (x)) ++p; else { return p; }
-       
-       skip_all('\\');
+
+       skip_to('\\');
+       skip('\\');
        if ((*p == '}') || (*p == 0))
                return p;
 
-       if (mystrcmp(&p, "fsc")) {
+       // New tags introduced in vsfilter 2.39
+       if (mystrcmp(&p, "xbord")) {
+               double val;
+               if (mystrtod(&p, &val))
+                       mp_msg(MSGT_ASS, MSGL_V, "stub: \\xbord%.2f\n", val);
+       } else if (mystrcmp(&p, "ybord")) {
+               double val;
+               if (mystrtod(&p, &val))
+                       mp_msg(MSGT_ASS, MSGL_V, "stub: \\ybord%.2f\n", val);
+       } else if (mystrcmp(&p, "xshad")) {
+               int val;
+               if (mystrtoi(&p, &val))
+                       mp_msg(MSGT_ASS, MSGL_V, "stub: \\xshad%d\n", val);
+       } else if (mystrcmp(&p, "yshad")) {
+               int val;
+               if (mystrtoi(&p, &val))
+                       mp_msg(MSGT_ASS, MSGL_V, "stub: \\yshad%d\n", val);
+       } else if (mystrcmp(&p, "fax")) {
+               int val;
+               if (mystrtoi(&p, &val))
+                       mp_msg(MSGT_ASS, MSGL_V, "stub: \\fax%d\n", val);
+       } else if (mystrcmp(&p, "fay")) {
+               int val;
+               if (mystrtoi(&p, &val))
+                       mp_msg(MSGT_ASS, MSGL_V, "stub: \\fay%d\n", val);
+       } else if (mystrcmp(&p, "iclip")) {
+               int x0, y0, x1, y1;
+               int res = 1;
+               skip('(');
+               res &= mystrtoi(&p, &x0);
+               skip(',');
+               res &= mystrtoi(&p, &y0);
+               skip(',');
+               res &= mystrtoi(&p, &x1);
+               skip(',');
+               res &= mystrtoi(&p, &y1);
+               skip(')');
+               mp_msg(MSGT_ASS, MSGL_V, "stub: \\iclip(%d,%d,%d,%d)\n", x0, y0, x1, y1);
+       } else if (mystrcmp(&p, "blur")) {
+               double val;
+               if (mystrtod(&p, &val)) {
+                       val = (val < 0) ? 0 : val;
+                       val = (val > BLUR_MAX_RADIUS) ? BLUR_MAX_RADIUS : val;
+                       render_context.blur = val;
+               } else
+                       render_context.blur = 0.0;
+       // ASS standard tags
+       } else if (mystrcmp(&p, "fsc")) {
                char tp = *p++;
                double val;
                if (tp == 'x') {
@@ -725,8 +888,8 @@ static char* parse_tag(char* p, double pwr) {
                else
                        render_context.hspacing = render_context.style->Spacing;
        } else if (mystrcmp(&p, "fs")) {
-               int val;
-               if (mystrtoi(&p, 10, &val))
+               double val;
+               if (mystrtod(&p, &val))
                        val = render_context.font_size * ( 1 - pwr ) + val * pwr;
                else
                        val = render_context.style->FontSize;
@@ -740,29 +903,29 @@ static char* parse_tag(char* p, double pwr) {
                        val = -1.; // reset to default
                change_border(val);
        } else if (mystrcmp(&p, "move")) {
-               int x1, x2, y1, y2;
+               double x1, x2, y1, y2;
                long long t1, t2, delta_t, t;
-               int x, y;
+               double x, y;
                double k;
                skip('(');
-               x1 = strtol(p, &p, 10);
+               mystrtod(&p, &x1);
                skip(',');
-               y1 = strtol(p, &p, 10);
+               mystrtod(&p, &y1);
                skip(',');
-               x2 = strtol(p, &p, 10);
+               mystrtod(&p, &x2);
                skip(',');
-               y2 = strtol(p, &p, 10);
+               mystrtod(&p, &y2);
                if (*p == ',') {
                        skip(',');
-                       t1 = strtoll(p, &p, 10);
+                       mystrtoll(&p, &t1);
                        skip(',');
-                       t2 = strtoll(p, &p, 10);
-                       mp_msg(MSGT_ASS, MSGL_DBG2, "movement6: (%d, %d) -> (%d, %d), (%" PRId64 " .. %" PRId64 ")\n", 
+                       mystrtoll(&p, &t2);
+                       mp_msg(MSGT_ASS, MSGL_DBG2, "movement6: (%f, %f) -> (%f, %f), (%" PRId64 " .. %" PRId64 ")\n",
                                x1, y1, x2, y2, (int64_t)t1, (int64_t)t2);
                } else {
                        t1 = 0;
                        t2 = render_context.event->Duration;
-                       mp_msg(MSGT_ASS, MSGL_DBG2, "movement: (%d, %d) -> (%d, %d)\n", x1, y1, x2, y2);
+                       mp_msg(MSGT_ASS, MSGL_DBG2, "movement: (%f, %f) -> (%f, %f)\n", x1, y1, x2, y2);
                }
                skip(')');
                delta_t = t2 - t1;
@@ -774,10 +937,12 @@ static char* parse_tag(char* p, double pwr) {
                else k = ((double)(t - t1)) / delta_t;
                x = k * (x2 - x1) + x1;
                y = k * (y2 - y1) + y1;
-               render_context.pos_x = x;
-               render_context.pos_y = y;
-               render_context.detect_collisions = 0;
-               render_context.evt_type = EVENT_POSITIONED;
+               if (render_context.evt_type != EVENT_POSITIONED) {
+                       render_context.pos_x = x;
+                       render_context.pos_y = y;
+                       render_context.detect_collisions = 0;
+                       render_context.evt_type = EVENT_POSITIONED;
+               }
        } else if (mystrcmp(&p, "frx")) {
                double val;
                if (mystrtod(&p, &val)) {
@@ -802,7 +967,7 @@ static char* parse_tag(char* p, double pwr) {
        } else if (mystrcmp(&p, "fn")) {
                char* start = p;
                char* family;
-               skip_all('\\');
+               skip_to('\\');
                if (p > start) {
                        family = malloc(p - start + 1);
                        strncpy(family, start, p - start);
@@ -829,7 +994,7 @@ static char* parse_tag(char* p, double pwr) {
                // FIXME: simplify
        } else if (mystrcmp(&p, "an")) {
                int val;
-               if (mystrtoi(&p, 10, &val) && val) {
+               if (mystrtoi(&p, &val) && val) {
                        int v = (val - 1) / 3; // 0, 1 or 2 for vertical alignment
                        mp_msg(MSGT_ASS, MSGL_DBG2, "an %d\n", val);
                        if (v != 0) v = 3 - v;
@@ -841,30 +1006,32 @@ static char* parse_tag(char* p, double pwr) {
                        render_context.alignment = render_context.style->Alignment;
        } else if (mystrcmp(&p, "a")) {
                int val;
-               if (mystrtoi(&p, 10, &val) && val)
+               if (mystrtoi(&p, &val) && val)
                        render_context.alignment = val;
                else
                        render_context.alignment = render_context.style->Alignment;
        } else if (mystrcmp(&p, "pos")) {
-               int v1, v2;
+               double v1, v2;
                skip('(');
-               v1 = strtol(p, &p, 10);
+               mystrtod(&p, &v1);
                skip(',');
-               v2 = strtol(p, &p, 10);
+               mystrtod(&p, &v2);
                skip(')');
-               mp_msg(MSGT_ASS, MSGL_DBG2, "pos(%d, %d)\n", v1, v2);
-               render_context.evt_type = EVENT_POSITIONED;
-               render_context.detect_collisions = 0;
-               render_context.pos_x = v1;
-               render_context.pos_y = v2;
+               mp_msg(MSGT_ASS, MSGL_DBG2, "pos(%f, %f)\n", v1, v2);
+               if (render_context.evt_type != EVENT_POSITIONED) {
+                       render_context.evt_type = EVENT_POSITIONED;
+                       render_context.detect_collisions = 0;
+                       render_context.pos_x = v1;
+                       render_context.pos_y = v2;
+               }
        } else if (mystrcmp(&p, "fad")) {
                int a1, a2, a3;
                long long t1, t2, t3, t4;
                if (*p == 'e') ++p; // either \fad or \fade
                skip('(');
-               a1 = strtol(p, &p, 10);
+               mystrtoi(&p, &a1);
                skip(',');
-               a2 = strtol(p, &p, 10);
+               mystrtoi(&p, &a2);
                if (*p == ')') {
                        // 2-argument version (\fad, according to specs)
                        // a1 and a2 are fade-in and fade-out durations
@@ -879,30 +1046,33 @@ static char* parse_tag(char* p, double pwr) {
                        // 6-argument version (\fade)
                        // a1 and a2 (and a3) are opacity values
                        skip(',');
-                       a3 = strtol(p, &p, 10);
+                       mystrtoi(&p, &a3);
                        skip(',');
-                       t1 = strtoll(p, &p, 10);
+                       mystrtoll(&p, &t1);
                        skip(',');
-                       t2 = strtoll(p, &p, 10);
+                       mystrtoll(&p, &t2);
                        skip(',');
-                       t3 = strtoll(p, &p, 10);
+                       mystrtoll(&p, &t3);
                        skip(',');
-                       t4 = strtoll(p, &p, 10);
+                       mystrtoll(&p, &t4);
                }
                skip(')');
                render_context.fade = interpolate_alpha(frame_context.time - render_context.event->Start, t1, t2, t3, t4, a1, a2, a3);
        } else if (mystrcmp(&p, "org")) {
                int v1, v2;
                skip('(');
-               v1 = strtol(p, &p, 10);
+               mystrtoi(&p, &v1);
                skip(',');
-               v2 = strtol(p, &p, 10);
+               mystrtoi(&p, &v2);
                skip(')');
                mp_msg(MSGT_ASS, MSGL_DBG2, "org(%d, %d)\n", v1, v2);
                //                              render_context.evt_type = EVENT_POSITIONED;
-               render_context.org_x = v1;
-               render_context.org_y = v2;
-               render_context.have_origin = 1;
+               if (!render_context.have_origin) {
+                       render_context.org_x = v1;
+                       render_context.org_y = v2;
+                       render_context.have_origin = 1;
+                       render_context.detect_collisions = 0;
+               }
        } else if (mystrcmp(&p, "t")) {
                double v[3];
                int v1, v2;
@@ -933,25 +1103,29 @@ static char* parse_tag(char* p, double pwr) {
                if (v3 < 0.)
                        v3 = 0.;
                t = frame_context.time - render_context.event->Start; // FIXME: move to render_context
-               if (t < t1)
+               if (t <= t1)
                        k = 0.;
-               else if (t > t2)
+               else if (t >= t2)
                        k = 1.;
-               else k = pow(((double)(t - t1)) / delta_t, v3);
+               else {
+                       assert(delta_t != 0.);
+                       k = pow(((double)(t - t1)) / delta_t, v3);
+               }
                while (*p == '\\')
-                       p = parse_tag(p, k); // maybe k*pwr ? no, specs forbid nested \t's 
-               skip_all(')'); // FIXME: better skip(')'), but much more tags support required
+                       p = parse_tag(p, k); // maybe k*pwr ? no, specs forbid nested \t's
+               skip_to(')'); // in case there is some unknown tag or a comment
+               skip(')');
        } else if (mystrcmp(&p, "clip")) {
                int x0, y0, x1, y1;
                int res = 1;
                skip('(');
-               res &= mystrtoi(&p, 10, &x0);
+               res &= mystrtoi(&p, &x0);
                skip(',');
-               res &= mystrtoi(&p, 10, &y0);
+               res &= mystrtoi(&p, &y0);
                skip(',');
-               res &= mystrtoi(&p, 10, &x1);
+               res &= mystrtoi(&p, &x1);
                skip(',');
-               res &= mystrtoi(&p, 10, &y1);
+               res &= mystrtoi(&p, &y1);
                skip(')');
                if (res) {
                        render_context.clip_x0 = render_context.clip_x0 * (1-pwr) + x0 * pwr;
@@ -994,13 +1168,16 @@ static char* parse_tag(char* p, double pwr) {
                reset_render_context();
        } else if (mystrcmp(&p, "be")) {
                int val;
-               if (mystrtoi(&p, 10, &val))
-                       render_context.be = val ? 1 : 0;
-               else
+               if (mystrtoi(&p, &val)) {
+                       // Clamp to a safe upper limit, since high values need excessive CPU
+                       val = (val < 0) ? 0 : val;
+                       val = (val > MAX_BE) ? MAX_BE : val;
+                       render_context.be = val;
+               } else
                        render_context.be = 0;
        } else if (mystrcmp(&p, "b")) {
                int b;
-               if (mystrtoi(&p, 10, &b)) {
+               if (mystrtoi(&p, &b)) {
                        if (pwr >= .5)
                                render_context.bold = b;
                } else
@@ -1008,42 +1185,53 @@ static char* parse_tag(char* p, double pwr) {
                update_font();
        } else if (mystrcmp(&p, "i")) {
                int i;
-               if (mystrtoi(&p, 10, &i)) {
+               if (mystrtoi(&p, &i)) {
                        if (pwr >= .5)
                                render_context.italic = i;
                } else
                        render_context.italic = render_context.style->Italic;
                update_font();
        } else if (mystrcmp(&p, "kf") || mystrcmp(&p, "K")) {
-               int val = strtol(p, &p, 10);
+               int val = 0;
+               mystrtoi(&p, &val);
                render_context.effect_type = EF_KARAOKE_KF;
                if (render_context.effect_timing)
                        render_context.effect_skip_timing += render_context.effect_timing;
                render_context.effect_timing = val * 10;
        } else if (mystrcmp(&p, "ko")) {
-               int val = strtol(p, &p, 10);
+               int val = 0;
+               mystrtoi(&p, &val);
                render_context.effect_type = EF_KARAOKE_KO;
                if (render_context.effect_timing)
                        render_context.effect_skip_timing += render_context.effect_timing;
                render_context.effect_timing = val * 10;
        } else if (mystrcmp(&p, "k")) {
-               int val = strtol(p, &p, 10);
+               int val = 0;
+               mystrtoi(&p, &val);
                render_context.effect_type = EF_KARAOKE;
                if (render_context.effect_timing)
                        render_context.effect_skip_timing += render_context.effect_timing;
                render_context.effect_timing = val * 10;
        } else if (mystrcmp(&p, "shad")) {
                int val;
-               if (mystrtoi(&p, 10, &val))
+               if (mystrtoi(&p, &val))
                        render_context.shadow = val;
                else
                        render_context.shadow = render_context.style->Shadow;
+       } else if (mystrcmp(&p, "pbo")) {
+               int val = 0;
+               mystrtoi(&p, &val); // ignored
+       } else if (mystrcmp(&p, "p")) {
+               int val;
+               if (!mystrtoi(&p, &val))
+                       val = 0;
+               render_context.drawing_mode = !!val;
        }
 
        return p;
 
 #undef skip
-#undef skip_all
+#undef skip_to
 }
 
 /**
@@ -1083,13 +1271,13 @@ static unsigned get_next_char(char** str)
                        p += 2;
                        *str = p;
                        return '\n';
-               } else if (*(p+1) == 'n') {
+               } else if ((*(p+1) == 'n') || (*(p+1) == 'h')) {
                        p += 2;
                        *str = p;
                        return ' ';
                }
        }
-       chr = utf8_get_char(&p);
+       chr = utf8_get_char((const char **)&p);
        *str = p;
        return chr;
 }
@@ -1106,7 +1294,7 @@ static void apply_transition_effects(ass_event_t* event)
        while (cnt < 4 && (p = strchr(p, ';'))) {
                v[cnt++] = atoi(++p);
        }
-       
+
        if (strncmp(event->Effect, "Banner;", 7) == 0) {
                int delay;
                if (cnt < 1) {
@@ -1174,6 +1362,7 @@ static void reset_render_context(void)
        if (render_context.family)
                free(render_context.family);
        render_context.family = strdup(render_context.style->FontName);
+       render_context.treat_family_as_pattern = render_context.style->treat_fontname_as_pattern;
        render_context.bold = render_context.style->Bold;
        render_context.italic = render_context.style->Italic;
        update_font();
@@ -1183,6 +1372,7 @@ static void reset_render_context(void)
        render_context.scale_y = render_context.style->ScaleY;
        render_context.hspacing = render_context.style->Spacing;
        render_context.be = 0;
+       render_context.blur = 0.0;
        render_context.shadow = render_context.style->Shadow;
        render_context.frx = render_context.fry = 0.;
        render_context.frz = M_PI * render_context.style->Angle / 180.;
@@ -1213,10 +1403,11 @@ static void init_render_context(ass_event_t* event)
        render_context.clip_y1 = frame_context.track->PlayResY;
        render_context.detect_collisions = 1;
        render_context.fade = 0;
+       render_context.drawing_mode = 0;
        render_context.effect_type = EF_NONE;
        render_context.effect_timing = 0;
        render_context.effect_skip_timing = 0;
-       
+
        apply_transition_effects(event);
 }
 
@@ -1224,11 +1415,22 @@ static void free_render_context(void)
 {
 }
 
-static int get_outline_glyph(int symbol, glyph_info_t* info, FT_Vector* advance)
+/**
+ * \brief Get normal and outline (border) glyphs
+ * \param symbol ucs4 char
+ * \param info out: struct filled with extracted data
+ * \param advance subpixel shift vector used for cache lookup
+ * Tries to get both glyphs from cache.
+ * If they can't be found, gets a glyph from font face, generates outline with FT_Stroker,
+ * and add them to cache.
+ * The glyphs are returned in info->glyph and info->outline_glyph
+ */
+static void get_outline_glyph(int symbol, glyph_info_t* info, FT_Vector* advance)
 {
        int error;
        glyph_hash_val_t* val;
        glyph_hash_key_t key;
+       memset(&key, 0, sizeof(key));
        key.font = render_context.font;
        key.size = render_context.font_size;
        key.ch = symbol;
@@ -1237,88 +1439,104 @@ static int get_outline_glyph(int symbol, glyph_info_t* info, FT_Vector* advance)
        key.advance = *advance;
        key.bold = render_context.bold;
        key.italic = render_context.italic;
+       key.outline = render_context.border * 0xFFFF;
 
-       info->glyph = info->outline_glyph = 0;
+       memset(info, 0, sizeof(glyph_info_t));
 
        val = cache_find_glyph(&key);
        if (val) {
                FT_Glyph_Copy(val->glyph, &info->glyph);
+               if (val->outline_glyph)
+                       FT_Glyph_Copy(val->outline_glyph, &info->outline_glyph);
                info->bbox = val->bbox_scaled;
                info->advance.x = val->advance.x;
                info->advance.y = val->advance.y;
        } else {
                glyph_hash_val_t v;
-               info->glyph = ass_font_get_glyph(frame_context.ass_priv->fontconfig_priv, render_context.font, symbol);
+               info->glyph = ass_font_get_glyph(frame_context.ass_priv->fontconfig_priv, render_context.font, symbol, global_settings->hinting);
                if (!info->glyph)
-                       return 0;
+                       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_PIXELS, &info->bbox);
 
+               if (render_context.stroker) {
+                       info->outline_glyph = info->glyph;
+                       error = FT_Glyph_StrokeBorder( &(info->outline_glyph), render_context.stroker, 0 , 0 ); // don't destroy original
+                       if (error) {
+                               mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FT_Glyph_Stroke_Error, error);
+                       }
+               }
+
+               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.advance = info->advance;
                v.bbox_scaled = info->bbox;
                cache_add_glyph(&key, &v);
        }
-
-       if (render_context.stroker) {
-               info->outline_glyph = info->glyph;
-               error = FT_Glyph_StrokeBorder( &(info->outline_glyph), render_context.stroker, 0 , 0 ); // don't destroy original
-               if (error) {
-                       mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FT_Glyph_Stroke_Error, error);
-               }
-       }
-       return 0;
 }
 
+static void transform_3d(FT_Vector shift, FT_Glyph* glyph, FT_Glyph* glyph2, double frx, double fry, double frz);
+
 /**
- * \brief Get normal and outline glyphs from cache (if possible) or font face
- * \param index face glyph index
- * \param symbol ucs4 char
- * \param info out: struct filled with extracted data
- * \param advance advance vector of the extracted glyph
- * \return 0 on success
+ * \brief Get bitmaps for a glyph
+ * \param info glyph info
+ * Tries to get glyph bitmaps from bitmap cache.
+ * If they can't be found, they are generated by rotating and rendering the glyph.
+ * After that, bitmaps are added to the cache.
+ * They are returned in info->bm (glyph), info->bm_o (outline) and info->bm_s (shadow).
  */
-static int get_bitmap_glyph(int symbol, glyph_info_t* info, FT_Vector* advance)
+static void get_bitmap_glyph(glyph_info_t* info)
 {
-       int error;
        bitmap_hash_val_t* val;
-       bitmap_hash_key_t* key = &(info->hash_key);
-       
-       key->font = render_context.font;
-       key->size = render_context.font_size;
-       key->ch = symbol;
-       key->outline = (render_context.border * 0xFFFF); // convert to 16.16
-       key->scale_x = (render_context.scale_x * 0xFFFF);
-       key->scale_y = (render_context.scale_y * 0xFFFF);
-       key->frx = (render_context.frx * 0xFFFF);
-       key->fry = (render_context.fry * 0xFFFF);
-       key->frz = (render_context.frz * 0xFFFF);
-       key->advance = *advance;
-       key->bold = render_context.bold;
-       key->italic = render_context.italic;
-       key->be = render_context.be;
+       bitmap_hash_key_t* key = &info->hash_key;
 
        val = cache_find_bitmap(key);
 /*     val = 0; */
-       
+
        if (val) {
                info->bm = val->bm;
                info->bm_o = val->bm_o;
                info->bm_s = val->bm_s;
-               info->bbox = val->bbox_scaled;
-               info->advance.x = val->advance.x;
-               info->advance.y = val->advance.y;
-       } else
+       } else {
+               FT_Vector shift;
+               bitmap_hash_val_t hash_val;
+               int error;
                info->bm = info->bm_o = info->bm_s = 0;
+               if (info->glyph && info->symbol != '\n' && info->symbol != 0) {
+                       // calculating rotation shift vector (from rotation origin to the glyph basepoint)
+                       shift.x = int_to_d6(info->hash_key.shift_x);
+                       shift.y = int_to_d6(info->hash_key.shift_y);
+                       // apply rotation
+                       transform_3d(shift, &info->glyph, &info->outline_glyph, info->frx, info->fry, info->frz);
 
-       return 0;
+                       // render glyph
+                       error = glyph_to_bitmap(ass_renderer->synth_priv,
+                                       info->glyph, info->outline_glyph,
+                                       &info->bm, &info->bm_o,
+                                       &info->bm_s, info->be, info->blur * frame_context.border_scale);
+                       if (error)
+                               info->symbol = 0;
+
+                       // add bitmaps to cache
+                       hash_val.bm_o = info->bm_o;
+                       hash_val.bm = info->bm;
+                       hash_val.bm_s = info->bm_s;
+                       cache_add_bitmap(&(info->hash_key), &hash_val);
+               }
+       }
+       // deallocate glyphs
+       if (info->glyph)
+               FT_Done_Glyph(info->glyph);
+       if (info->outline_glyph)
+               FT_Done_Glyph(info->outline_glyph);
 }
 
 /**
  * This function goes through text_info and calculates text parameters.
  * The following text_info fields are filled:
- *   n_lines
  *   height
  *   lines[].height
  *   lines[].asc
@@ -1345,6 +1563,7 @@ static void measure_text(void)
                                max_desc = cur->desc;
                }
        }
+       text_info.height += (text_info.n_lines - 1) * double_to_d6(global_settings->line_spacing);
 }
 
 /**
@@ -1383,8 +1602,8 @@ static void wrap_lines_smart(int max_text_width)
                        break_at = i;
                        mp_msg(MSGT_ASS, MSGL_DBG2, "forced line break at %d\n", break_at);
                }
-               
-               if (len >= max_text_width) {
+
+               if ((len >= max_text_width) && (frame_context.track->WrapStyle != 2)) {
                        break_type = 1;
                        break_at = last_space;
                        if (break_at == -1)
@@ -1400,7 +1619,7 @@ static void wrap_lines_smart(int max_text_width)
                        // marking break_at+1 as start of a new line
                        int lead = break_at + 1; // the first symbol of the new line
                        if (text_info.n_lines >= MAX_LINES) {
-                               // to many lines ! 
+                               // to many lines !
                                // no more linebreaks
                                for (j = lead; j < text_info.length; ++j)
                                        text_info.glyphs[j].linebreak = 0;
@@ -1413,7 +1632,7 @@ static void wrap_lines_smart(int max_text_width)
                        s_offset = s1->bbox.xMin + s1->pos.x;
                        text_info.n_lines ++;
                }
-               
+
                if (cur->symbol == ' ')
                        last_space = i;
 
@@ -1459,11 +1678,11 @@ static void wrap_lines_smart(int max_text_width)
                        if (i == text_info.length)
                                break;
                }
-               
+
        }
        assert(text_info.n_lines >= 1);
 #undef DIFF
-       
+
        measure_text();
 
        pen_shift_x = 0;
@@ -1475,7 +1694,7 @@ static void wrap_lines_smart(int max_text_width)
                        int height = text_info.lines[cur_line - 1].desc + text_info.lines[cur_line].asc;
                        cur_line ++;
                        pen_shift_x = - cur->pos.x;
-                       pen_shift_y += d6_to_int(height) + global_settings->line_spacing;
+                       pen_shift_y += d6_to_int(height + double_to_d6(global_settings->line_spacing));
                        mp_msg(MSGT_ASS, MSGL_DBG2, "shifting from %d to %d by (%d, %d)\n", i, text_info.length - 1, pen_shift_x, pen_shift_y);
                }
                cur->pos.x += pen_shift_x;
@@ -1487,7 +1706,7 @@ static void wrap_lines_smart(int max_text_width)
  * \brief determine karaoke effects
  * Karaoke effects cannot be calculated during parse stage (get_next_char()),
  * so they are done in a separate step.
- * Parse stage: when karaoke style override is found, its parameters are stored in the next glyph's 
+ * Parse stage: when karaoke style override is found, its parameters are stored in the next glyph's
  * (the first glyph of the karaoke word)'s effect_type and effect_timing.
  * This function:
  * 1. sets effect_type for all glyphs in the word (_karaoke_ word)
@@ -1587,73 +1806,46 @@ static void get_base_point(FT_BBox bbox, int alignment, int* bx, int* by)
 }
 
 /**
- * \brief Multiply 4-vector by 4-matrix
- * \param a 4-vector
- * \param m 4-matrix]
- * \param b out: 4-vector
- * Calculates a * m and stores result in b
- */
-static inline void transform_point_3d(double *a, double *m, double *b)
-{
-       b[0] = a[0] * m[0] + a[1] * m[4] + a[2] * m[8] +  a[3] * m[12];
-       b[1] = a[0] * m[1] + a[1] * m[5] + a[2] * m[9] +  a[3] * m[13];
-       b[2] = a[0] * m[2] + a[1] * m[6] + a[2] * m[10] + a[3] * m[14];
-       b[3] = a[0] * m[3] + a[1] * m[7] + a[2] * m[11] + a[3] * m[15];
-}
-
-/**
- * \brief Apply 3d transformation to a vector
- * \param v FreeType vector (2d)
- * \param m 4-matrix
- * Transforms v by m, projects the result back to the screen plane
- * Result is returned in v.
- */
-static inline void transform_vector_3d(FT_Vector* v, double *m) {
-       double a[4], b[4];
-       a[0] = d6_to_double(v->x);
-       a[1] = d6_to_double(v->y);
-       a[2] = 0.;
-       a[3] = 1.;
-       transform_point_3d(a, m, b);
-       /* Apply perspective projection with the following matrix:
-          2500     0     0     0
-             0  2500     0     0
-             0     0     0     0
-             0     0     8     2500
-          where 2500 is camera distance, 8 - z-axis scale.
-          Camera is always located in (org_x, org_y, -2500). This means
-          that different subtitle events can be displayed at the same time
-          using different cameras. */
-       b[0] *= 2500;
-       b[1] *= 2500;
-       b[3] = 8 * b[2] + 2500;
-       if (b[3] < 0.001 && b[3] > -0.001)
-               b[3] = b[3] < 0. ? -0.001 : 0.001;
-       v->x = double_to_d6(b[0] / b[3]);
-       v->y = double_to_d6(b[1] / b[3]);
-}
-
-/**
- * \brief Apply 3d transformation to a glyph
- * \param glyph FreeType glyph
- * \param m 4-matrix
- * Transforms glyph by m, projects the result back to the screen plane
- * Result is returned in glyph.
+ * \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 inline void transform_glyph_3d(FT_Glyph glyph, double *m, FT_Vector shift) {
-       int i;
-       FT_Outline* outline = &((FT_OutlineGlyph)glyph)->outline;
+static void transform_3d_points(FT_Vector shift, FT_Glyph glyph, double frx, double fry, double frz) {
+       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;
 
        for (i=0; i<outline->n_points; i++) {
-               p[i].x += shift.x;
-               p[i].y += shift.y;
-               transform_vector_3d(p + i, m);
-               p[i].x -= shift.x;
-               p[i].y -= shift.y;
-       }
+               x = p[i].x + shift.x;
+               y = p[i].y + shift.y;
+               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;
 
-       //transform_vector_3d(&glyph->advance, m);
+               xx = x*cy + z*sy;
+               yy = y;
+               zz = x*sy - z*cy;
+
+               zz = FFMAX(zz, -19000);
+
+               x = (xx * 20000) / (zz + 20000);
+               y = (yy * 20000) / (zz + 20000);
+               p[i].x = x - shift.x + 0.5;
+               p[i].y = y - shift.y + 0.5;
+       }
 }
 
 /**
@@ -1666,38 +1858,30 @@ static inline void transform_glyph_3d(FT_Glyph glyph, double *m, FT_Vector shift
  * \param frz z-axis rotation angle
  * Rotates both glyphs by frx, fry and frz. Shift vector is added before rotation and subtracted after it.
  */
-void transform_3d(FT_Vector shift, FT_Glyph* glyph, FT_Glyph* glyph2, double frx, double fry, double frz)
+static void transform_3d(FT_Vector shift, FT_Glyph* glyph, FT_Glyph* glyph2, double frx, double fry, double frz)
 {
+       frx = - frx;
+       frz = - frz;
        if (frx != 0. || fry != 0. || frz != 0.) {
-               double m[16];
-               double sx = sin(frx);
-               double sy = sin(fry);
-               double sz = sin(frz);
-               double cx = cos(frx);
-               double cy = cos(fry);
-               double cz = cos(frz);
-               m[0] = cy * cz;   m[1] = cz*sx*sy + sz*cx;   m[2]  = sz*sx - cz*cx*sy;   m[3] = 0.0;
-               m[4] = -sz*cy;    m[5] = cz*cx - sz*sy*sx;   m[6]  = sz*sy*cx + cz*sx;   m[7] = 0.0;
-               m[8] = sy;        m[9] = -sx*cy;             m[10] = cx*cy;              m[11] = 0.0;
-               m[12] = 0.0;      m[13] = 0.0;               m[14] = 0.0;                m[15] = 1.0;
-
                if (glyph && *glyph)
-                       transform_glyph_3d(*glyph, m, shift);
+                       transform_3d_points(shift, *glyph, frx, fry, frz);
 
                if (glyph2 && *glyph2)
-                       transform_glyph_3d(*glyph2, m, shift);
+                       transform_3d_points(shift, *glyph2, frx, fry, frz);
        }
 }
 
+
 /**
  * \brief Main ass rendering function, glues everything together
  * \param event event to render
+ * \param event_images struct containing resulting images, will also be initialized
  * Process event, appending resulting ass_image_t's to images_root.
  */
 static int ass_render_event(ass_event_t* event, event_images_t* event_images)
 {
        char* p;
-       FT_UInt previous; 
+       FT_UInt previous;
        FT_UInt num_glyphs;
        FT_Vector pen;
        unsigned code;
@@ -1730,8 +1914,10 @@ static int ass_render_event(ass_event_t* event, event_images_t* event_images)
        while (1) {
                // get next char, executing style override
                // this affects render_context
-               code = get_next_char(&p);
-               
+               do {
+                       code = get_next_char(&p);
+               } while (code && render_context.drawing_mode); // skip everything in drawing mode
+
                // face could have been changed in get_next_char
                if (!render_context.font) {
                        free_render_context();
@@ -1742,7 +1928,7 @@ static int ass_render_event(ass_event_t* event, event_images_t* event_images)
                        break;
 
                if (text_info.length >= MAX_GLYPHS) {
-                       mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_MAX_GLYPHS_Reached, 
+                       mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_MAX_GLYPHS_Reached,
                                        (int)(event - frame_context.track->events), event->Start, event->Duration, event->Text);
                        break;
                }
@@ -1754,33 +1940,28 @@ static int ass_render_event(ass_event_t* event, event_images_t* event_images)
                        pen.y += delta.y * render_context.scale_y;
                }
 
-               shift.x = pen.x & 63;
-               shift.y = pen.y & 63;
+               shift.x = pen.x & SUBPIXEL_MASK;
+               shift.y = pen.y & SUBPIXEL_MASK;
 
-               {
-                       FT_Matrix matrix;
-                       matrix.xx = (FT_Fixed)( render_context.scale_x * frame_context.font_scale_x * 0x10000L );
-                       matrix.xy = (FT_Fixed)( 0 * 0x10000L );
-                       matrix.yx = (FT_Fixed)( 0 * 0x10000L );
-                       matrix.yy = (FT_Fixed)( render_context.scale_y * 0x10000L );
-
-                       ass_font_set_transform(render_context.font, &matrix, &shift );
+               if (render_context.evt_type == EVENT_POSITIONED) {
+                       shift.x += double_to_d6(x2scr_pos(render_context.pos_x)) & SUBPIXEL_MASK;
+                       shift.y -= double_to_d6(y2scr_pos(render_context.pos_y)) & SUBPIXEL_MASK;
                }
 
-               error = get_outline_glyph(code, text_info.glyphs + text_info.length, &shift);
-               error |= get_bitmap_glyph(code, text_info.glyphs + text_info.length, &shift);
+               ass_font_set_transform(render_context.font,
+                                      render_context.scale_x * frame_context.font_scale_x,
+                                      render_context.scale_y,
+                                      &shift );
+
+               get_outline_glyph(code, text_info.glyphs + text_info.length, &shift);
 
-               if (error) {
-                       continue;
-               }
-               
                text_info.glyphs[text_info.length].pos.x = pen.x >> 6;
                text_info.glyphs[text_info.length].pos.y = pen.y >> 6;
-               
+
                pen.x += text_info.glyphs[text_info.length].advance.x;
                pen.x += double_to_d6(render_context.hspacing);
                pen.y += text_info.glyphs[text_info.length].advance.y;
-               
+
                previous = code;
 
                text_info.glyphs[text_info.length].symbol = code;
@@ -1794,6 +1975,7 @@ static int ass_render_event(ass_event_t* event, event_images_t* event_images)
                text_info.glyphs[text_info.length].effect_timing = render_context.effect_timing;
                text_info.glyphs[text_info.length].effect_skip_timing = render_context.effect_skip_timing;
                text_info.glyphs[text_info.length].be = render_context.be;
+               text_info.glyphs[text_info.length].blur = render_context.blur;
                text_info.glyphs[text_info.length].shadow = render_context.shadow;
                text_info.glyphs[text_info.length].frx = render_context.frx;
                text_info.glyphs[text_info.length].fry = render_context.fry;
@@ -1804,29 +1986,45 @@ static int ass_render_event(ass_event_t* event, event_images_t* event_images)
                text_info.glyphs[text_info.length].asc *= render_context.scale_y;
                text_info.glyphs[text_info.length].desc *= render_context.scale_y;
 
+               // fill bitmap_hash_key
+               text_info.glyphs[text_info.length].hash_key.font = render_context.font;
+               text_info.glyphs[text_info.length].hash_key.size = render_context.font_size;
+               text_info.glyphs[text_info.length].hash_key.outline = render_context.border * 0xFFFF;
+               text_info.glyphs[text_info.length].hash_key.scale_x = render_context.scale_x * 0xFFFF;
+               text_info.glyphs[text_info.length].hash_key.scale_y = render_context.scale_y * 0xFFFF;
+               text_info.glyphs[text_info.length].hash_key.frx = render_context.frx * 0xFFFF;
+               text_info.glyphs[text_info.length].hash_key.fry = render_context.fry * 0xFFFF;
+               text_info.glyphs[text_info.length].hash_key.frz = render_context.frz * 0xFFFF;
+               text_info.glyphs[text_info.length].hash_key.bold = render_context.bold;
+               text_info.glyphs[text_info.length].hash_key.italic = render_context.italic;
+               text_info.glyphs[text_info.length].hash_key.ch = code;
+               text_info.glyphs[text_info.length].hash_key.advance = shift;
+               text_info.glyphs[text_info.length].hash_key.be = render_context.be;
+               text_info.glyphs[text_info.length].hash_key.blur = render_context.blur;
+
                text_info.length++;
 
                render_context.effect_type = EF_NONE;
                render_context.effect_timing = 0;
                render_context.effect_skip_timing = 0;
        }
-       
+
        if (text_info.length == 0) {
                // no valid symbols in the event; this can be smth like {comment}
                free_render_context();
                return 1;
        }
-       
+
        // depends on glyph x coordinates being monotonous, so it should be done before line wrap
        process_karaoke_effects();
-       
+
        // alignments
        alignment = render_context.alignment;
        halign = alignment & 3;
        valign = alignment & 12;
 
-       MarginL = (event->MarginL) ? event->MarginL : render_context.style->MarginL; 
-       MarginR = (event->MarginR) ? event->MarginR : render_context.style->MarginR; 
+       MarginL = (event->MarginL) ? event->MarginL : render_context.style->MarginL;
+       MarginR = (event->MarginR) ? event->MarginR : render_context.style->MarginR;
        MarginV = (event->MarginV) ? event->MarginV : render_context.style->MarginV;
 
        if (render_context.evt_type != EVENT_HSCROLL) {
@@ -1866,12 +2064,12 @@ static int ass_render_event(ass_event_t* event, event_images_t* event_images)
        } else { // render_context.evt_type == EVENT_HSCROLL
                measure_text();
        }
-       
+
        // determing text bounding box
        compute_string_bbox(&text_info, &bbox);
-       
+
        // determine device coordinates for text
-       
+
        // x coordinate for everything except positioned events
        if (render_context.evt_type == EVENT_NORMAL ||
            render_context.evt_type == EVENT_VSCROLL) {
@@ -1911,18 +2109,18 @@ static int ass_render_event(ass_event_t* event, event_images_t* event_images)
        if (render_context.evt_type == EVENT_POSITIONED) {
                int base_x = 0;
                int base_y = 0;
-               mp_msg(MSGT_ASS, MSGL_DBG2, "positioned event at %d, %d\n", render_context.pos_x, render_context.pos_y);
+               mp_msg(MSGT_ASS, MSGL_DBG2, "positioned event at %f, %f\n", render_context.pos_x, render_context.pos_y);
                get_base_point(bbox, alignment, &base_x, &base_y);
-               device_x = x2scr(render_context.pos_x) - base_x;
-               device_y = y2scr(render_context.pos_y) - base_y;
+               device_x = x2scr_pos(render_context.pos_x) - base_x;
+               device_y = y2scr_pos(render_context.pos_y) - base_y;
        }
-       
+
        // fix clip coordinates (they depend on alignment)
-       render_context.clip_x0 = x2scr(render_context.clip_x0);
-       render_context.clip_x1 = x2scr(render_context.clip_x1);
        if (render_context.evt_type == EVENT_NORMAL ||
            render_context.evt_type == EVENT_HSCROLL ||
            render_context.evt_type == EVENT_VSCROLL) {
+               render_context.clip_x0 = x2scr(render_context.clip_x0);
+               render_context.clip_x1 = x2scr(render_context.clip_x1);
                if (valign == VALIGN_TOP) {
                        render_context.clip_y0 = y2scr_top(render_context.clip_y0);
                        render_context.clip_y1 = y2scr_top(render_context.clip_y1);
@@ -1934,38 +2132,44 @@ static int ass_render_event(ass_event_t* event, event_images_t* event_images)
                        render_context.clip_y1 = y2scr_sub(render_context.clip_y1);
                }
        } else if (render_context.evt_type == EVENT_POSITIONED) {
-               render_context.clip_y0 = y2scr(render_context.clip_y0);
-               render_context.clip_y1 = y2scr(render_context.clip_y1);
+               render_context.clip_x0 = x2scr_pos(render_context.clip_x0);
+               render_context.clip_x1 = x2scr_pos(render_context.clip_x1);
+               render_context.clip_y0 = y2scr_pos(render_context.clip_y0);
+               render_context.clip_y1 = y2scr_pos(render_context.clip_y1);
        }
 
-       // rotate glyphs if needed
+       // calculate rotation parameters
        {
                FT_Vector center;
-               
+
                if (render_context.have_origin) {
                        center.x = x2scr(render_context.org_x);
                        center.y = y2scr(render_context.org_y);
                } else {
-                       int bx, by;
+                       int bx = 0, by = 0;
                        get_base_point(bbox, alignment, &bx, &by);
                        center.x = device_x + bx;
                        center.y = device_y + by;
                }
 
                for (i = 0; i < text_info.length; ++i) {
-                       FT_Vector start;
-                       FT_Vector start_old;
-                       FT_Vector shift;
                        glyph_info_t* info = text_info.glyphs + i;
 
-                       // calculating shift vector
-                       shift.x = int_to_d6(info->pos.x + device_x - center.x);
-                       shift.y = - int_to_d6(info->pos.y + device_y - center.y);
-
-                       transform_3d(shift, &info->glyph, &info->outline_glyph, info->frx, info->fry, info->frz);
+                       if (info->hash_key.frx || info->hash_key.fry || info->hash_key.frz) {
+                               info->hash_key.shift_x = info->pos.x + device_x - center.x;
+                               info->hash_key.shift_y = - (info->pos.y + device_y - center.y);
+                       } else {
+                               info->hash_key.shift_x = 0;
+                               info->hash_key.shift_y = 0;
+                       }
                }
        }
 
+       // convert glyphs to bitmaps
+       for (i = 0; i < text_info.length; ++i)
+               get_bitmap_glyph(text_info.glyphs + i);
+
+       memset(event_images, 0, sizeof(*event_images));
        event_images->top = device_y - d6_to_int(text_info.lines[0].asc);
        event_images->height = d6_to_int(text_info.height);
        event_images->detect_collisions = render_context.detect_collisions;
@@ -1974,7 +2178,7 @@ static int ass_render_event(ass_event_t* event, event_images_t* event_images)
        event_images->imgs = render_text(&text_info, device_x, device_y);
 
        free_render_context();
-       
+
        return 0;
 }
 
@@ -1994,7 +2198,9 @@ void ass_free_images(ass_image_t* img)
 static void ass_reconfigure(ass_renderer_t* priv)
 {
        priv->render_id = ++last_render_id;
+       ass_glyph_cache_reset();
        ass_bitmap_cache_reset();
+       ass_composite_cache_reset();
        ass_free_images(priv->prev_images_root);
        priv->prev_images_root = 0;
 }
@@ -2045,7 +2251,20 @@ void ass_set_font_scale(ass_renderer_t* priv, double font_scale)
        }
 }
 
-int ass_set_fonts(ass_renderer_t* priv, const char* default_font, const char* default_family)
+void ass_set_hinting(ass_renderer_t* priv, ass_hinting_t ht)
+{
+       if (priv->settings.hinting != ht) {
+               priv->settings.hinting = ht;
+               ass_reconfigure(priv);
+       }
+}
+
+void ass_set_line_spacing(ass_renderer_t* priv, double line_spacing)
+{
+       priv->settings.line_spacing = line_spacing;
+}
+
+static int ass_set_fonts_(ass_renderer_t* priv, const char* default_font, const char* default_family, int fc)
 {
        if (priv->settings.default_font)
                free(priv->settings.default_font);
@@ -2057,11 +2276,21 @@ int ass_set_fonts(ass_renderer_t* priv, const char* default_font, const char* de
 
        if (priv->fontconfig_priv)
                fontconfig_done(priv->fontconfig_priv);
-       priv->fontconfig_priv = fontconfig_init(priv->library, priv->ftlibrary, default_family, default_font);
+       priv->fontconfig_priv = fontconfig_init(priv->library, priv->ftlibrary, default_family, default_font, fc);
 
        return !!priv->fontconfig_priv;
 }
 
+int ass_set_fonts(ass_renderer_t* priv, const char* default_font, const char* default_family)
+{
+       return ass_set_fonts_(priv, default_font, default_family, 1);
+}
+
+int ass_set_fonts_nofc(ass_renderer_t* priv, const char* default_font, const char* default_family)
+{
+       return ass_set_fonts_(priv, default_font, default_family, 0);
+}
+
 /**
  * \brief Start a new frame
  */
@@ -2072,25 +2301,34 @@ static int ass_start_frame(ass_renderer_t *priv, ass_track_t* track, long long n
 
        if (!priv->settings.frame_width && !priv->settings.frame_height)
                return 1; // library not initialized
-       
+
+       if (track->n_events == 0)
+               return 1; // nothing to do
+
        frame_context.ass_priv = priv;
        frame_context.width = global_settings->frame_width;
        frame_context.height = global_settings->frame_height;
        frame_context.orig_width = global_settings->frame_width - global_settings->left_margin - global_settings->right_margin;
        frame_context.orig_height = global_settings->frame_height - global_settings->top_margin - global_settings->bottom_margin;
+       frame_context.orig_width_nocrop = global_settings->frame_width -
+               FFMAX(global_settings->left_margin, 0) -
+               FFMAX(global_settings->right_margin, 0);
+       frame_context.orig_height_nocrop = global_settings->frame_height -
+               FFMAX(global_settings->top_margin, 0) -
+               FFMAX(global_settings->bottom_margin, 0);
        frame_context.track = track;
        frame_context.time = now;
 
        ass_lazy_track_init();
-       
-       frame_context.font_scale = global_settings->font_size_coeff * ass_internal_font_size_coeff *
-                                  frame_context.orig_height / frame_context.track->PlayResY;
-       frame_context.border_scale = ((double)frame_context.orig_height) / frame_context.track->PlayResY;
 
-       if (frame_context.orig_width * track->PlayResY == frame_context.orig_height * track->PlayResX)
-               frame_context.font_scale_x = 1.;
+       frame_context.font_scale = global_settings->font_size_coeff *
+                                  frame_context.orig_height / frame_context.track->PlayResY;
+       if (frame_context.track->ScaledBorderAndShadow)
+               frame_context.border_scale = ((double)frame_context.orig_height) / frame_context.track->PlayResY;
        else
-               frame_context.font_scale_x = ((double)(frame_context.orig_width * track->PlayResY)) / (frame_context.orig_height * track->PlayResX);
+               frame_context.border_scale = 1.;
+
+       frame_context.font_scale_x = 1.;
 
        priv->prev_images_root = priv->images_root;
        priv->images_root = 0;
@@ -2192,7 +2430,7 @@ static int fit_segment(segment_t* s, segment_t* fixed, int* cnt, int dir)
        fixed[*cnt].b = s->b + shift;
        (*cnt)++;
        qsort(fixed, *cnt, sizeof(segment_t), cmp_segment);
-       
+
        return shift;
 }
 
@@ -2247,7 +2485,7 @@ static void fix_collisions(event_images_t* imgs, int cnt)
                        priv->top = imgs[i].top;
                        priv->height = imgs[i].height;
                }
-               
+
        }
 }
 
@@ -2320,7 +2558,7 @@ ass_image_t* ass_render_frame(ass_renderer_t *priv, ass_track_t* track, long lon
        int i, cnt, rc;
        event_images_t* last;
        ass_image_t** tail;
-       
+
        // init frame
        rc = ass_start_frame(priv, track, now);
        if (rc != 0)
@@ -2366,7 +2604,7 @@ ass_image_t* ass_render_frame(ass_renderer_t *priv, ass_track_t* track, long lon
 
        if (detect_change)
                *detect_change = ass_detect_change(priv);
-       
+
        // free the previous image list
        ass_free_images(priv->prev_images_root);
        priv->prev_images_root = 0;