Fix division by zero in "\t" parsing.
[mplayer.git] / libass / ass_render.c
index 67e8d71..f69c0c9 100644 (file)
@@ -112,7 +112,7 @@ typedef struct glyph_info_s {
        int shadow;
        double frx, fry, frz; // rotation
        
-       glyph_hash_key_t hash_key;
+       bitmap_hash_key_t hash_key;
 } glyph_info_t;
 
 typedef struct line_info_s {
@@ -252,6 +252,7 @@ ass_renderer_t* ass_renderer_init(ass_library_t* library)
        // images_root and related stuff is zero-filled in calloc
        
        ass_font_cache_init();
+       ass_bitmap_cache_init();
        ass_glyph_cache_init();
 
        text_info.glyphs = calloc(MAX_GLYPHS, sizeof(glyph_info_t));
@@ -266,6 +267,7 @@ ass_init_exit:
 void ass_renderer_done(ass_renderer_t* priv)
 {
        ass_font_cache_done();
+       ass_bitmap_cache_done();
        ass_glyph_cache_done();
        if (render_context.stroker) {
                FT_Stroker_Done(render_context.stroker);
@@ -386,12 +388,12 @@ 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;
        bitmap_t* bm;
-       glyph_hash_val_t hash_val;
+       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].glyph && text_info->glyphs[i].bm == 0) {
                        if ((text_info->glyphs[i].symbol == '\n') || (text_info->glyphs[i].symbol == 0))
                                continue;
                        error = glyph_to_bitmap(ass_renderer->synth_priv,
@@ -400,23 +402,23 @@ static ass_image_t* render_text(text_info_t* text_info, int dst_x, int dst_y)
                                        &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
-                       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_glyph(&(text_info->glyphs[i].hash_key), &hash_val);
-
+                       cache_add_bitmap(&(text_info->glyphs[i].hash_key), &hash_val);
                }
        }
 
        for (i = 0; i < text_info->length; ++i) {
+               if (text_info->glyphs[i].glyph)
+                       FT_Done_Glyph(text_info->glyphs[i].glyph);
+               if (text_info->glyphs[i].outline_glyph)
+                       FT_Done_Glyph(text_info->glyphs[i].outline_glyph);
+       }
+
+       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;
@@ -680,7 +682,7 @@ static unsigned interpolate_alpha(long long now,
        return a;
 }
 
-static void reset_render_context();
+static void reset_render_context(void);
 
 /**
  * \brief Parse style override tag.
@@ -774,26 +776,35 @@ static char* parse_tag(char* p, double pwr) {
                render_context.evt_type = EVENT_POSITIONED;
        } else if (mystrcmp(&p, "frx")) {
                double val;
-               mystrtod(&p, &val);
-               val *= M_PI / 180;
-               render_context.frx = val * pwr + render_context.frx * (1-pwr);
+               if (mystrtod(&p, &val)) {
+                       val *= M_PI / 180;
+                       render_context.frx = val * pwr + render_context.frx * (1-pwr);
+               } else
+                       render_context.frx = 0.;
        } else if (mystrcmp(&p, "fry")) {
                double val;
-               mystrtod(&p, &val);
-               val *= M_PI / 180;
-               render_context.fry = val * pwr + render_context.fry * (1-pwr);
+               if (mystrtod(&p, &val)) {
+                       val *= M_PI / 180;
+                       render_context.fry = val * pwr + render_context.fry * (1-pwr);
+               } else
+                       render_context.fry = 0.;
        } else if (mystrcmp(&p, "frz") || mystrcmp(&p, "fr")) {
                double val;
-               mystrtod(&p, &val);
-               val *= M_PI / 180;
-               render_context.frz = val * pwr + render_context.frz * (1-pwr);
+               if (mystrtod(&p, &val)) {
+                       val *= M_PI / 180;
+                       render_context.frz = val * pwr + render_context.frz * (1-pwr);
+               } else
+                       render_context.frz = M_PI * render_context.style->Angle / 180.;
        } else if (mystrcmp(&p, "fn")) {
                char* start = p;
                char* family;
                skip_all('\\');
-               family = malloc(p - start + 1);
-               strncpy(family, start, p - start);
-               family[p - start] = '\0';
+               if (p > start) {
+                       family = malloc(p - start + 1);
+                       strncpy(family, start, p - start);
+                       family[p - start] = '\0';
+               } else
+                       family = strdup(render_context.style->FontName);
                if (render_context.family)
                        free(render_context.family);
                render_context.family = family;
@@ -813,17 +824,23 @@ static char* parse_tag(char* p, double pwr) {
                }
                // FIXME: simplify
        } else if (mystrcmp(&p, "an")) {
-               int val = strtol(p, &p, 10);
-               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;
-               val = ((val - 1) % 3) + 1; // horizontal alignment
-               val += v*4;
-               mp_msg(MSGT_ASS, MSGL_DBG2, "align %d\n", val);
-               render_context.alignment = val;
+               int val;
+               if (mystrtoi(&p, 10, &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;
+                       val = ((val - 1) % 3) + 1; // horizontal alignment
+                       val += v*4;
+                       mp_msg(MSGT_ASS, MSGL_DBG2, "align %d\n", val);
+                       render_context.alignment = val;
+               } else
+                       render_context.alignment = render_context.style->Alignment;
        } else if (mystrcmp(&p, "a")) {
-               int val = strtol(p, &p, 10);
-               render_context.alignment = val;
+               int val;
+               if (mystrtoi(&p, 10, &val) && val)
+                       render_context.alignment = val;
+               else
+                       render_context.alignment = render_context.style->Alignment;
        } else if (mystrcmp(&p, "pos")) {
                int v1, v2;
                skip('(');
@@ -912,11 +929,14 @@ 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
@@ -979,17 +999,19 @@ static char* parse_tag(char* p, double pwr) {
                        render_context.be = 0;
        } else if (mystrcmp(&p, "b")) {
                int b;
-               if (mystrtoi(&p, 10, &b))
-                       render_context.bold = b;
-               else
-                       render_context.bold = - render_context.style->Bold;
+               if (mystrtoi(&p, 10, &b)) {
+                       if (pwr >= .5)
+                               render_context.bold = b;
+               } else
+                       render_context.bold = render_context.style->Bold;
                update_font();
        } else if (mystrcmp(&p, "i")) {
                int i;
-               if (mystrtoi(&p, 10, &i))
-                       render_context.italic = i;
-               else
-                       render_context.italic = - render_context.style->Italic;
+               if (mystrtoi(&p, 10, &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);
@@ -1151,8 +1173,8 @@ static void reset_render_context(void)
        if (render_context.family)
                free(render_context.family);
        render_context.family = strdup(render_context.style->FontName);
-       render_context.bold = render_context.style->Bold;
-       render_context.italic = render_context.style->Italic;
+       render_context.bold = render_context.style->Bold;
+       render_context.italic = render_context.style->Italic;
        update_font();
 
        change_border(-1.);
@@ -1178,7 +1200,7 @@ static void init_render_context(ass_event_t* event)
        reset_render_context();
 
        render_context.evt_type = EVENT_NORMAL;
-       render_context.alignment = 0;
+       render_context.alignment = render_context.style->Alignment;
        render_context.pos_x = 0;
        render_context.pos_y = 0;
        render_context.org_x = 0;
@@ -1201,6 +1223,57 @@ static void free_render_context(void)
 {
 }
 
+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;
+       key.font = render_context.font;
+       key.size = render_context.font_size;
+       key.ch = symbol;
+       key.scale_x = (render_context.scale_x * 0xFFFF);
+       key.scale_y = (render_context.scale_y * 0xFFFF);
+       key.advance = *advance;
+       key.bold = render_context.bold;
+       key.italic = render_context.italic;
+
+       info->glyph = info->outline_glyph = 0;
+
+       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);
+               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_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);
+       }
+}
+
 /**
  * \brief Get normal and outline glyphs from cache (if possible) or font face
  * \param index face glyph index
@@ -1209,63 +1282,20 @@ static void free_render_context(void)
  * \param advance advance vector of the extracted glyph
  * \return 0 on success
  */
-static int get_glyph(int symbol, glyph_info_t* info, FT_Vector* advance)
+static void get_bitmap_glyph(glyph_info_t* info)
 {
-       int error;
-       glyph_hash_val_t* val;
-       glyph_hash_key_t* key = &(info->hash_key);
+       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;
-
-       val = cache_find_glyph(key);
-//     val = 0;
+       val = cache_find_bitmap(key);
+/*     val = 0; */
        
        if (val) {
-               info->glyph = info->outline_glyph = 0;
                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;
-
-               return 0;
-       }
-
-       // not found, get a new outline glyph from face
-//     mp_msg(MSGT_ASS, MSGL_INFO, "miss, index = %d, symbol = %c, adv = (%d, %d)\n", index, symbol, advance->x, advance->y);
-
-       info->outline_glyph = 0;
-       info->bm = info->bm_o = info->bm_s = 0;
-       
-       info->glyph = ass_font_get_glyph(frame_context.ass_priv->fontconfig_priv, render_context.font, symbol);
-       if (!info->glyph)
-               return 0;
-
-       info->advance.x = d16_to_d6(info->glyph->advance.x);
-       info->advance.y = d16_to_d6(info->glyph->advance.y);
-
-       if (render_context.stroker) {
-               info->outline_glyph = info->glyph;
-               error = FT_Glyph_Stroke( &(info->outline_glyph), render_context.stroker, 0 ); // don't destroy original
-               if (error) {
-                       mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FT_Glyph_Stroke_Error, error);
-               }
-       }
-
-       return 0;
+       } else
+               info->bm = info->bm_o = info->bm_s = 0;
 }
 
 /**
@@ -1277,7 +1307,7 @@ static int get_glyph(int symbol, glyph_info_t* info, FT_Vector* advance)
  *   lines[].asc
  *   lines[].desc
  */
-static void measure_text()
+static void measure_text(void)
 {
        int cur_line = 0, max_asc = 0, max_desc = 0;
        int i;
@@ -1369,6 +1399,11 @@ static void wrap_lines_smart(int max_text_width)
                
                if (cur->symbol == ' ')
                        last_space = i;
+
+               // make sure the hard linebreak is not forgotten when
+               // there was a new soft linebreak just inserted
+               if (cur->symbol == '\n' && break_type == 1)
+                       i--;
        }
 #define DIFF(x,y) (((x) < (y)) ? (y - x) : (x - y))
        exit = 0;
@@ -1557,12 +1592,25 @@ static inline void transform_point_3d(double *a, double *m, double *b)
  * Result is returned in v.
  */
 static inline void transform_vector_3d(FT_Vector* v, double *m) {
+       const double camera = 2500 * frame_context.border_scale; // camera distance
        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] *= camera;
+       b[1] *= camera;
+       b[3] = 8 * b[2] + camera;
        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]);
@@ -1576,27 +1624,33 @@ static inline void transform_vector_3d(FT_Vector* v, double *m) {
  * Transforms glyph by m, projects the result back to the screen plane
  * Result is returned in glyph.
  */
-static inline void transform_glyph_3d(FT_Glyph glyph, double *m) {
+static inline void transform_glyph_3d(FT_Glyph glyph, double *m, FT_Vector shift) {
        int i;
        FT_Outline* outline = &((FT_OutlineGlyph)glyph)->outline;
+       FT_Vector* p = outline->points;
+
+       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;
+       }
 
-       for (i=0; i<outline->n_points; i++)
-               transform_vector_3d(outline->points + i, m);
-
-       transform_vector_3d(&glyph->advance, m);
+       //transform_vector_3d(&glyph->advance, m);
 }
 
 /**
  * \brief Apply 3d transformation to several objects
- * \param vec FreeType vector
+ * \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 the given vector and both glyphs by frx, fry and frz.
+ * Rotates both glyphs by frx, fry and frz. Shift vector is added before rotation and subtracted after it.
  */
-void transform_3d(FT_Vector* vec, FT_Glyph* glyph, FT_Glyph* glyph2, double frx, double fry, double frz)
+void transform_3d(FT_Vector shift, FT_Glyph* glyph, FT_Glyph* glyph2, double frx, double fry, double frz)
 {
        if (frx != 0. || fry != 0. || frz != 0.) {
                double m[16];
@@ -1612,13 +1666,10 @@ void transform_3d(FT_Vector* vec, FT_Glyph* glyph, FT_Glyph* glyph2, double frx,
                m[12] = 0.0;      m[13] = 0.0;               m[14] = 0.0;                m[15] = 1.0;
 
                if (glyph && *glyph)
-                       transform_glyph_3d(*glyph, m);
+                       transform_glyph_3d(*glyph, m, shift);
 
                if (glyph2 && *glyph2)
-                       transform_glyph_3d(*glyph2, m);
-
-               if (vec)
-                       transform_vector_3d(vec, m);
+                       transform_glyph_3d(*glyph2, m, shift);
        }
 }
 
@@ -1633,7 +1684,6 @@ static int ass_render_event(ass_event_t* event, event_images_t* event_images)
        FT_UInt previous; 
        FT_UInt num_glyphs;
        FT_Vector pen;
-       int error;
        unsigned code;
        FT_BBox bbox;
        int i, j;
@@ -1700,25 +1750,16 @@ static int ass_render_event(ass_event_t* event, event_images_t* event_images)
 
                        ass_font_set_transform(render_context.font, &matrix, &shift );
                }
-               
-               error = get_glyph(code, text_info.glyphs + text_info.length, &shift);
 
-               if (error) {
-                       continue;
-               }
+               get_outline_glyph(code, text_info.glyphs + text_info.length, &shift);
                
-               text_info.glyphs[text_info.length].pos.x = d6_to_int(pen.x);
-               text_info.glyphs[text_info.length].pos.y = d6_to_int(pen.y);
+               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;
                
-               // if it's an outline glyph, we still need to fill the bbox
-               if (text_info.glyphs[text_info.length].glyph) {
-                       FT_Glyph_Get_CBox( text_info.glyphs[text_info.length].glyph, FT_GLYPH_BBOX_PIXELS, &(text_info.glyphs[text_info.length].bbox) );
-               }
-
                previous = code;
 
                text_info.glyphs[text_info.length].symbol = code;
@@ -1742,6 +1783,21 @@ 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.length++;
 
                render_context.effect_type = EF_NONE;
@@ -1760,8 +1816,6 @@ static int ass_render_event(ass_event_t* event, event_images_t* event_images)
        
        // alignments
        alignment = render_context.alignment;
-       if (!alignment)
-               alignment = render_context.style->Alignment;
        halign = alignment & 3;
        valign = alignment & 12;
 
@@ -1782,16 +1836,16 @@ static int ass_render_event(ass_event_t* event, event_images_t* event_images)
                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) {
-                               int width, shift;
+                               int width, shift = 0;
                                glyph_info_t* first_glyph = text_info.glyphs + last_break + 1;
                                glyph_info_t* last_glyph = text_info.glyphs + i - 1;
 
                                while ((last_glyph > first_glyph) && ((last_glyph->symbol == '\n') || (last_glyph->symbol == 0)))
                                        last_glyph --;
 
-                               width = last_glyph->pos.x + last_glyph->bbox.xMax - first_glyph->pos.x - first_glyph->bbox.xMin;
-                               shift = - first_glyph->bbox.xMin; // now text line starts exactly at 0 (left margin)
+                               width = last_glyph->pos.x + d6_to_int(last_glyph->advance.x) - first_glyph->pos.x;
                                if (halign == HALIGN_LEFT) { // left aligned, no action
+                                       shift = 0;
                                } else if (halign == HALIGN_RIGHT) { // right aligned
                                        shift = max_text_width - width;
                                } else if (halign == HALIGN_CENTER) { // centered
@@ -1883,8 +1937,8 @@ static int ass_render_event(ass_event_t* event, event_images_t* event_images)
                FT_Vector center;
                
                if (render_context.have_origin) {
-                       center.x = render_context.org_x;
-                       center.y = render_context.org_y;
+                       center.x = x2scr(render_context.org_x);
+                       center.y = y2scr(render_context.org_y);
                } else {
                        int bx, by;
                        get_base_point(bbox, alignment, &bx, &by);
@@ -1893,25 +1947,25 @@ static int ass_render_event(ass_event_t* event, event_images_t* event_images)
                }
 
                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 = (position - center)*M - (position - center)
-                       start.x = int_to_d6(info->pos.x + device_x - center.x);
-                       start.y = - int_to_d6(info->pos.y + device_y - center.y);
-                       start_old.x = start.x;
-                       start_old.y = start.y;
-
-                       transform_3d(&start, &info->glyph, &info->outline_glyph, info->frx, info->fry, info->frz);
-                       
-                       start.x -= start_old.x;
-                       start.y -= start_old.y;
+                       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;
+                       }
+                       get_bitmap_glyph(info);
 
-                       info->pos.x += d6_to_int(start.x);
-                       info->pos.y -= d6_to_int(start.y);
+                       if (info->bm == 0) {
+                               // 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);
+                       }
                }
        }
 
@@ -1944,6 +1998,7 @@ static void ass_reconfigure(ass_renderer_t* priv)
 {
        priv->render_id = ++last_render_id;
        ass_glyph_cache_reset();
+       ass_bitmap_cache_reset();
        ass_free_images(priv->prev_images_root);
        priv->prev_images_root = 0;
 }
@@ -2055,10 +2110,6 @@ static int cmp_event_layer(const void* p1, const void* p2)
                return -1;
        if (e1->Layer > e2->Layer)
                return 1;
-       if (e1->Start < e2->Start)
-               return -1;
-       if (e1->Start > e2->Start)
-               return 1;
        if (e1->ReadOrder < e2->ReadOrder)
                return -1;
        if (e1->ReadOrder > e2->ReadOrder)