]> granicus.if.org Git - libass/blob - libass/ass_render.c
rasterizer: drop outlines with points at too large coordinates
[libass] / libass / ass_render.c
1 /*
2  * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
3  *
4  * This file is part of libass.
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18
19 #include "config.h"
20 #include "ass_compat.h"
21
22 #include <assert.h>
23 #include <math.h>
24 #include <string.h>
25 #include <stdbool.h>
26
27 #include "ass_render.h"
28 #include "ass_parse.h"
29 #include "ass_shaper.h"
30
31 #define MAX_GLYPHS_INITIAL 1024
32 #define MAX_LINES_INITIAL 64
33 #define MAX_BITMAPS_INITIAL 16
34 #define MAX_SUB_BITMAPS_INITIAL 64
35 #define SUBPIXEL_MASK 63
36 #define SUBPIXEL_ACCURACY 7
37
38
39 ASS_Renderer *ass_renderer_init(ASS_Library *library)
40 {
41     int error;
42     FT_Library ft;
43     ASS_Renderer *priv = 0;
44     int vmajor, vminor, vpatch;
45
46     error = FT_Init_FreeType(&ft);
47     if (error) {
48         ass_msg(library, MSGL_FATAL, "%s failed", "FT_Init_FreeType");
49         goto ass_init_exit;
50     }
51
52     FT_Library_Version(ft, &vmajor, &vminor, &vpatch);
53     ass_msg(library, MSGL_V, "Raster: FreeType %d.%d.%d",
54            vmajor, vminor, vpatch);
55
56     priv = calloc(1, sizeof(ASS_Renderer));
57     if (!priv) {
58         FT_Done_FreeType(ft);
59         goto ass_init_exit;
60     }
61
62     priv->library = library;
63     priv->ftlibrary = ft;
64     // images_root and related stuff is zero-filled in calloc
65
66 #if (defined(__i386__) || defined(__x86_64__)) && CONFIG_ASM
67     if (has_avx2())
68         priv->engine = &ass_bitmap_engine_avx2;
69     else if (has_sse2())
70         priv->engine = &ass_bitmap_engine_sse2;
71     else
72         priv->engine = &ass_bitmap_engine_c;
73 #else
74     priv->engine = &ass_bitmap_engine_c;
75 #endif
76
77 #if CONFIG_RASTERIZER
78     rasterizer_init(&priv->rasterizer, 16);
79 #endif
80
81     priv->cache.font_cache = ass_font_cache_create();
82     priv->cache.bitmap_cache = ass_bitmap_cache_create();
83     priv->cache.composite_cache = ass_composite_cache_create();
84     priv->cache.outline_cache = ass_outline_cache_create();
85     priv->cache.glyph_max = GLYPH_CACHE_MAX;
86     priv->cache.bitmap_max_size = BITMAP_CACHE_MAX_SIZE;
87     priv->cache.composite_max_size = COMPOSITE_CACHE_MAX_SIZE;
88
89     priv->text_info.max_bitmaps = MAX_BITMAPS_INITIAL;
90     priv->text_info.max_glyphs = MAX_GLYPHS_INITIAL;
91     priv->text_info.max_lines = MAX_LINES_INITIAL;
92     priv->text_info.n_bitmaps = 0;
93     priv->text_info.combined_bitmaps = calloc(MAX_BITMAPS_INITIAL, sizeof(CombinedBitmapInfo));
94     priv->text_info.glyphs = calloc(MAX_GLYPHS_INITIAL, sizeof(GlyphInfo));
95     priv->text_info.lines = calloc(MAX_LINES_INITIAL, sizeof(LineInfo));
96
97     priv->settings.font_size_coeff = 1.;
98     priv->settings.selective_style_overrides = ASS_OVERRIDE_BIT_SELECTIVE_FONT_SCALE;
99
100     priv->shaper = ass_shaper_new(0);
101     ass_shaper_info(library);
102 #ifdef CONFIG_HARFBUZZ
103     priv->settings.shaper = ASS_SHAPING_COMPLEX;
104 #else
105     priv->settings.shaper = ASS_SHAPING_SIMPLE;
106 #endif
107
108   ass_init_exit:
109     if (priv)
110         ass_msg(library, MSGL_V, "Initialized");
111     else
112         ass_msg(library, MSGL_ERR, "Initialization failed");
113
114     return priv;
115 }
116
117 static void free_list_clear(ASS_Renderer *render_priv)
118 {
119     if (render_priv->free_head) {
120         FreeList *item = render_priv->free_head;
121         while(item) {
122             FreeList *oi = item;
123             ass_aligned_free(item->object);
124             item = item->next;
125             free(oi);
126         }
127         render_priv->free_head = NULL;
128     }
129 }
130
131 void ass_renderer_done(ASS_Renderer *render_priv)
132 {
133     ass_cache_done(render_priv->cache.font_cache);
134     ass_cache_done(render_priv->cache.bitmap_cache);
135     ass_cache_done(render_priv->cache.composite_cache);
136     ass_cache_done(render_priv->cache.outline_cache);
137
138     ass_free_images(render_priv->images_root);
139     ass_free_images(render_priv->prev_images_root);
140
141 #if CONFIG_RASTERIZER
142     rasterizer_done(&render_priv->rasterizer);
143 #endif
144
145     if (render_priv->state.stroker) {
146         FT_Stroker_Done(render_priv->state.stroker);
147         render_priv->state.stroker = 0;
148     }
149     if (render_priv->fontselect)
150         ass_fontselect_free(render_priv->fontselect);
151     ass_shaper_free(render_priv->shaper);
152     if (render_priv->ftlibrary)
153         FT_Done_FreeType(render_priv->ftlibrary);
154     free(render_priv->eimg);
155     free(render_priv->text_info.glyphs);
156     free(render_priv->text_info.lines);
157
158     free(render_priv->text_info.combined_bitmaps);
159
160     free(render_priv->settings.default_font);
161     free(render_priv->settings.default_family);
162
163     free(render_priv->user_override_style.FontName);
164
165     free_list_clear(render_priv);
166     free(render_priv);
167 }
168
169 /**
170  * \brief Create a new ASS_Image
171  * Parameters are the same as ASS_Image fields.
172  */
173 static ASS_Image *my_draw_bitmap(unsigned char *bitmap, int bitmap_w,
174                                  int bitmap_h, int stride, int dst_x,
175                                  int dst_y, uint32_t color)
176 {
177     ASS_Image *img = malloc(sizeof(ASS_Image));
178
179     if (img) {
180         img->w = bitmap_w;
181         img->h = bitmap_h;
182         img->stride = stride;
183         img->bitmap = bitmap;
184         img->color = color;
185         img->dst_x = dst_x;
186         img->dst_y = dst_y;
187     }
188
189     return img;
190 }
191
192 /**
193  * \brief Mapping between script and screen coordinates
194  */
195 static double x2scr_pos(ASS_Renderer *render_priv, double x)
196 {
197     return x * render_priv->orig_width / render_priv->font_scale_x / render_priv->track->PlayResX +
198         render_priv->settings.left_margin;
199 }
200 static double x2scr(ASS_Renderer *render_priv, double x)
201 {
202     if (render_priv->state.explicit)
203         return x2scr_pos(render_priv, x);
204     return x * render_priv->orig_width_nocrop / render_priv->font_scale_x /
205         render_priv->track->PlayResX +
206         FFMAX(render_priv->settings.left_margin, 0);
207 }
208 static double x2scr_pos_scaled(ASS_Renderer *render_priv, double x)
209 {
210     return x * render_priv->orig_width / render_priv->track->PlayResX +
211         render_priv->settings.left_margin;
212 }
213 static double x2scr_scaled(ASS_Renderer *render_priv, double x)
214 {
215     if (render_priv->state.explicit)
216         return x2scr_pos_scaled(render_priv, x);
217     return x * render_priv->orig_width_nocrop /
218         render_priv->track->PlayResX +
219         FFMAX(render_priv->settings.left_margin, 0);
220 }
221 /**
222  * \brief Mapping between script and screen coordinates
223  */
224 static double y2scr_pos(ASS_Renderer *render_priv, double y)
225 {
226     return y * render_priv->orig_height / render_priv->track->PlayResY +
227         render_priv->settings.top_margin;
228 }
229 static double y2scr(ASS_Renderer *render_priv, double y)
230 {
231     if (render_priv->state.explicit)
232         return y2scr_pos(render_priv, y);
233     return y * render_priv->orig_height_nocrop /
234         render_priv->track->PlayResY +
235         FFMAX(render_priv->settings.top_margin, 0);
236 }
237
238 // the same for toptitles
239 static double y2scr_top(ASS_Renderer *render_priv, double y)
240 {
241     if (render_priv->state.explicit)
242         return y2scr_pos(render_priv, y);
243     if (render_priv->settings.use_margins)
244         return y * render_priv->orig_height_nocrop /
245             render_priv->track->PlayResY;
246     else
247         return y * render_priv->orig_height_nocrop /
248             render_priv->track->PlayResY +
249             FFMAX(render_priv->settings.top_margin, 0);
250 }
251 // the same for subtitles
252 static double y2scr_sub(ASS_Renderer *render_priv, double y)
253 {
254     if (render_priv->state.explicit)
255         return y2scr_pos(render_priv, y);
256     if (render_priv->settings.use_margins)
257         return y * render_priv->orig_height_nocrop /
258             render_priv->track->PlayResY +
259             FFMAX(render_priv->settings.top_margin, 0)
260             + FFMAX(render_priv->settings.bottom_margin, 0);
261     else
262         return y * render_priv->orig_height_nocrop /
263             render_priv->track->PlayResY +
264             FFMAX(render_priv->settings.top_margin, 0);
265 }
266
267 /*
268  * \brief Convert bitmap glyphs into ASS_Image list with inverse clipping
269  *
270  * Inverse clipping with the following strategy:
271  * - find rectangle from (x0, y0) to (cx0, y1)
272  * - find rectangle from (cx0, y0) to (cx1, cy0)
273  * - find rectangle from (cx0, cy1) to (cx1, y1)
274  * - find rectangle from (cx1, y0) to (x1, y1)
275  * These rectangles can be invalid and in this case are discarded.
276  * Afterwards, they are clipped against the screen coordinates.
277  * In an additional pass, the rectangles need to be split up left/right for
278  * karaoke effects.  This can result in a lot of bitmaps (6 to be exact).
279  */
280 static ASS_Image **render_glyph_i(ASS_Renderer *render_priv,
281                                   Bitmap *bm, int dst_x, int dst_y,
282                                   uint32_t color, uint32_t color2, int brk,
283                                   ASS_Image **tail, unsigned int type)
284 {
285     int i, j, x0, y0, x1, y1, cx0, cy0, cx1, cy1, sx, sy, zx, zy;
286     Rect r[4];
287     ASS_Image *img;
288
289     dst_x += bm->left;
290     dst_y += bm->top;
291
292     // we still need to clip against screen boundaries
293     zx = x2scr_pos_scaled(render_priv, 0);
294     zy = y2scr_pos(render_priv, 0);
295     sx = x2scr_pos_scaled(render_priv, render_priv->track->PlayResX);
296     sy = y2scr_pos(render_priv, render_priv->track->PlayResY);
297
298     x0 = 0;
299     y0 = 0;
300     x1 = bm->w;
301     y1 = bm->h;
302     cx0 = render_priv->state.clip_x0 - dst_x;
303     cy0 = render_priv->state.clip_y0 - dst_y;
304     cx1 = render_priv->state.clip_x1 - dst_x;
305     cy1 = render_priv->state.clip_y1 - dst_y;
306
307     // calculate rectangles and discard invalid ones while we're at it.
308     i = 0;
309     r[i].x0 = x0;
310     r[i].y0 = y0;
311     r[i].x1 = (cx0 > x1) ? x1 : cx0;
312     r[i].y1 = y1;
313     if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++;
314     r[i].x0 = (cx0 < 0) ? x0 : cx0;
315     r[i].y0 = y0;
316     r[i].x1 = (cx1 > x1) ? x1 : cx1;
317     r[i].y1 = (cy0 > y1) ? y1 : cy0;
318     if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++;
319     r[i].x0 = (cx0 < 0) ? x0 : cx0;
320     r[i].y0 = (cy1 < 0) ? y0 : cy1;
321     r[i].x1 = (cx1 > x1) ? x1 : cx1;
322     r[i].y1 = y1;
323     if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++;
324     r[i].x0 = (cx1 < 0) ? x0 : cx1;
325     r[i].y0 = y0;
326     r[i].x1 = x1;
327     r[i].y1 = y1;
328     if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++;
329
330     // clip each rectangle to screen coordinates
331     for (j = 0; j < i; j++) {
332         r[j].x0 = (r[j].x0 + dst_x < zx) ? zx - dst_x : r[j].x0;
333         r[j].y0 = (r[j].y0 + dst_y < zy) ? zy - dst_y : r[j].y0;
334         r[j].x1 = (r[j].x1 + dst_x > sx) ? sx - dst_x : r[j].x1;
335         r[j].y1 = (r[j].y1 + dst_y > sy) ? sy - dst_y : r[j].y1;
336     }
337
338     // draw the rectangles
339     for (j = 0; j < i; j++) {
340         int lbrk = brk;
341         // kick out rectangles that are invalid now
342         if (r[j].x1 <= r[j].x0 || r[j].y1 <= r[j].y0)
343             continue;
344         // split up into left and right for karaoke, if needed
345         if (lbrk > r[j].x0) {
346             if (lbrk > r[j].x1) lbrk = r[j].x1;
347             img = my_draw_bitmap(bm->buffer + r[j].y0 * bm->stride + r[j].x0,
348                 lbrk - r[j].x0, r[j].y1 - r[j].y0,
349                 bm->stride, dst_x + r[j].x0, dst_y + r[j].y0, color);
350             if (!img) break;
351             img->type = type;
352             *tail = img;
353             tail = &img->next;
354         }
355         if (lbrk < r[j].x1) {
356             if (lbrk < r[j].x0) lbrk = r[j].x0;
357             img = my_draw_bitmap(bm->buffer + r[j].y0 * bm->stride + lbrk,
358                 r[j].x1 - lbrk, r[j].y1 - r[j].y0,
359                 bm->stride, dst_x + lbrk, dst_y + r[j].y0, color2);
360             if (!img) break;
361             img->type = type;
362             *tail = img;
363             tail = &img->next;
364         }
365     }
366
367     return tail;
368 }
369
370 /**
371  * \brief convert bitmap glyph into ASS_Image struct(s)
372  * \param bit freetype bitmap glyph, FT_PIXEL_MODE_GRAY
373  * \param dst_x bitmap x coordinate in video frame
374  * \param dst_y bitmap y coordinate in video frame
375  * \param color first color, RGBA
376  * \param color2 second color, RGBA
377  * \param brk x coordinate relative to glyph origin, color is used to the left of brk, color2 - to the right
378  * \param tail pointer to the last image's next field, head of the generated list should be stored here
379  * \return pointer to the new list tail
380  * Performs clipping. Uses my_draw_bitmap for actual bitmap convertion.
381  */
382 static ASS_Image **
383 render_glyph(ASS_Renderer *render_priv, Bitmap *bm, int dst_x, int dst_y,
384              uint32_t color, uint32_t color2, int brk, ASS_Image **tail, unsigned int type)
385 {
386     // Inverse clipping in use?
387     if (render_priv->state.clip_mode)
388         return render_glyph_i(render_priv, bm, dst_x, dst_y, color, color2,
389                               brk, tail, type);
390
391     // brk is relative to dst_x
392     // color = color left of brk
393     // color2 = color right of brk
394     int b_x0, b_y0, b_x1, b_y1; // visible part of the bitmap
395     int clip_x0, clip_y0, clip_x1, clip_y1;
396     int tmp;
397     ASS_Image *img;
398
399     dst_x += bm->left;
400     dst_y += bm->top;
401     brk -= bm->left;
402
403     // clipping
404     clip_x0 = FFMINMAX(render_priv->state.clip_x0, 0, render_priv->width);
405     clip_y0 = FFMINMAX(render_priv->state.clip_y0, 0, render_priv->height);
406     clip_x1 = FFMINMAX(render_priv->state.clip_x1, 0, render_priv->width);
407     clip_y1 = FFMINMAX(render_priv->state.clip_y1, 0, render_priv->height);
408     b_x0 = 0;
409     b_y0 = 0;
410     b_x1 = bm->w;
411     b_y1 = bm->h;
412
413     tmp = dst_x - clip_x0;
414     if (tmp < 0) {
415         b_x0 = -tmp;
416         render_priv->state.has_clips = 1;
417     }
418     tmp = dst_y - clip_y0;
419     if (tmp < 0) {
420         b_y0 = -tmp;
421         render_priv->state.has_clips = 1;
422     }
423     tmp = clip_x1 - dst_x - bm->w;
424     if (tmp < 0) {
425         b_x1 = bm->w + tmp;
426         render_priv->state.has_clips = 1;
427     }
428     tmp = clip_y1 - dst_y - bm->h;
429     if (tmp < 0) {
430         b_y1 = bm->h + tmp;
431         render_priv->state.has_clips = 1;
432     }
433
434     if ((b_y0 >= b_y1) || (b_x0 >= b_x1))
435         return tail;
436
437     if (brk > b_x0) {           // draw left part
438         if (brk > b_x1)
439             brk = b_x1;
440         img = my_draw_bitmap(bm->buffer + bm->stride * b_y0 + b_x0,
441                              brk - b_x0, b_y1 - b_y0, bm->stride,
442                              dst_x + b_x0, dst_y + b_y0, color);
443         if (!img) return tail;
444         img->type = type;
445         *tail = img;
446         tail = &img->next;
447     }
448     if (brk < b_x1) {           // draw right part
449         if (brk < b_x0)
450             brk = b_x0;
451         img = my_draw_bitmap(bm->buffer + bm->stride * b_y0 + brk,
452                              b_x1 - brk, b_y1 - b_y0, bm->stride,
453                              dst_x + brk, dst_y + b_y0, color2);
454         if (!img) return tail;
455         img->type = type;
456         *tail = img;
457         tail = &img->next;
458     }
459     return tail;
460 }
461
462 // Return true if the object could be added, and the object is not NULL.
463 static bool free_list_add(ASS_Renderer *render_priv, void *object)
464 {
465     if (!object)
466         return false;
467     FreeList *l = calloc(1, sizeof(FreeList));
468     if (!l)
469         return false;
470     if (!render_priv->free_head) {
471         render_priv->free_head = l;
472         render_priv->free_head->object = object;
473         render_priv->free_tail = render_priv->free_head;
474     } else {
475         l->object = object;
476         render_priv->free_tail->next = l;
477         render_priv->free_tail = render_priv->free_tail->next;
478     }
479     return true;
480 }
481
482 /**
483  * Iterate through a list of bitmaps and blend with clip vector, if
484  * applicable. The blended bitmaps are added to a free list which is freed
485  * at the start of a new frame.
486  */
487 static void blend_vector_clip(ASS_Renderer *render_priv,
488                               ASS_Image *head)
489 {
490     Bitmap *clip_bm = NULL;
491     ASS_Image *cur;
492     ASS_Drawing *drawing = render_priv->state.clip_drawing;
493     BitmapHashKey key;
494     BitmapHashValue *val;
495
496     if (!drawing)
497         return;
498
499     // Try to get mask from cache
500     memset(&key, 0, sizeof(key));
501     key.type = BITMAP_CLIP;
502     key.u.clip.text = drawing->text;
503     val = ass_cache_get(render_priv->cache.bitmap_cache, &key);
504
505     if (val) {
506         clip_bm = val->bm;
507     } else {
508         BitmapHashValue v;
509
510         // Not found in cache, parse and rasterize it
511         ASS_Outline *outline = ass_drawing_parse(drawing, 1);
512         if (!outline) {
513             ass_msg(render_priv->library, MSGL_WARN,
514                     "Clip vector parsing failed. Skipping.");
515             return;
516         }
517
518         // We need to translate the clip according to screen borders
519         if (render_priv->settings.left_margin != 0 ||
520             render_priv->settings.top_margin != 0) {
521             FT_Vector trans = {
522                 .x = int_to_d6(render_priv->settings.left_margin),
523                 .y = -int_to_d6(render_priv->settings.top_margin),
524             };
525             outline_translate(outline, trans.x, trans.y);
526         }
527
528         clip_bm = outline_to_bitmap(render_priv, outline, 0);
529
530         // Add to cache
531         memset(&v, 0, sizeof(v));
532         key.u.clip.text = strdup(drawing->text);
533         v.bm = clip_bm;
534         ass_cache_put(render_priv->cache.bitmap_cache, &key, &v);
535     }
536
537     if (!clip_bm) return;
538
539     // Iterate through bitmaps and blend/clip them
540     for (cur = head; cur; cur = cur->next) {
541         int left, top, right, bottom, w, h;
542         int ax, ay, aw, ah, as;
543         int bx, by, bw, bh, bs;
544         int aleft, atop, bleft, btop;
545         unsigned char *abuffer, *bbuffer, *nbuffer;
546
547         render_priv->state.has_clips = 1;
548
549         abuffer = cur->bitmap;
550         bbuffer = clip_bm->buffer;
551         ax = cur->dst_x;
552         ay = cur->dst_y;
553         aw = cur->w;
554         ah = cur->h;
555         as = cur->stride;
556         bx = clip_bm->left;
557         by = clip_bm->top;
558         bw = clip_bm->w;
559         bh = clip_bm->h;
560         bs = clip_bm->stride;
561
562         // Calculate overlap coordinates
563         left = (ax > bx) ? ax : bx;
564         top = (ay > by) ? ay : by;
565         right = ((ax + aw) < (bx + bw)) ? (ax + aw) : (bx + bw);
566         bottom = ((ay + ah) < (by + bh)) ? (ay + ah) : (by + bh);
567         aleft = left - ax;
568         atop = top - ay;
569         w = right - left;
570         h = bottom - top;
571         bleft = left - bx;
572         btop = top - by;
573
574         if (render_priv->state.clip_drawing_mode) {
575             // Inverse clip
576             if (ax + aw < bx || ay + ah < by || ax > bx + bw ||
577                 ay > by + bh || !h || !w) {
578                 continue;
579             }
580
581             // Allocate new buffer and add to free list
582             nbuffer = ass_aligned_alloc(32, as * ah);
583             if (!free_list_add(render_priv, nbuffer)) {
584                 ass_aligned_free(nbuffer);
585                 return;
586             }
587
588             // Blend together
589             memcpy(nbuffer, abuffer, ((ah - 1) * as) + aw);
590             render_priv->engine->sub_bitmaps(nbuffer + atop * as + aleft, as,
591                                              bbuffer + btop * bs + bleft, bs,
592                                              h, w);
593         } else {
594             // Regular clip
595             if (ax + aw < bx || ay + ah < by || ax > bx + bw ||
596                 ay > by + bh || !h || !w) {
597                 cur->w = cur->h = cur->stride = 0;
598                 continue;
599             }
600
601             // Allocate new buffer and add to free list
602             unsigned align = (w >= 16) ? 16 : ((w >= 8) ? 8 : 1);
603             unsigned ns = ass_align(align, w);
604             nbuffer = ass_aligned_alloc(align, ns * h);
605             if (!free_list_add(render_priv, nbuffer)) {
606                 ass_aligned_free(nbuffer);
607                 return;
608             }
609
610             // Blend together
611             render_priv->engine->mul_bitmaps(nbuffer, ns,
612                                              abuffer + atop * as + aleft, as,
613                                              bbuffer + btop * bs + bleft, bs,
614                                              w, h);
615             cur->dst_x += aleft;
616             cur->dst_y += atop;
617             cur->w = w;
618             cur->h = h;
619             cur->stride = ns;
620         }
621         cur->bitmap = nbuffer;
622     }
623 }
624
625 /**
626  * \brief Convert TextInfo struct to ASS_Image list
627  * Splits glyphs in halves when needed (for \kf karaoke).
628  */
629 static ASS_Image *render_text(ASS_Renderer *render_priv)
630 {
631     ASS_Image *head;
632     ASS_Image **tail = &head;
633     TextInfo *text_info = &render_priv->text_info;
634
635     for (int i = 0; i < text_info->n_bitmaps; ++i) {
636         CombinedBitmapInfo *info = &text_info->combined_bitmaps[i];
637         if (!info->bm_s || render_priv->state.border_style == 4)
638             continue;
639
640         tail =
641             render_glyph(render_priv, info->bm_s, info->x, info->y, info->c[3], 0,
642                          1000000, tail, IMAGE_TYPE_SHADOW);
643     }
644
645     for (int i = 0; i < text_info->n_bitmaps; ++i) {
646         CombinedBitmapInfo *info = &text_info->combined_bitmaps[i];
647         if (!info->bm_o)
648             continue;
649
650         if ((info->effect_type == EF_KARAOKE_KO)
651                 && (info->effect_timing <= info->first_pos_x)) {
652             // do nothing
653         } else {
654             tail =
655                 render_glyph(render_priv, info->bm_o, info->x, info->y, info->c[2],
656                              0, 1000000, tail, IMAGE_TYPE_OUTLINE);
657         }
658     }
659
660     for (int i = 0; i < text_info->n_bitmaps; ++i) {
661         CombinedBitmapInfo *info = &text_info->combined_bitmaps[i];
662         if (!info->bm)
663             continue;
664
665         if ((info->effect_type == EF_KARAOKE)
666                 || (info->effect_type == EF_KARAOKE_KO)) {
667             if (info->effect_timing > info->first_pos_x)
668                 tail =
669                     render_glyph(render_priv, info->bm, info->x, info->y,
670                                  info->c[0], 0, 1000000, tail, IMAGE_TYPE_CHARACTER);
671             else
672                 tail =
673                     render_glyph(render_priv, info->bm, info->x, info->y,
674                                  info->c[1], 0, 1000000, tail, IMAGE_TYPE_CHARACTER);
675         } else if (info->effect_type == EF_KARAOKE_KF) {
676             tail =
677                 render_glyph(render_priv, info->bm, info->x, info->y, info->c[0],
678                              info->c[1], info->effect_timing, tail, IMAGE_TYPE_CHARACTER);
679         } else
680             tail =
681                 render_glyph(render_priv, info->bm, info->x, info->y, info->c[0],
682                              0, 1000000, tail, IMAGE_TYPE_CHARACTER);
683     }
684
685     *tail = 0;
686     blend_vector_clip(render_priv, head);
687
688     return head;
689 }
690
691 static void compute_string_bbox(TextInfo *text, DBBox *bbox)
692 {
693     int i;
694
695     if (text->length > 0) {
696         bbox->xMin = 32000;
697         bbox->xMax = -32000;
698         bbox->yMin = -1 * text->lines[0].asc + d6_to_double(text->glyphs[0].pos.y);
699         bbox->yMax = text->height - text->lines[0].asc +
700                      d6_to_double(text->glyphs[0].pos.y);
701
702         for (i = 0; i < text->length; ++i) {
703             GlyphInfo *info = text->glyphs + i;
704             if (info->skip) continue;
705             double s = d6_to_double(info->pos.x);
706             double e = s + d6_to_double(info->cluster_advance.x);
707             bbox->xMin = FFMIN(bbox->xMin, s);
708             bbox->xMax = FFMAX(bbox->xMax, e);
709         }
710     } else
711         bbox->xMin = bbox->xMax = bbox->yMin = bbox->yMax = 0.;
712 }
713
714 static ASS_Style *handle_selective_style_overrides(ASS_Renderer *render_priv,
715                                                    ASS_Style *rstyle)
716 {
717     // The script style is the one the event was declared with.
718     ASS_Style *script = render_priv->track->styles +
719                         render_priv->state.event->Style;
720     // The user style was set with ass_set_selective_style_override().
721     ASS_Style *user = &render_priv->user_override_style;
722     ASS_Style *new = &render_priv->state.override_style_temp_storage;
723     int explicit = event_has_hard_overrides(render_priv->state.event->Text) ||
724                    render_priv->state.evt_type != EVENT_NORMAL;
725     int requested = render_priv->settings.selective_style_overrides;
726     double scale;
727
728     user->Name = "OverrideStyle"; // name insignificant
729
730     // Either the event's style, or the style forced with a \r tag.
731     if (!rstyle)
732         rstyle = script;
733
734     // Create a new style that contains a mix of the original style and
735     // user_style (the user's override style). Copy only fields from the
736     // script's style that are deemed necessary.
737     *new = *rstyle;
738
739     render_priv->state.explicit = explicit;
740
741     render_priv->state.apply_font_scale =
742         !explicit || !(requested & ASS_OVERRIDE_BIT_SELECTIVE_FONT_SCALE);
743
744     // On positioned events, do not apply most overrides.
745     if (explicit)
746         requested = 0;
747
748     if (requested & ASS_OVERRIDE_BIT_STYLE)
749         requested |= ASS_OVERRIDE_BIT_FONT_NAME |
750                      ASS_OVERRIDE_BIT_FONT_SIZE_FIELDS |
751                      ASS_OVERRIDE_BIT_COLORS |
752                      ASS_OVERRIDE_BIT_BORDER |
753                      ASS_OVERRIDE_BIT_ATTRIBUTES;
754
755     // Copies fields even not covered by any of the other bits.
756     if (requested & ASS_OVERRIDE_FULL_STYLE)
757         *new = *user;
758
759     // The user style is supposed to be independent of the script resolution.
760     // Treat the user style's values as if they were specified for a script with
761     // PlayResY=288, and rescale the values to the current script.
762     scale = render_priv->track->PlayResY / 288.0;
763
764     if (requested & ASS_OVERRIDE_BIT_FONT_SIZE_FIELDS) {
765         new->FontSize = user->FontSize * scale;
766         new->Spacing = user->Spacing * scale;
767         new->ScaleX = user->ScaleX;
768         new->ScaleY = user->ScaleY;
769     }
770
771     if (requested & ASS_OVERRIDE_BIT_FONT_NAME) {
772         new->FontName = user->FontName;
773         new->treat_fontname_as_pattern = user->treat_fontname_as_pattern;
774     }
775
776     if (requested & ASS_OVERRIDE_BIT_COLORS) {
777         new->PrimaryColour = user->PrimaryColour;
778         new->SecondaryColour = user->SecondaryColour;
779         new->OutlineColour = user->OutlineColour;
780         new->BackColour = user->BackColour;
781     }
782
783     if (requested & ASS_OVERRIDE_BIT_ATTRIBUTES) {
784         new->Bold = user->Bold;
785         new->Italic = user->Italic;
786         new->Underline = user->Underline;
787         new->StrikeOut = user->StrikeOut;
788     }
789
790     if (requested & ASS_OVERRIDE_BIT_BORDER) {
791         new->BorderStyle = user->BorderStyle;
792         new->Outline = user->Outline * scale;
793         new->Shadow = user->Shadow * scale;
794     }
795
796     if (requested & ASS_OVERRIDE_BIT_ALIGNMENT)
797         new->Alignment = user->Alignment;
798
799     if (requested & ASS_OVERRIDE_BIT_MARGINS) {
800         new->MarginL = user->MarginL;
801         new->MarginR = user->MarginR;
802         new->MarginV = user->MarginV;
803     }
804
805     if (!new->FontName)
806         new->FontName = rstyle->FontName;
807
808     render_priv->state.style = new;
809     render_priv->state.overrides = requested;
810
811     return new;
812 }
813
814 static void init_font_scale(ASS_Renderer *render_priv)
815 {
816     ASS_Settings *settings_priv = &render_priv->settings;
817
818     render_priv->font_scale = ((double) render_priv->orig_height) /
819                               render_priv->track->PlayResY;
820     if (settings_priv->storage_height)
821         render_priv->blur_scale = ((double) render_priv->orig_height) /
822             settings_priv->storage_height;
823     else
824         render_priv->blur_scale = 1.;
825     if (render_priv->track->ScaledBorderAndShadow)
826         render_priv->border_scale =
827             ((double) render_priv->orig_height) /
828             render_priv->track->PlayResY;
829     else
830         render_priv->border_scale = render_priv->blur_scale;
831     if (!settings_priv->storage_height)
832         render_priv->blur_scale = render_priv->border_scale;
833
834     if (render_priv->state.apply_font_scale) {
835         render_priv->font_scale *= settings_priv->font_size_coeff;
836         render_priv->border_scale *= settings_priv->font_size_coeff;
837         render_priv->blur_scale *= settings_priv->font_size_coeff;
838     }
839 }
840
841 /**
842  * \brief partially reset render_context to style values
843  * Works like {\r}: resets some style overrides
844  */
845 void reset_render_context(ASS_Renderer *render_priv, ASS_Style *style)
846 {
847     style = handle_selective_style_overrides(render_priv, style);
848
849     init_font_scale(render_priv);
850
851     render_priv->state.c[0] = style->PrimaryColour;
852     render_priv->state.c[1] = style->SecondaryColour;
853     render_priv->state.c[2] = style->OutlineColour;
854     render_priv->state.c[3] = style->BackColour;
855     render_priv->state.flags =
856         (style->Underline ? DECO_UNDERLINE : 0) |
857         (style->StrikeOut ? DECO_STRIKETHROUGH : 0);
858     render_priv->state.font_size = style->FontSize;
859
860     free(render_priv->state.family);
861     render_priv->state.family = NULL;
862     render_priv->state.family = strdup(style->FontName);
863     render_priv->state.treat_family_as_pattern =
864         style->treat_fontname_as_pattern;
865     render_priv->state.bold = style->Bold;
866     render_priv->state.italic = style->Italic;
867     update_font(render_priv);
868
869     render_priv->state.border_style = style->BorderStyle;
870     render_priv->state.border_x = style->Outline;
871     render_priv->state.border_y = style->Outline;
872     change_border(render_priv, render_priv->state.border_x, render_priv->state.border_y);
873     render_priv->state.scale_x = style->ScaleX;
874     render_priv->state.scale_y = style->ScaleY;
875     render_priv->state.hspacing = style->Spacing;
876     render_priv->state.be = 0;
877     render_priv->state.blur = style->Blur;
878     render_priv->state.shadow_x = style->Shadow;
879     render_priv->state.shadow_y = style->Shadow;
880     render_priv->state.frx = render_priv->state.fry = 0.;
881     render_priv->state.frz = M_PI * style->Angle / 180.;
882     render_priv->state.fax = render_priv->state.fay = 0.;
883     render_priv->state.font_encoding = style->Encoding;
884 }
885
886 /**
887  * \brief Start new event. Reset render_priv->state.
888  */
889 static void
890 init_render_context(ASS_Renderer *render_priv, ASS_Event *event)
891 {
892     render_priv->state.event = event;
893     render_priv->state.parsed_tags = 0;
894     render_priv->state.has_clips = 0;
895     render_priv->state.evt_type = EVENT_NORMAL;
896
897     reset_render_context(render_priv, NULL);
898     render_priv->state.wrap_style = render_priv->track->WrapStyle;
899
900     render_priv->state.alignment = render_priv->state.style->Alignment;
901     render_priv->state.pos_x = 0;
902     render_priv->state.pos_y = 0;
903     render_priv->state.org_x = 0;
904     render_priv->state.org_y = 0;
905     render_priv->state.have_origin = 0;
906     render_priv->state.clip_x0 = 0;
907     render_priv->state.clip_y0 = 0;
908     render_priv->state.clip_x1 = render_priv->track->PlayResX;
909     render_priv->state.clip_y1 = render_priv->track->PlayResY;
910     render_priv->state.clip_mode = 0;
911     render_priv->state.detect_collisions = 1;
912     render_priv->state.fade = 0;
913     render_priv->state.drawing_scale = 0;
914     render_priv->state.pbo = 0;
915     render_priv->state.effect_type = EF_NONE;
916     render_priv->state.effect_timing = 0;
917     render_priv->state.effect_skip_timing = 0;
918
919     apply_transition_effects(render_priv, event);
920 }
921
922 static void free_render_context(ASS_Renderer *render_priv)
923 {
924     free(render_priv->state.family);
925     ass_drawing_free(render_priv->state.clip_drawing);
926
927     render_priv->state.family = NULL;
928     render_priv->state.clip_drawing = NULL;
929
930     TextInfo *text_info = &render_priv->text_info;
931     for (int n = 0; n < text_info->length; n++)
932         ass_drawing_free(text_info->glyphs[n].drawing);
933     text_info->length = 0;
934 }
935
936 /*
937  * Replace the outline of a glyph by a contour which makes up a simple
938  * opaque rectangle.
939  */
940 static void draw_opaque_box(ASS_Renderer *render_priv, GlyphInfo *info,
941                             int asc, int desc, ASS_Outline *ol,
942                             FT_Vector advance, int sx, int sy)
943 {
944     int adv = advance.x;
945     double scale_y = info->orig_scale_y;
946     double scale_x = info->orig_scale_x;
947
948     // to avoid gaps
949     sx = FFMAX(64, sx);
950     sy = FFMAX(64, sy);
951
952     // Emulate the WTFish behavior of VSFilter, i.e. double-scale
953     // the sizes of the opaque box.
954     adv += double_to_d6(info->hspacing * render_priv->font_scale * scale_x);
955     adv *= scale_x;
956     sx *= scale_x;
957     sy *= scale_y;
958     desc *= scale_y;
959     desc += asc * (scale_y - 1.0);
960
961     FT_Vector points[4] = {
962         { .x = -sx,         .y = asc + sy },
963         { .x = adv + sx,    .y = asc + sy },
964         { .x = adv + sx,    .y = -desc - sy },
965         { .x = -sx,         .y = -desc - sy },
966     };
967
968     ol->n_points = ol->n_contours = 0;
969     if (!outline_alloc(ol, 4, 1))
970         return;
971     for (int i = 0; i < 4; ++i) {
972         ol->points[ol->n_points] = points[i];
973         ol->tags[ol->n_points++] = 1;
974     }
975     ol->contours[ol->n_contours++] = ol->n_points - 1;
976 }
977
978 /*
979  * Stroke an outline glyph in x/y direction.  Applies various fixups to get
980  * around limitations of the FreeType stroker.
981  */
982 static void stroke_outline(ASS_Renderer *render_priv, ASS_Outline *outline,
983                            int sx, int sy)
984 {
985     if (sx <= 0 && sy <= 0)
986         return;
987
988     fix_freetype_stroker(outline, sx, sy);
989
990     size_t n_points = outline->n_points;
991     if (n_points > SHRT_MAX) {
992         ass_msg(render_priv->library, MSGL_WARN, "Too many outline points: %d",
993                 outline->n_points);
994         n_points = SHRT_MAX;
995     }
996
997     size_t n_contours = FFMIN(outline->n_contours, SHRT_MAX);
998     short contours_small[EFFICIENT_CONTOUR_COUNT];
999     short *contours = contours_small;
1000     short *contours_large = NULL;
1001     if (n_contours > EFFICIENT_CONTOUR_COUNT) {
1002         contours_large = malloc(n_contours * sizeof(short));
1003         if (!contours_large)
1004             return;
1005         contours = contours_large;
1006     }
1007     for (size_t i = 0; i < n_contours; ++i)
1008         contours[i] = FFMIN(outline->contours[i], n_points - 1);
1009
1010     FT_Outline ftol;
1011     ftol.n_points = n_points;
1012     ftol.n_contours = n_contours;
1013     ftol.points = outline->points;
1014     ftol.tags = outline->tags;
1015     ftol.contours = contours;
1016     ftol.flags = 0;
1017
1018     // Borders are equal; use the regular stroker
1019     if (sx == sy && render_priv->state.stroker) {
1020         int error;
1021         FT_StrokerBorder border = FT_Outline_GetOutsideBorder(&ftol);
1022         error = FT_Stroker_ParseOutline(render_priv->state.stroker, &ftol, 0);
1023         if (error) {
1024             ass_msg(render_priv->library, MSGL_WARN,
1025                     "FT_Stroker_ParseOutline failed, error: %d", error);
1026         }
1027         unsigned new_points, new_contours;
1028         error = FT_Stroker_GetBorderCounts(render_priv->state.stroker, border,
1029                 &new_points, &new_contours);
1030         if (error) {
1031             ass_msg(render_priv->library, MSGL_WARN,
1032                     "FT_Stroker_GetBorderCounts failed, error: %d", error);
1033         }
1034         outline_free(outline);
1035         outline->n_points = outline->n_contours = 0;
1036         if (new_contours > FFMAX(EFFICIENT_CONTOUR_COUNT, n_contours)) {
1037             if (!ASS_REALLOC_ARRAY(contours_large, new_contours)) {
1038                 free(contours_large);
1039                 return;
1040             }
1041         }
1042         n_points = new_points;
1043         n_contours = new_contours;
1044         if (!outline_alloc(outline, n_points, n_contours)) {
1045             ass_msg(render_priv->library, MSGL_WARN,
1046                     "Not enough memory for border outline");
1047             free(contours_large);
1048             return;
1049         }
1050         ftol.n_points = ftol.n_contours = 0;
1051         ftol.points = outline->points;
1052         ftol.tags = outline->tags;
1053
1054         FT_Stroker_ExportBorder(render_priv->state.stroker, border, &ftol);
1055
1056         outline->n_points = n_points;
1057         outline->n_contours = n_contours;
1058         for (size_t i = 0; i < n_contours; ++i)
1059             outline->contours[i] = (unsigned short) contours[i];
1060
1061     // "Stroke" with the outline emboldener (in two passes if needed).
1062     // The outlines look uglier, but the emboldening never adds any points
1063     } else {
1064 #if (FREETYPE_MAJOR > 2) || \
1065     ((FREETYPE_MAJOR == 2) && (FREETYPE_MINOR > 4)) || \
1066     ((FREETYPE_MAJOR == 2) && (FREETYPE_MINOR == 4) && (FREETYPE_PATCH >= 10))
1067         FT_Outline_EmboldenXY(&ftol, sx * 2, sy * 2);
1068         FT_Outline_Translate(&ftol, -sx, -sy);
1069 #else
1070         int i;
1071         FT_Outline nol;
1072
1073         FT_Outline_New(render_priv->ftlibrary, ftol.n_points,
1074                        ftol.n_contours, &nol);
1075         FT_Outline_Copy(&ftol, &nol);
1076
1077         FT_Outline_Embolden(&ftol, sx * 2);
1078         FT_Outline_Translate(&ftol, -sx, -sx);
1079         FT_Outline_Embolden(&nol, sy * 2);
1080         FT_Outline_Translate(&nol, -sy, -sy);
1081
1082         for (i = 0; i < ftol.n_points; i++)
1083             ftol.points[i].y = nol.points[i].y;
1084
1085         FT_Outline_Done(render_priv->ftlibrary, &nol);
1086 #endif
1087     }
1088
1089     free(contours_large);
1090 }
1091
1092 /**
1093  * \brief Prepare glyph hash
1094  */
1095 static void
1096 fill_glyph_hash(ASS_Renderer *priv, OutlineHashKey *outline_key,
1097                 GlyphInfo *info)
1098 {
1099     if (info->drawing) {
1100         DrawingHashKey *key = &outline_key->u.drawing;
1101         outline_key->type = OUTLINE_DRAWING;
1102         key->scale_x = double_to_d16(info->scale_x);
1103         key->scale_y = double_to_d16(info->scale_y);
1104         key->outline.x = double_to_d16(info->border_x);
1105         key->outline.y = double_to_d16(info->border_y);
1106         key->border_style = info->border_style;
1107         // hpacing only matters for opaque box borders (see draw_opaque_box),
1108         // so for normal borders, maximize cache utility by ignoring it
1109         key->hspacing =
1110             info->border_style == 3 ? double_to_d16(info->hspacing) : 0;
1111         key->hash = info->drawing->hash;
1112         key->text = info->drawing->text;
1113         key->pbo = info->drawing->pbo;
1114         key->scale = info->drawing->scale;
1115     } else {
1116         GlyphHashKey *key = &outline_key->u.glyph;
1117         outline_key->type = OUTLINE_GLYPH;
1118         key->font = info->font;
1119         key->size = info->font_size;
1120         key->face_index = info->face_index;
1121         key->glyph_index = info->glyph_index;
1122         key->bold = info->bold;
1123         key->italic = info->italic;
1124         key->scale_x = double_to_d16(info->scale_x);
1125         key->scale_y = double_to_d16(info->scale_y);
1126         key->outline.x = double_to_d16(info->border_x);
1127         key->outline.y = double_to_d16(info->border_y);
1128         key->flags = info->flags;
1129         key->border_style = info->border_style;
1130         key->hspacing =
1131             info->border_style == 3 ? double_to_d16(info->hspacing) : 0;
1132     }
1133 }
1134
1135 /**
1136  * \brief Prepare combined-bitmap hash
1137  */
1138 static void fill_composite_hash(CompositeHashKey *hk, CombinedBitmapInfo *info)
1139 {
1140     hk->filter = info->filter;
1141     hk->bitmap_count = info->bitmap_count;
1142     hk->bitmaps = info->bitmaps;
1143 }
1144
1145 /**
1146  * \brief Get normal and outline (border) glyphs
1147  * \param info out: struct filled with extracted data
1148  * Tries to get both glyphs from cache.
1149  * If they can't be found, gets a glyph from font face, generates outline with FT_Stroker,
1150  * and add them to cache.
1151  * The glyphs are returned in info->glyph and info->outline_glyph
1152  */
1153 static void
1154 get_outline_glyph(ASS_Renderer *priv, GlyphInfo *info)
1155 {
1156     OutlineHashValue *val;
1157     OutlineHashKey key;
1158
1159     memset(&info->hash_key, 0, sizeof(info->hash_key));
1160
1161     fill_glyph_hash(priv, &key, info);
1162     val = ass_cache_get(priv->cache.outline_cache, &key);
1163
1164     if (!val) {
1165         OutlineHashValue v;
1166         memset(&v, 0, sizeof(v));
1167
1168         if (info->drawing) {
1169             ASS_Drawing *drawing = info->drawing;
1170             ass_drawing_hash(drawing);
1171             if(!ass_drawing_parse(drawing, 0))
1172                 return;
1173             v.outline = outline_copy(&drawing->outline);
1174             v.advance.x = drawing->advance.x;
1175             v.advance.y = drawing->advance.y;
1176             v.asc = drawing->asc;
1177             v.desc = drawing->desc;
1178             key.u.drawing.text = strdup(drawing->text);
1179         } else {
1180             ass_face_set_size(info->font->faces[info->face_index],
1181                               info->font_size);
1182             ass_font_set_transform(info->font, info->scale_x,
1183                                    info->scale_y, NULL);
1184             FT_Glyph glyph =
1185                 ass_font_get_glyph(info->font,
1186                         info->symbol, info->face_index, info->glyph_index,
1187                         priv->settings.hinting, info->flags);
1188             if (glyph != NULL) {
1189                 v.outline = outline_convert(&((FT_OutlineGlyph)glyph)->outline);
1190                 if (priv->settings.shaper == ASS_SHAPING_SIMPLE) {
1191                     v.advance.x = d16_to_d6(glyph->advance.x);
1192                     v.advance.y = d16_to_d6(glyph->advance.y);
1193                 }
1194                 FT_Done_Glyph(glyph);
1195                 ass_font_get_asc_desc(info->font, info->symbol,
1196                         &v.asc, &v.desc);
1197                 v.asc  *= info->scale_y;
1198                 v.desc *= info->scale_y;
1199             }
1200         }
1201
1202         if (!v.outline)
1203             return;
1204
1205         outline_get_cbox(v.outline, &v.bbox_scaled);
1206
1207         if (info->border_style == 3) {
1208             FT_Vector advance;
1209
1210             v.border = calloc(1, sizeof(ASS_Outline));
1211
1212             if (priv->settings.shaper == ASS_SHAPING_SIMPLE || info->drawing)
1213                 advance = v.advance;
1214             else
1215                 advance = info->advance;
1216
1217             draw_opaque_box(priv, info, v.asc, v.desc, v.border, advance,
1218                     double_to_d6(info->border_x * priv->border_scale),
1219                     double_to_d6(info->border_y * priv->border_scale));
1220
1221         } else if ((info->border_x > 0 || info->border_y > 0)
1222                 && double_to_d6(info->scale_x) && double_to_d6(info->scale_y)) {
1223
1224             change_border(priv, info->border_x, info->border_y);
1225             v.border = outline_copy(v.outline);
1226             stroke_outline(priv, v.border,
1227                     double_to_d6(info->border_x * priv->border_scale),
1228                     double_to_d6(info->border_y * priv->border_scale));
1229         }
1230
1231         val = ass_cache_put(priv->cache.outline_cache, &key, &v);
1232     }
1233
1234     info->hash_key.u.outline.outline = val;
1235     info->outline = val->outline;
1236     info->border = val->border;
1237     info->bbox = val->bbox_scaled;
1238     if (info->drawing || priv->settings.shaper == ASS_SHAPING_SIMPLE) {
1239         info->cluster_advance.x = info->advance.x = val->advance.x;
1240         info->cluster_advance.y = info->advance.y = val->advance.y;
1241     }
1242     info->asc = val->asc;
1243     info->desc = val->desc;
1244 }
1245
1246 /**
1247  * \brief Apply transformation to outline points of a glyph
1248  * Applies rotations given by frx, fry and frz and projects the points back
1249  * onto the screen plane.
1250  */
1251 static void
1252 transform_3d_points(FT_Vector shift, ASS_Outline *outline, double frx, double fry,
1253                     double frz, double fax, double fay, double scale,
1254                     int yshift)
1255 {
1256     double sx = sin(frx);
1257     double sy = sin(fry);
1258     double sz = sin(frz);
1259     double cx = cos(frx);
1260     double cy = cos(fry);
1261     double cz = cos(frz);
1262     FT_Vector *p = outline->points;
1263     double x, y, z, xx, yy, zz;
1264     int dist;
1265
1266     dist = 20000 * scale;
1267     for (size_t i = 0; i < outline->n_points; ++i) {
1268         x = (double) p[i].x + shift.x + (fax * (yshift - p[i].y));
1269         y = (double) p[i].y + shift.y + (-fay * p[i].x);
1270         z = 0.;
1271
1272         xx = x * cz + y * sz;
1273         yy = -(x * sz - y * cz);
1274         zz = z;
1275
1276         x = xx;
1277         y = yy * cx + zz * sx;
1278         z = yy * sx - zz * cx;
1279
1280         xx = x * cy + z * sy;
1281         yy = y;
1282         zz = x * sy - z * cy;
1283
1284         zz = FFMAX(zz, 1000 - dist);
1285
1286         x = (xx * dist) / (zz + dist);
1287         y = (yy * dist) / (zz + dist);
1288         p[i].x = x - shift.x + 0.5;
1289         p[i].y = y - shift.y + 0.5;
1290     }
1291 }
1292
1293 /**
1294  * \brief Apply 3d transformation to several objects
1295  * \param shift FreeType vector
1296  * \param glyph FreeType glyph
1297  * \param glyph2 FreeType glyph
1298  * \param frx x-axis rotation angle
1299  * \param fry y-axis rotation angle
1300  * \param frz z-axis rotation angle
1301  * Rotates both glyphs by frx, fry and frz. Shift vector is added before rotation and subtracted after it.
1302  */
1303 static void
1304 transform_3d(FT_Vector shift, ASS_Outline *outline, ASS_Outline *border,
1305              double frx, double fry, double frz, double fax, double fay,
1306              double scale, int yshift)
1307 {
1308     frx = -frx;
1309     frz = -frz;
1310     if (frx != 0. || fry != 0. || frz != 0. || fax != 0. || fay != 0.) {
1311         if (outline)
1312             transform_3d_points(shift, outline, frx, fry, frz,
1313                                 fax, fay, scale, yshift);
1314
1315         if (border)
1316             transform_3d_points(shift, border, frx, fry, frz,
1317                                 fax, fay, scale, yshift);
1318     }
1319 }
1320
1321 /**
1322  * \brief Get bitmaps for a glyph
1323  * \param info glyph info
1324  * Tries to get glyph bitmaps from bitmap cache.
1325  * If they can't be found, they are generated by rotating and rendering the glyph.
1326  * After that, bitmaps are added to the cache.
1327  * They are returned in info->bm (glyph), info->bm_o (outline) and info->bm_s (shadow).
1328  */
1329 static void
1330 get_bitmap_glyph(ASS_Renderer *render_priv, GlyphInfo *info)
1331 {
1332     BitmapHashValue *val;
1333     OutlineBitmapHashKey *key = &info->hash_key.u.outline;
1334
1335     if (!info->outline || info->symbol == '\n' || info->symbol == 0 || info->skip)
1336         return;
1337
1338     val = ass_cache_get(render_priv->cache.bitmap_cache, &info->hash_key);
1339
1340     if (!val) {
1341         FT_Vector shift;
1342         BitmapHashValue hash_val;
1343         int error;
1344         double fax_scaled, fay_scaled;
1345         double scale_x = render_priv->font_scale_x;
1346
1347         hash_val.bm = hash_val.bm_o = NULL;
1348
1349         ASS_Outline *outline = outline_copy(info->outline);
1350         ASS_Outline *border  = outline_copy(info->border);
1351
1352         // calculating rotation shift vector (from rotation origin to the glyph basepoint)
1353         shift.x = key->shift_x;
1354         shift.y = key->shift_y;
1355         fax_scaled = info->fax / info->scale_y * info->scale_x;
1356         fay_scaled = info->fay / info->scale_x * info->scale_y;
1357
1358         // apply rotation
1359         // use blur_scale because, like blurs, VSFilter forgets to scale this
1360         transform_3d(shift, outline, border,
1361                 info->frx, info->fry, info->frz, fax_scaled,
1362                 fay_scaled, render_priv->blur_scale, info->asc);
1363
1364         // PAR correction scaling
1365         FT_Matrix m = { double_to_d16(scale_x), 0,
1366             0, double_to_d16(1.0) };
1367
1368         // subpixel shift
1369         if (outline) {
1370             if (scale_x != 1.0)
1371                 outline_transform(outline, &m);
1372             outline_translate(outline, key->advance.x, -key->advance.y);
1373         }
1374         if (border) {
1375             if (scale_x != 1.0)
1376                 outline_transform(border, &m);
1377             outline_translate(border, key->advance.x, -key->advance.y);
1378         }
1379
1380         // render glyph
1381         error = outline_to_bitmap2(render_priv, outline, border,
1382                                    &hash_val.bm, &hash_val.bm_o);
1383         if (error)
1384             info->symbol = 0;
1385
1386         val = ass_cache_put(render_priv->cache.bitmap_cache, &info->hash_key,
1387                 &hash_val);
1388
1389         outline_free(outline);
1390         free(outline);
1391         outline_free(border);
1392         free(border);
1393     }
1394
1395     info->image = val;
1396 }
1397
1398 /**
1399  * This function goes through text_info and calculates text parameters.
1400  * The following text_info fields are filled:
1401  *   height
1402  *   lines[].height
1403  *   lines[].asc
1404  *   lines[].desc
1405  */
1406 static void measure_text(ASS_Renderer *render_priv)
1407 {
1408     TextInfo *text_info = &render_priv->text_info;
1409     int cur_line = 0;
1410     double max_asc = 0., max_desc = 0.;
1411     GlyphInfo *last = NULL;
1412     int i;
1413     int empty_line = 1;
1414     text_info->height = 0.;
1415     for (i = 0; i < text_info->length + 1; ++i) {
1416         if ((i == text_info->length) || text_info->glyphs[i].linebreak) {
1417             if (empty_line && cur_line > 0 && last) {
1418                 max_asc = d6_to_double(last->asc) / 2.0;
1419                 max_desc = d6_to_double(last->desc) / 2.0;
1420             }
1421             text_info->lines[cur_line].asc = max_asc;
1422             text_info->lines[cur_line].desc = max_desc;
1423             text_info->height += max_asc + max_desc;
1424             cur_line++;
1425             max_asc = max_desc = 0.;
1426             empty_line = 1;
1427         }
1428         if (i < text_info->length) {
1429             GlyphInfo *cur = text_info->glyphs + i;
1430             if (d6_to_double(cur->asc) > max_asc)
1431                 max_asc = d6_to_double(cur->asc);
1432             if (d6_to_double(cur->desc) > max_desc)
1433                 max_desc = d6_to_double(cur->desc);
1434             if (cur->symbol != '\n' && cur->symbol != 0) {
1435                 empty_line = 0;
1436                 last = cur;
1437             }
1438         }
1439     }
1440     text_info->height +=
1441         (text_info->n_lines -
1442          1) * render_priv->settings.line_spacing;
1443 }
1444
1445 /**
1446  * Mark extra whitespace for later removal.
1447  */
1448 #define IS_WHITESPACE(x) ((x->symbol == ' ' || x->symbol == '\n') \
1449                           && !x->linebreak)
1450 static void trim_whitespace(ASS_Renderer *render_priv)
1451 {
1452     int i, j;
1453     GlyphInfo *cur;
1454     TextInfo *ti = &render_priv->text_info;
1455
1456     // Mark trailing spaces
1457     i = ti->length - 1;
1458     cur = ti->glyphs + i;
1459     while (i && IS_WHITESPACE(cur)) {
1460         cur->skip++;
1461         cur = ti->glyphs + --i;
1462     }
1463
1464     // Mark leading whitespace
1465     i = 0;
1466     cur = ti->glyphs;
1467     while (i < ti->length && IS_WHITESPACE(cur)) {
1468         cur->skip++;
1469         cur = ti->glyphs + ++i;
1470     }
1471
1472     // Mark all extraneous whitespace inbetween
1473     for (i = 0; i < ti->length; ++i) {
1474         cur = ti->glyphs + i;
1475         if (cur->linebreak) {
1476             // Mark whitespace before
1477             j = i - 1;
1478             cur = ti->glyphs + j;
1479             while (j && IS_WHITESPACE(cur)) {
1480                 cur->skip++;
1481                 cur = ti->glyphs + --j;
1482             }
1483             // A break itself can contain a whitespace, too
1484             cur = ti->glyphs + i;
1485             if (cur->symbol == ' ' || cur->symbol == '\n') {
1486                 cur->skip++;
1487                 // Mark whitespace after
1488                 j = i + 1;
1489                 cur = ti->glyphs + j;
1490                 while (j < ti->length && IS_WHITESPACE(cur)) {
1491                     cur->skip++;
1492                     cur = ti->glyphs + ++j;
1493                 }
1494                 i = j - 1;
1495             }
1496         }
1497     }
1498 }
1499 #undef IS_WHITESPACE
1500
1501 /**
1502  * \brief rearrange text between lines
1503  * \param max_text_width maximal text line width in pixels
1504  * The algo is similar to the one in libvo/sub.c:
1505  * 1. Place text, wrapping it when current line is full
1506  * 2. Try moving words from the end of a line to the beginning of the next one while it reduces
1507  * the difference in lengths between this two lines.
1508  * The result may not be optimal, but usually is good enough.
1509  *
1510  * FIXME: implement style 0 and 3 correctly
1511  */
1512 static void
1513 wrap_lines_smart(ASS_Renderer *render_priv, double max_text_width)
1514 {
1515     int i;
1516     GlyphInfo *cur, *s1, *e1, *s2, *s3;
1517     int last_space;
1518     int break_type;
1519     int exit;
1520     double pen_shift_x;
1521     double pen_shift_y;
1522     int cur_line;
1523     int run_offset;
1524     TextInfo *text_info = &render_priv->text_info;
1525
1526     last_space = -1;
1527     text_info->n_lines = 1;
1528     break_type = 0;
1529     s1 = text_info->glyphs;     // current line start
1530     for (i = 0; i < text_info->length; ++i) {
1531         int break_at = -1;
1532         double s_offset, len;
1533         cur = text_info->glyphs + i;
1534         s_offset = d6_to_double(s1->bbox.xMin + s1->pos.x);
1535         len = d6_to_double(cur->bbox.xMax + cur->pos.x) - s_offset;
1536
1537         if (cur->symbol == '\n') {
1538             break_type = 2;
1539             break_at = i;
1540             ass_msg(render_priv->library, MSGL_DBG2,
1541                     "forced line break at %d", break_at);
1542         } else if (cur->symbol == ' ') {
1543             last_space = i;
1544         } else if (len >= max_text_width
1545                    && (render_priv->state.wrap_style != 2)) {
1546             break_type = 1;
1547             break_at = last_space;
1548             if (break_at >= 0)
1549                 ass_msg(render_priv->library, MSGL_DBG2, "line break at %d",
1550                         break_at);
1551         }
1552
1553         if (break_at != -1) {
1554             // need to use one more line
1555             // marking break_at+1 as start of a new line
1556             int lead = break_at + 1;    // the first symbol of the new line
1557             if (text_info->n_lines >= text_info->max_lines) {
1558                 // Raise maximum number of lines
1559                 text_info->max_lines *= 2;
1560                 text_info->lines = realloc(text_info->lines,
1561                                            sizeof(LineInfo) *
1562                                            text_info->max_lines);
1563             }
1564             if (lead < text_info->length) {
1565                 text_info->glyphs[lead].linebreak = break_type;
1566                 last_space = -1;
1567                 s1 = text_info->glyphs + lead;
1568                 text_info->n_lines++;
1569             }
1570         }
1571     }
1572 #define DIFF(x,y) (((x) < (y)) ? (y - x) : (x - y))
1573     exit = 0;
1574     while (!exit && render_priv->state.wrap_style != 1) {
1575         exit = 1;
1576         s3 = text_info->glyphs;
1577         s1 = s2 = 0;
1578         for (i = 0; i <= text_info->length; ++i) {
1579             cur = text_info->glyphs + i;
1580             if ((i == text_info->length) || cur->linebreak) {
1581                 s1 = s2;
1582                 s2 = s3;
1583                 s3 = cur;
1584                 if (s1 && (s2->linebreak == 1)) {       // have at least 2 lines, and linebreak is 'soft'
1585                     double l1, l2, l1_new, l2_new;
1586                     GlyphInfo *w = s2;
1587
1588                     do {
1589                         --w;
1590                     } while ((w > s1) && (w->symbol == ' '));
1591                     while ((w > s1) && (w->symbol != ' ')) {
1592                         --w;
1593                     }
1594                     e1 = w;
1595                     while ((e1 > s1) && (e1->symbol == ' ')) {
1596                         --e1;
1597                     }
1598                     if (w->symbol == ' ')
1599                         ++w;
1600
1601                     l1 = d6_to_double(((s2 - 1)->bbox.xMax + (s2 - 1)->pos.x) -
1602                         (s1->bbox.xMin + s1->pos.x));
1603                     l2 = d6_to_double(((s3 - 1)->bbox.xMax + (s3 - 1)->pos.x) -
1604                         (s2->bbox.xMin + s2->pos.x));
1605                     l1_new = d6_to_double(
1606                         (e1->bbox.xMax + e1->pos.x) -
1607                         (s1->bbox.xMin + s1->pos.x));
1608                     l2_new = d6_to_double(
1609                         ((s3 - 1)->bbox.xMax + (s3 - 1)->pos.x) -
1610                         (w->bbox.xMin + w->pos.x));
1611
1612                     if (DIFF(l1_new, l2_new) < DIFF(l1, l2)) {
1613                         w->linebreak = 1;
1614                         s2->linebreak = 0;
1615                         exit = 0;
1616                     }
1617                 }
1618             }
1619             if (i == text_info->length)
1620                 break;
1621         }
1622
1623     }
1624     assert(text_info->n_lines >= 1);
1625 #undef DIFF
1626
1627     measure_text(render_priv);
1628     trim_whitespace(render_priv);
1629
1630     cur_line = 1;
1631     run_offset = 0;
1632
1633     i = 0;
1634     cur = text_info->glyphs + i;
1635     while (i < text_info->length && cur->skip)
1636         cur = text_info->glyphs + ++i;
1637     pen_shift_x = d6_to_double(-cur->pos.x);
1638     pen_shift_y = 0.;
1639
1640     for (i = 0; i < text_info->length; ++i) {
1641         cur = text_info->glyphs + i;
1642         if (cur->linebreak) {
1643             while (i < text_info->length && cur->skip && cur->symbol != '\n')
1644                 cur = text_info->glyphs + ++i;
1645             double height =
1646                 text_info->lines[cur_line - 1].desc +
1647                 text_info->lines[cur_line].asc;
1648             text_info->lines[cur_line - 1].len = i -
1649                 text_info->lines[cur_line - 1].offset;
1650             text_info->lines[cur_line].offset = i;
1651             cur_line++;
1652             run_offset++;
1653             pen_shift_x = d6_to_double(-cur->pos.x);
1654             pen_shift_y += height + render_priv->settings.line_spacing;
1655         }
1656         cur->pos.x += double_to_d6(pen_shift_x);
1657         cur->pos.y += double_to_d6(pen_shift_y);
1658     }
1659     text_info->lines[cur_line - 1].len =
1660         text_info->length - text_info->lines[cur_line - 1].offset;
1661
1662 #if 0
1663     // print line info
1664     for (i = 0; i < text_info->n_lines; i++) {
1665         printf("line %d offset %d length %d\n", i, text_info->lines[i].offset,
1666                 text_info->lines[i].len);
1667     }
1668 #endif
1669 }
1670
1671 /**
1672  * \brief Calculate base point for positioning and rotation
1673  * \param bbox text bbox
1674  * \param alignment alignment
1675  * \param bx, by out: base point coordinates
1676  */
1677 static void get_base_point(DBBox *bbox, int alignment, double *bx, double *by)
1678 {
1679     const int halign = alignment & 3;
1680     const int valign = alignment & 12;
1681     if (bx)
1682         switch (halign) {
1683         case HALIGN_LEFT:
1684             *bx = bbox->xMin;
1685             break;
1686         case HALIGN_CENTER:
1687             *bx = (bbox->xMax + bbox->xMin) / 2.0;
1688             break;
1689         case HALIGN_RIGHT:
1690             *bx = bbox->xMax;
1691             break;
1692         }
1693     if (by)
1694         switch (valign) {
1695         case VALIGN_TOP:
1696             *by = bbox->yMin;
1697             break;
1698         case VALIGN_CENTER:
1699             *by = (bbox->yMax + bbox->yMin) / 2.0;
1700             break;
1701         case VALIGN_SUB:
1702             *by = bbox->yMax;
1703             break;
1704         }
1705 }
1706
1707 /**
1708  * Prepare bitmap hash key of a glyph
1709  */
1710 static void
1711 fill_bitmap_hash(ASS_Renderer *priv, GlyphInfo *info,
1712                  OutlineBitmapHashKey *hash_key)
1713 {
1714     hash_key->frx = rot_key(info->frx);
1715     hash_key->fry = rot_key(info->fry);
1716     hash_key->frz = rot_key(info->frz);
1717     hash_key->fax = double_to_d16(info->fax);
1718     hash_key->fay = double_to_d16(info->fay);
1719 }
1720
1721 /**
1722  * \brief Adjust the glyph's font size and scale factors to ensure smooth
1723  *  scaling and handle pathological font sizes. The main problem here is
1724  *  freetype's grid fitting, which destroys animations by font size, or will
1725  *  result in incorrect final text size if font sizes are very small and
1726  *  scale factors very large. See Google Code issue #46.
1727  * \param priv guess what
1728  * \param glyph the glyph to be modified
1729  */
1730 static void
1731 fix_glyph_scaling(ASS_Renderer *priv, GlyphInfo *glyph)
1732 {
1733     double ft_size;
1734     if (priv->settings.hinting == ASS_HINTING_NONE) {
1735         // arbitrary, not too small to prevent grid fitting rounding effects
1736         // XXX: this is a rather crude hack
1737         ft_size = 256.0;
1738     } else {
1739         // If hinting is enabled, we want to pass the real font size
1740         // to freetype. Normalize scale_y to 1.0.
1741         ft_size = glyph->scale_y * glyph->font_size;
1742     }
1743     glyph->scale_x = glyph->scale_x * glyph->font_size / ft_size;
1744     glyph->scale_y = glyph->scale_y * glyph->font_size / ft_size;
1745     glyph->font_size = ft_size;
1746 }
1747
1748  /**
1749   * \brief Checks whether a glyph should start a new bitmap run
1750   * \param info Pointer to new GlyphInfo to check
1751   * \param current_info Pointer to CombinedBitmapInfo for current run (may be NULL)
1752   * \return 1 if a new run should be started
1753   */
1754 static int is_new_bm_run(GlyphInfo *info, GlyphInfo *last)
1755 {
1756     // FIXME: Don't break on glyph substitutions
1757     return !last || info->effect || info->drawing || last->drawing ||
1758         strcmp(last->font->desc.family, info->font->desc.family) ||
1759         last->font->desc.vertical != info->font->desc.vertical ||
1760         last->face_index != info->face_index ||
1761         last->font_size != info->font_size ||
1762         last->c[0] != info->c[0] ||
1763         last->c[1] != info->c[1] ||
1764         last->c[2] != info->c[2] ||
1765         last->c[3] != info->c[3] ||
1766         last->be != info->be ||
1767         last->blur != info->blur ||
1768         last->shadow_x != info->shadow_x ||
1769         last->shadow_y != info->shadow_y ||
1770         last->frx != info->frx ||
1771         last->fry != info->fry ||
1772         last->frz != info->frz ||
1773         last->fax != info->fax ||
1774         last->fay != info->fay ||
1775         last->scale_x != info->scale_x ||
1776         last->scale_y != info->scale_y ||
1777         last->border_style != info->border_style ||
1778         last->border_x != info->border_x ||
1779         last->border_y != info->border_y ||
1780         last->hspacing != info->hspacing ||
1781         last->italic != info->italic ||
1782         last->bold != info->bold ||
1783         last->flags != info->flags;
1784 }
1785
1786 static void make_shadow_bitmap(CombinedBitmapInfo *info, ASS_Renderer *render_priv)
1787 {
1788     if (!(info->filter.flags & FILTER_NONZERO_SHADOW)) {
1789         if (info->bm && info->bm_o && !(info->filter.flags & FILTER_BORDER_STYLE_3)) {
1790             fix_outline(info->bm, info->bm_o);
1791         } else if (info->bm_o && !(info->filter.flags & FILTER_NONZERO_BORDER)) {
1792             ass_free_bitmap(info->bm_o);
1793             info->bm_o = 0;
1794         }
1795         return;
1796     }
1797
1798     // Create shadow and fix outline as needed
1799     if (info->bm && info->bm_o && !(info->filter.flags & FILTER_BORDER_STYLE_3)) {
1800         info->bm_s = copy_bitmap(render_priv->engine, info->bm_o);
1801         fix_outline(info->bm, info->bm_o);
1802     } else if (info->bm_o && (info->filter.flags & FILTER_NONZERO_BORDER)) {
1803         info->bm_s = copy_bitmap(render_priv->engine, info->bm_o);
1804     } else if (info->bm_o) {
1805         info->bm_s = info->bm_o;
1806         info->bm_o = 0;
1807     } else if (info->bm)
1808         info->bm_s = copy_bitmap(render_priv->engine, info->bm);
1809
1810     if (!info->bm_s)
1811         return;
1812
1813     // Works right even for negative offsets
1814     // '>>' rounds toward negative infinity, '&' returns correct remainder
1815     info->bm_s->left += info->filter.shadow.x >> 6;
1816     info->bm_s->top  += info->filter.shadow.y >> 6;
1817     shift_bitmap(info->bm_s, info->filter.shadow.x & SUBPIXEL_MASK, info->filter.shadow.y & SUBPIXEL_MASK);
1818 }
1819
1820 // Parse event text.
1821 // Fill render_priv->text_info.
1822 static int parse_events(ASS_Renderer *render_priv, ASS_Event *event)
1823 {
1824     TextInfo *text_info = &render_priv->text_info;
1825     ASS_Drawing *drawing = NULL;
1826     unsigned code;
1827     char *p, *q;
1828     int i;
1829
1830     p = event->Text;
1831
1832     // Event parsing.
1833     while (1) {
1834         // get next char, executing style override
1835         // this affects render_context
1836         code = 0;
1837         while (*p) {
1838             if ((*p == '{') && (q = strchr(p, '}'))) {
1839                 while (p < q)
1840                     p = parse_tag(render_priv, p, q, 1.);
1841                 assert(*p == '}');
1842                 p++;
1843             } else if (render_priv->state.drawing_scale) {
1844                 q = p;
1845                 if (*p == '{')
1846                     q++;
1847                 while ((*q != '{') && (*q != 0))
1848                     q++;
1849                 if (!drawing) {
1850                     drawing = ass_drawing_new(render_priv->library,
1851                                               render_priv->ftlibrary);
1852                     if (!drawing)
1853                         return 1;
1854                 }
1855                 ass_drawing_set_text(drawing, p, q - p);
1856                 code = 0xfffc; // object replacement character
1857                 p = q;
1858                 break;
1859             } else {
1860                 code = get_next_char(render_priv, &p);
1861                 break;
1862             }
1863         }
1864
1865         if (code == 0)
1866             break;
1867
1868         // face could have been changed in get_next_char
1869         if (!render_priv->state.font) {
1870             free_render_context(render_priv);
1871             ass_drawing_free(drawing);
1872             return 1;
1873         }
1874
1875         if (text_info->length >= text_info->max_glyphs) {
1876             // Raise maximum number of glyphs
1877             text_info->max_glyphs *= 2;
1878             text_info->glyphs =
1879                 realloc(text_info->glyphs,
1880                         sizeof(GlyphInfo) * text_info->max_glyphs);
1881         }
1882
1883         GlyphInfo *info = &text_info->glyphs[text_info->length];
1884
1885         // Clear current GlyphInfo
1886         memset(info, 0, sizeof(GlyphInfo));
1887
1888         // Parse drawing
1889         if (drawing && drawing->text) {
1890             drawing->scale_x = render_priv->state.scale_x *
1891                                      render_priv->font_scale;
1892             drawing->scale_y = render_priv->state.scale_y *
1893                                      render_priv->font_scale;
1894             drawing->scale = render_priv->state.drawing_scale;
1895             drawing->pbo = render_priv->state.pbo;
1896             info->drawing = drawing;
1897             drawing = NULL;
1898         }
1899
1900         // Fill glyph information
1901         info->symbol = code;
1902         info->font = render_priv->state.font;
1903         for (i = 0; i < 4; ++i) {
1904             uint32_t clr = render_priv->state.c[i];
1905             // VSFilter compatibility: apply fade only when it's positive
1906             if (render_priv->state.fade > 0)
1907                 change_alpha(&clr,
1908                              mult_alpha(_a(clr), render_priv->state.fade), 1.);
1909             info->c[i] = clr;
1910         }
1911
1912         info->effect_type = render_priv->state.effect_type;
1913         info->effect_timing = render_priv->state.effect_timing;
1914         info->effect_skip_timing = render_priv->state.effect_skip_timing;
1915         info->font_size =
1916             render_priv->state.font_size * render_priv->font_scale;
1917         info->be = render_priv->state.be;
1918         info->blur = render_priv->state.blur;
1919         info->shadow_x = render_priv->state.shadow_x;
1920         info->shadow_y = render_priv->state.shadow_y;
1921         info->scale_x = info->orig_scale_x = render_priv->state.scale_x;
1922         info->scale_y = info->orig_scale_y = render_priv->state.scale_y;
1923         info->border_style = render_priv->state.border_style;
1924         info->border_x= render_priv->state.border_x;
1925         info->border_y = render_priv->state.border_y;
1926         info->hspacing = render_priv->state.hspacing;
1927         info->bold = render_priv->state.bold;
1928         info->italic = render_priv->state.italic;
1929         info->flags = render_priv->state.flags;
1930         info->frx = render_priv->state.frx;
1931         info->fry = render_priv->state.fry;
1932         info->frz = render_priv->state.frz;
1933         info->fax = render_priv->state.fax;
1934         info->fay = render_priv->state.fay;
1935
1936         if (!info->drawing)
1937             fix_glyph_scaling(render_priv, info);
1938
1939         text_info->length++;
1940
1941         render_priv->state.effect_type = EF_NONE;
1942         render_priv->state.effect_timing = 0;
1943         render_priv->state.effect_skip_timing = 0;
1944     }
1945
1946     ass_drawing_free(drawing);
1947
1948     return 0;
1949 }
1950
1951 // Process render_priv->text_info and load glyph outlines.
1952 static void retrieve_glyphs(ASS_Renderer *render_priv)
1953 {
1954     GlyphInfo *glyphs = render_priv->text_info.glyphs;
1955     int i;
1956
1957     for (i = 0; i < render_priv->text_info.length; i++) {
1958         GlyphInfo *info = glyphs + i;
1959         while (info) {
1960             get_outline_glyph(render_priv, info);
1961             info = info->next;
1962         }
1963         info = glyphs + i;
1964
1965         // Add additional space after italic to non-italic style changes
1966         if (i && glyphs[i - 1].italic && !info->italic) {
1967             int back = i - 1;
1968             GlyphInfo *og = &glyphs[back];
1969             while (back && og->bbox.xMax - og->bbox.xMin == 0
1970                     && og->italic)
1971                 og = &glyphs[--back];
1972             if (og->bbox.xMax > og->cluster_advance.x)
1973                 og->cluster_advance.x = og->bbox.xMax;
1974         }
1975
1976         // add horizontal letter spacing
1977         info->cluster_advance.x += double_to_d6(info->hspacing *
1978                 render_priv->font_scale * info->orig_scale_x);
1979
1980         // add displacement for vertical shearing
1981         info->cluster_advance.y += (info->fay / info->scale_x * info->scale_y) * info->cluster_advance.x;
1982     }
1983 }
1984
1985 // Preliminary layout (for line wrapping)
1986 static void preliminary_layout(ASS_Renderer *render_priv)
1987 {
1988     FT_Vector pen;
1989     int i;
1990
1991     pen.x = 0;
1992     pen.y = 0;
1993     for (i = 0; i < render_priv->text_info.length; i++) {
1994         GlyphInfo *info = render_priv->text_info.glyphs + i;
1995         FT_Vector cluster_pen = pen;
1996         while (info) {
1997             info->pos.x = cluster_pen.x;
1998             info->pos.y = cluster_pen.y;
1999
2000             cluster_pen.x += info->advance.x;
2001             cluster_pen.y += info->advance.y;
2002
2003             // fill bitmap hash
2004             info->hash_key.type = BITMAP_OUTLINE;
2005             fill_bitmap_hash(render_priv, info, &info->hash_key.u.outline);
2006
2007             info = info->next;
2008         }
2009         info = render_priv->text_info.glyphs + i;
2010         pen.x += info->cluster_advance.x;
2011         pen.y += info->cluster_advance.y;
2012     }
2013 }
2014
2015 // Reorder text into visual order
2016 static void reorder_text(ASS_Renderer *render_priv)
2017 {
2018     TextInfo *text_info = &render_priv->text_info;
2019     FT_Vector pen;
2020     int i;
2021
2022     FriBidiStrIndex *cmap = ass_shaper_reorder(render_priv->shaper, text_info);
2023     if (!cmap) {
2024         ass_msg(render_priv->library, MSGL_ERR, "Failed to reorder text");
2025         ass_shaper_cleanup(render_priv->shaper, text_info);
2026         free_render_context(render_priv);
2027         return;
2028     }
2029
2030     // Reposition according to the map
2031     pen.x = 0;
2032     pen.y = 0;
2033     int lineno = 1;
2034     double last_pen_x = 0;
2035     double last_fay = 0;
2036     for (i = 0; i < text_info->length; i++) {
2037         GlyphInfo *info = text_info->glyphs + cmap[i];
2038         if (text_info->glyphs[i].linebreak) {
2039             pen.y -= (last_fay / info->scale_x * info->scale_y) * (pen.x - last_pen_x);
2040             last_pen_x = pen.x = 0;
2041             pen.y += double_to_d6(text_info->lines[lineno-1].desc);
2042             pen.y += double_to_d6(text_info->lines[lineno].asc);
2043             pen.y += double_to_d6(render_priv->settings.line_spacing);
2044             lineno++;
2045         }
2046         else if (last_fay != info->fay) {
2047             pen.y -= (last_fay / info->scale_x * info->scale_y) * (pen.x - last_pen_x);
2048             last_pen_x = pen.x;
2049         }
2050         last_fay = info->fay;
2051         if (info->skip) continue;
2052         FT_Vector cluster_pen = pen;
2053         while (info) {
2054             info->pos.x = info->offset.x + cluster_pen.x;
2055             info->pos.y = info->offset.y + cluster_pen.y;
2056             cluster_pen.x += info->advance.x;
2057             cluster_pen.y += info->advance.y;
2058             info = info->next;
2059         }
2060         info = text_info->glyphs + cmap[i];
2061         pen.x += info->cluster_advance.x;
2062         pen.y += info->cluster_advance.y;
2063     }
2064 }
2065
2066 static void align_lines(ASS_Renderer *render_priv, double max_text_width)
2067 {
2068     TextInfo *text_info = &render_priv->text_info;
2069     GlyphInfo *glyphs = text_info->glyphs;
2070     int i, j;
2071     double width = 0;
2072     int last_break = -1;
2073     int halign = render_priv->state.alignment & 3;
2074
2075     if (render_priv->state.evt_type == EVENT_HSCROLL)
2076         return;
2077
2078     for (i = 0; i <= text_info->length; ++i) {   // (text_info->length + 1) is the end of the last line
2079         if ((i == text_info->length) || glyphs[i].linebreak) {
2080             double shift = 0;
2081             if (halign == HALIGN_LEFT) {    // left aligned, no action
2082                 shift = 0;
2083             } else if (halign == HALIGN_RIGHT) {    // right aligned
2084                 shift = max_text_width - width;
2085             } else if (halign == HALIGN_CENTER) {   // centered
2086                 shift = (max_text_width - width) / 2.0;
2087             }
2088             for (j = last_break + 1; j < i; ++j) {
2089                 GlyphInfo *info = glyphs + j;
2090                 while (info) {
2091                     info->pos.x += double_to_d6(shift);
2092                     info = info->next;
2093                 }
2094             }
2095             last_break = i - 1;
2096             width = 0;
2097         }
2098         if (i < text_info->length && !glyphs[i].skip &&
2099                 glyphs[i].symbol != '\n' && glyphs[i].symbol != 0) {
2100             width += d6_to_double(glyphs[i].cluster_advance.x);
2101         }
2102     }
2103 }
2104
2105 static void calculate_rotation_params(ASS_Renderer *render_priv, DBBox *bbox,
2106                                       double device_x, double device_y)
2107 {
2108     TextInfo *text_info = &render_priv->text_info;
2109     DVector center;
2110     int i;
2111
2112     if (render_priv->state.have_origin) {
2113         center.x = x2scr(render_priv, render_priv->state.org_x);
2114         center.y = y2scr(render_priv, render_priv->state.org_y);
2115     } else {
2116         double bx = 0., by = 0.;
2117         get_base_point(bbox, render_priv->state.alignment, &bx, &by);
2118         center.x = device_x + bx;
2119         center.y = device_y + by;
2120     }
2121
2122     for (i = 0; i < text_info->length; ++i) {
2123         GlyphInfo *info = text_info->glyphs + i;
2124         while (info) {
2125             OutlineBitmapHashKey *key = &info->hash_key.u.outline;
2126
2127             if (key->frx || key->fry || key->frz || key->fax || key->fay) {
2128                 key->shift_x = info->pos.x + double_to_d6(device_x - center.x);
2129                 key->shift_y = -(info->pos.y + double_to_d6(device_y - center.y));
2130             } else {
2131                 key->shift_x = 0;
2132                 key->shift_y = 0;
2133             }
2134             info = info->next;
2135         }
2136     }
2137 }
2138
2139
2140 static inline void rectangle_reset(Rectangle *rect)
2141 {
2142     rect->x_min = rect->y_min = INT_MAX;
2143     rect->x_max = rect->y_max = INT_MIN;
2144 }
2145
2146 static inline void rectangle_combine(Rectangle *rect, const Bitmap *bm, int x, int y)
2147 {
2148     rect->x_min = FFMIN(rect->x_min, x + bm->left);
2149     rect->y_min = FFMIN(rect->y_min, y + bm->top);
2150     rect->x_max = FFMAX(rect->x_max, x + bm->left + bm->w);
2151     rect->y_max = FFMAX(rect->y_max, y + bm->top + bm->h);
2152 }
2153
2154 // Convert glyphs to bitmaps, combine them, apply blur, generate shadows.
2155 static void render_and_combine_glyphs(ASS_Renderer *render_priv,
2156                                       double device_x, double device_y)
2157 {
2158     TextInfo *text_info = &render_priv->text_info;
2159     int left = render_priv->settings.left_margin;
2160     device_x = (device_x - left) * render_priv->font_scale_x + left;
2161     unsigned nb_bitmaps = 0;
2162     char linebreak = 0;
2163     CombinedBitmapInfo *combined_info = text_info->combined_bitmaps;
2164     CombinedBitmapInfo *current_info = NULL;
2165     GlyphInfo *last_info = NULL;
2166     for (int i = 0; i < text_info->length; ++i) {
2167         GlyphInfo *info = text_info->glyphs + i;
2168         if (info->linebreak) linebreak = 1;
2169         if (info->skip) continue;
2170         for (; info; info = info->next) {
2171             OutlineBitmapHashKey *key = &info->hash_key.u.outline;
2172
2173             info->pos.x = double_to_d6(device_x + d6_to_double(info->pos.x) * render_priv->font_scale_x);
2174             info->pos.y = double_to_d6(device_y) + info->pos.y;
2175             key->advance.x = info->pos.x & (SUBPIXEL_MASK & ~SUBPIXEL_ACCURACY);
2176             key->advance.y = info->pos.y & (SUBPIXEL_MASK & ~SUBPIXEL_ACCURACY);
2177             int x = info->pos.x >> 6, y = info->pos.y >> 6;
2178             get_bitmap_glyph(render_priv, info);
2179
2180             if(linebreak || is_new_bm_run(info, last_info)) {
2181                 linebreak = 0;
2182                 last_info = NULL;
2183                 if (nb_bitmaps >= text_info->max_bitmaps) {
2184                     size_t new_size = 2 * text_info->max_bitmaps;
2185                     if (!ASS_REALLOC_ARRAY(text_info->combined_bitmaps, new_size))
2186                         continue;
2187                     text_info->max_bitmaps = new_size;
2188                     combined_info = text_info->combined_bitmaps;
2189                 }
2190                 current_info = &combined_info[nb_bitmaps];
2191
2192                 memcpy(&current_info->c, &info->c, sizeof(info->c));
2193                 current_info->effect_type = info->effect_type;
2194                 current_info->effect_timing = info->effect_timing;
2195                 current_info->first_pos_x = info->bbox.xMax >> 6;
2196
2197                 current_info->filter.flags = 0;
2198                 if (info->border_style == 3)
2199                     current_info->filter.flags |= FILTER_BORDER_STYLE_3;
2200                 if (info->border_x || info->border_y)
2201                     current_info->filter.flags |= FILTER_NONZERO_BORDER;
2202                 if (info->shadow_x || info->shadow_y)
2203                     current_info->filter.flags |= FILTER_NONZERO_SHADOW;
2204                 // VSFilter compatibility: invisible fill and no border?
2205                 // In this case no shadow is supposed to be rendered.
2206                 if (info->border || (info->c[0] & 0xFF) != 0xFF)
2207                     current_info->filter.flags |= FILTER_DRAW_SHADOW;
2208
2209                 current_info->filter.be = info->be;
2210                 current_info->filter.blur = 2 * info->blur * render_priv->blur_scale;
2211                 current_info->filter.shadow.x = double_to_d6(info->shadow_x * render_priv->border_scale);
2212                 current_info->filter.shadow.y = double_to_d6(info->shadow_y * render_priv->border_scale);
2213
2214                 current_info->x = current_info->y = INT_MAX;
2215                 rectangle_reset(&current_info->rect);
2216                 rectangle_reset(&current_info->rect_o);
2217                 current_info->bm = current_info->bm_o = current_info->bm_s = NULL;
2218                 current_info->n_bm = current_info->n_bm_o = 0;
2219
2220                 current_info->bitmap_count = current_info->max_bitmap_count = 0;
2221                 current_info->bitmaps = malloc(MAX_SUB_BITMAPS_INITIAL * sizeof(BitmapRef));
2222                 if (!current_info->bitmaps)
2223                     continue;
2224                 current_info->max_bitmap_count = MAX_SUB_BITMAPS_INITIAL;
2225
2226                 ++nb_bitmaps;
2227             }
2228             last_info = info;
2229
2230             if (!info->image || !current_info)
2231                 continue;
2232
2233             if (current_info->bitmap_count >= current_info->max_bitmap_count) {
2234                 size_t new_size = 2 * current_info->max_bitmap_count;
2235                 if (!ASS_REALLOC_ARRAY(current_info->bitmaps, new_size))
2236                     continue;
2237                 current_info->max_bitmap_count = new_size;
2238             }
2239             current_info->bitmaps[current_info->bitmap_count].image = info->image;
2240             current_info->bitmaps[current_info->bitmap_count].x = x;
2241             current_info->bitmaps[current_info->bitmap_count].y = y;
2242             ++current_info->bitmap_count;
2243
2244             current_info->x = FFMIN(current_info->x, x);
2245             current_info->y = FFMIN(current_info->y, y);
2246             if (info->image->bm) {
2247                 rectangle_combine(&current_info->rect, info->image->bm, x, y);
2248                 ++current_info->n_bm;
2249             }
2250             if (info->image->bm_o) {
2251                 rectangle_combine(&current_info->rect_o, info->image->bm_o, x, y);
2252                 ++current_info->n_bm_o;
2253             }
2254         }
2255     }
2256
2257     CompositeHashKey hk;
2258     CompositeHashValue *hv;
2259     for (int i = 0; i < nb_bitmaps; ++i) {
2260         CombinedBitmapInfo *info = &combined_info[i];
2261         for (int j = 0; j < info->bitmap_count; ++j) {
2262             info->bitmaps[j].x -= info->x;
2263             info->bitmaps[j].y -= info->y;
2264         }
2265
2266         fill_composite_hash(&hk, info);
2267
2268         hv = ass_cache_get(render_priv->cache.composite_cache, &hk);
2269         if (hv) {
2270             info->bm = hv->bm;
2271             info->bm_o = hv->bm_o;
2272             info->bm_s = hv->bm_s;
2273             free(info->bitmaps);
2274             continue;
2275         }
2276
2277         int bord = be_padding(info->filter.be);
2278         if (!bord && info->n_bm == 1) {
2279             for (int j = 0; j < info->bitmap_count; ++j) {
2280                 if (!info->bitmaps[j].image->bm)
2281                     continue;
2282                 info->bm = copy_bitmap(render_priv->engine, info->bitmaps[j].image->bm);
2283                 if (info->bm) {
2284                     info->bm->left += info->bitmaps[j].x;
2285                     info->bm->top  += info->bitmaps[j].y;
2286                 }
2287                 break;
2288             }
2289         } else if (info->n_bm) {
2290             info->bm = alloc_bitmap(render_priv->engine,
2291                                     info->rect.x_max - info->rect.x_min + 2 * bord,
2292                                     info->rect.y_max - info->rect.y_min + 2 * bord);
2293             Bitmap *dst = info->bm;
2294             if (dst) {
2295                 dst->left = info->rect.x_min - info->x - bord;
2296                 dst->top  = info->rect.y_min - info->y - bord;
2297                 for (int j = 0; j < info->bitmap_count; ++j) {
2298                     Bitmap *src = info->bitmaps[j].image->bm;
2299                     if (!src)
2300                         continue;
2301                     int x = info->bitmaps[j].x + src->left - dst->left;
2302                     int y = info->bitmaps[j].y + src->top  - dst->top;
2303                     assert(x >= 0 && x + src->w <= dst->w);
2304                     assert(y >= 0 && y + src->h <= dst->h);
2305                     unsigned char *buf = dst->buffer + y * dst->stride + x;
2306                     render_priv->engine->add_bitmaps(buf, dst->stride,
2307                                                      src->buffer, src->stride,
2308                                                      src->h, src->w);
2309                 }
2310             }
2311         }
2312         if (!bord && info->n_bm_o == 1) {
2313             for (int j = 0; j < info->bitmap_count; ++j) {
2314                 if (!info->bitmaps[j].image->bm_o)
2315                     continue;
2316                 info->bm_o = copy_bitmap(render_priv->engine, info->bitmaps[j].image->bm_o);
2317                 if (info->bm_o) {
2318                     info->bm_o->left += info->bitmaps[j].x;
2319                     info->bm_o->top  += info->bitmaps[j].y;
2320                 }
2321                 break;
2322             }
2323         } else if (info->n_bm_o) {
2324             info->bm_o = alloc_bitmap(render_priv->engine,
2325                                       info->rect_o.x_max - info->rect_o.x_min + 2 * bord,
2326                                       info->rect_o.y_max - info->rect_o.y_min + 2 * bord);
2327             Bitmap *dst = info->bm_o;
2328             if (dst) {
2329                 dst->left = info->rect_o.x_min - info->x - bord;
2330                 dst->top  = info->rect_o.y_min - info->y - bord;
2331                 for (int j = 0; j < info->bitmap_count; ++j) {
2332                     Bitmap *src = info->bitmaps[j].image->bm_o;
2333                     if (!src)
2334                         continue;
2335                     int x = info->bitmaps[j].x + src->left - dst->left;
2336                     int y = info->bitmaps[j].y + src->top  - dst->top;
2337                     assert(x >= 0 && x + src->w <= dst->w);
2338                     assert(y >= 0 && y + src->h <= dst->h);
2339                     unsigned char *buf = dst->buffer + y * dst->stride + x;
2340                     render_priv->engine->add_bitmaps(buf, dst->stride,
2341                                                      src->buffer, src->stride,
2342                                                      src->h, src->w);
2343                 }
2344             }
2345         }
2346
2347         if(info->bm || info->bm_o){
2348             ass_synth_blur(render_priv->engine, info->filter.flags & FILTER_BORDER_STYLE_3,
2349                            info->filter.be, info->filter.blur, info->bm, info->bm_o);
2350             if (info->filter.flags & FILTER_DRAW_SHADOW)
2351                 make_shadow_bitmap(info, render_priv);
2352         }
2353
2354         CompositeHashValue chv;
2355         chv.bm = info->bm;
2356         chv.bm_o = info->bm_o;
2357         chv.bm_s = info->bm_s;
2358
2359         ass_cache_put(render_priv->cache.composite_cache, &hk, &chv);
2360     }
2361
2362     text_info->n_bitmaps = nb_bitmaps;
2363 }
2364
2365 static void add_background(ASS_Renderer *render_priv, EventImages *event_images)
2366 {
2367     void *nbuffer = ass_aligned_alloc(1, event_images->width * event_images->height);
2368     if (!free_list_add(render_priv, nbuffer)) {
2369         ass_aligned_free(nbuffer);
2370     } else {
2371         memset(nbuffer, 0xFF, event_images->width * event_images->height);
2372         ASS_Image *img = my_draw_bitmap(nbuffer, event_images->width,
2373                                         event_images->height,
2374                                         event_images->width,
2375                                         event_images->left,
2376                                         event_images->top,
2377                                         render_priv->state.c[3]);
2378         if (img) {
2379             img->next = event_images->imgs;
2380             event_images->imgs = img;
2381         }
2382     }
2383 }
2384
2385 /**
2386  * \brief Main ass rendering function, glues everything together
2387  * \param event event to render
2388  * \param event_images struct containing resulting images, will also be initialized
2389  * Process event, appending resulting ASS_Image's to images_root.
2390  */
2391 static int
2392 ass_render_event(ASS_Renderer *render_priv, ASS_Event *event,
2393                  EventImages *event_images)
2394 {
2395     DBBox bbox;
2396     int MarginL, MarginR, MarginV;
2397     int valign;
2398     double device_x = 0;
2399     double device_y = 0;
2400     TextInfo *text_info = &render_priv->text_info;
2401
2402     if (event->Style >= render_priv->track->n_styles) {
2403         ass_msg(render_priv->library, MSGL_WARN, "No style found");
2404         return 1;
2405     }
2406     if (!event->Text) {
2407         ass_msg(render_priv->library, MSGL_WARN, "Empty event");
2408         return 1;
2409     }
2410
2411     free_render_context(render_priv);
2412     init_render_context(render_priv, event);
2413
2414     if (parse_events(render_priv, event))
2415         return 1;
2416
2417     if (text_info->length == 0) {
2418         // no valid symbols in the event; this can be smth like {comment}
2419         free_render_context(render_priv);
2420         return 1;
2421     }
2422
2423     // Find shape runs and shape text
2424     ass_shaper_set_base_direction(render_priv->shaper,
2425             resolve_base_direction(render_priv->state.font_encoding));
2426     ass_shaper_find_runs(render_priv->shaper, render_priv, text_info->glyphs,
2427             text_info->length);
2428     if (ass_shaper_shape(render_priv->shaper, text_info) < 0) {
2429         ass_msg(render_priv->library, MSGL_ERR, "Failed to shape text");
2430         free_render_context(render_priv);
2431         return 1;
2432     }
2433
2434     retrieve_glyphs(render_priv);
2435
2436     preliminary_layout(render_priv);
2437
2438     // depends on glyph x coordinates being monotonous, so it should be done before line wrap
2439     process_karaoke_effects(render_priv);
2440
2441     valign = render_priv->state.alignment & 12;
2442
2443     MarginL =
2444         (event->MarginL) ? event->MarginL : render_priv->state.style->MarginL;
2445     MarginR =
2446         (event->MarginR) ? event->MarginR : render_priv->state.style->MarginR;
2447     MarginV =
2448         (event->MarginV) ? event->MarginV : render_priv->state.style->MarginV;
2449
2450     // calculate max length of a line
2451     double max_text_width =
2452         x2scr(render_priv, render_priv->track->PlayResX - MarginR) -
2453         x2scr(render_priv, MarginL);
2454
2455     // wrap lines
2456     if (render_priv->state.evt_type != EVENT_HSCROLL) {
2457         // rearrange text in several lines
2458         wrap_lines_smart(render_priv, max_text_width);
2459     } else {
2460         // no breaking or wrapping, everything in a single line
2461         text_info->lines[0].offset = 0;
2462         text_info->lines[0].len = text_info->length;
2463         text_info->n_lines = 1;
2464         measure_text(render_priv);
2465     }
2466
2467     reorder_text(render_priv);
2468
2469     align_lines(render_priv, max_text_width);
2470
2471     // determing text bounding box
2472     compute_string_bbox(text_info, &bbox);
2473
2474     // determine device coordinates for text
2475
2476     // x coordinate for everything except positioned events
2477     if (render_priv->state.evt_type == EVENT_NORMAL ||
2478         render_priv->state.evt_type == EVENT_VSCROLL) {
2479         device_x = x2scr(render_priv, MarginL);
2480     } else if (render_priv->state.evt_type == EVENT_HSCROLL) {
2481         if (render_priv->state.scroll_direction == SCROLL_RL)
2482             device_x =
2483                 x2scr(render_priv,
2484                       render_priv->track->PlayResX -
2485                       render_priv->state.scroll_shift);
2486         else if (render_priv->state.scroll_direction == SCROLL_LR)
2487             device_x =
2488                 x2scr(render_priv,
2489                       render_priv->state.scroll_shift) - (bbox.xMax -
2490                                                           bbox.xMin);
2491     }
2492
2493     // y coordinate for everything except positioned events
2494     if (render_priv->state.evt_type == EVENT_NORMAL ||
2495         render_priv->state.evt_type == EVENT_HSCROLL) {
2496         if (valign == VALIGN_TOP) {     // toptitle
2497             device_y =
2498                 y2scr_top(render_priv,
2499                           MarginV) + text_info->lines[0].asc;
2500         } else if (valign == VALIGN_CENTER) {   // midtitle
2501             double scr_y =
2502                 y2scr(render_priv, render_priv->track->PlayResY / 2.0);
2503             device_y = scr_y - (bbox.yMax + bbox.yMin) / 2.0;
2504         } else {                // subtitle
2505             double line_pos = render_priv->state.explicit ?
2506                 0 : render_priv->settings.line_position;
2507             double scr_top, scr_bottom, scr_y0;
2508             if (valign != VALIGN_SUB)
2509                 ass_msg(render_priv->library, MSGL_V,
2510                        "Invalid valign, assuming 0 (subtitle)");
2511             scr_bottom =
2512                 y2scr_sub(render_priv,
2513                           render_priv->track->PlayResY - MarginV);
2514             scr_top = y2scr_top(render_priv, 0); //xxx not always 0?
2515             device_y = scr_bottom + (scr_top - scr_bottom) * line_pos / 100.0;
2516             device_y -= text_info->height;
2517             device_y += text_info->lines[0].asc;
2518             // clip to top to avoid confusion if line_position is very high,
2519             // turning the subtitle into a toptitle
2520             // also, don't change behavior if line_position is not used
2521             scr_y0 = scr_top + text_info->lines[0].asc;
2522             if (device_y < scr_y0 && line_pos > 0) {
2523                 device_y = scr_y0;
2524             }
2525         }
2526     } else if (render_priv->state.evt_type == EVENT_VSCROLL) {
2527         if (render_priv->state.scroll_direction == SCROLL_TB)
2528             device_y =
2529                 y2scr(render_priv,
2530                       render_priv->state.clip_y0 +
2531                       render_priv->state.scroll_shift) - (bbox.yMax -
2532                                                           bbox.yMin);
2533         else if (render_priv->state.scroll_direction == SCROLL_BT)
2534             device_y =
2535                 y2scr(render_priv,
2536                       render_priv->state.clip_y1 -
2537                       render_priv->state.scroll_shift);
2538     }
2539
2540     // positioned events are totally different
2541     if (render_priv->state.evt_type == EVENT_POSITIONED) {
2542         double base_x = 0;
2543         double base_y = 0;
2544         get_base_point(&bbox, render_priv->state.alignment, &base_x, &base_y);
2545         device_x =
2546             x2scr_pos(render_priv, render_priv->state.pos_x) - base_x;
2547         device_y =
2548             y2scr_pos(render_priv, render_priv->state.pos_y) - base_y;
2549     }
2550
2551     // fix clip coordinates (they depend on alignment)
2552     if (render_priv->state.evt_type == EVENT_NORMAL ||
2553         render_priv->state.evt_type == EVENT_HSCROLL ||
2554         render_priv->state.evt_type == EVENT_VSCROLL) {
2555         render_priv->state.clip_x0 =
2556             x2scr_scaled(render_priv, render_priv->state.clip_x0);
2557         render_priv->state.clip_x1 =
2558             x2scr_scaled(render_priv, render_priv->state.clip_x1);
2559         if (valign == VALIGN_TOP) {
2560             render_priv->state.clip_y0 =
2561                 y2scr_top(render_priv, render_priv->state.clip_y0);
2562             render_priv->state.clip_y1 =
2563                 y2scr_top(render_priv, render_priv->state.clip_y1);
2564         } else if (valign == VALIGN_CENTER) {
2565             render_priv->state.clip_y0 =
2566                 y2scr(render_priv, render_priv->state.clip_y0);
2567             render_priv->state.clip_y1 =
2568                 y2scr(render_priv, render_priv->state.clip_y1);
2569         } else if (valign == VALIGN_SUB) {
2570             render_priv->state.clip_y0 =
2571                 y2scr_sub(render_priv, render_priv->state.clip_y0);
2572             render_priv->state.clip_y1 =
2573                 y2scr_sub(render_priv, render_priv->state.clip_y1);
2574         }
2575     } else if (render_priv->state.evt_type == EVENT_POSITIONED) {
2576         render_priv->state.clip_x0 =
2577             x2scr_pos_scaled(render_priv, render_priv->state.clip_x0);
2578         render_priv->state.clip_x1 =
2579             x2scr_pos_scaled(render_priv, render_priv->state.clip_x1);
2580         render_priv->state.clip_y0 =
2581             y2scr_pos(render_priv, render_priv->state.clip_y0);
2582         render_priv->state.clip_y1 =
2583             y2scr_pos(render_priv, render_priv->state.clip_y1);
2584     }
2585
2586     if (render_priv->state.explicit) {
2587         // we still need to clip against screen boundaries
2588         double zx = x2scr_pos_scaled(render_priv, 0);
2589         double zy = y2scr_pos(render_priv, 0);
2590         double sx = x2scr_pos_scaled(render_priv, render_priv->track->PlayResX);
2591         double sy = y2scr_pos(render_priv, render_priv->track->PlayResY);
2592
2593         render_priv->state.clip_x0 = render_priv->state.clip_x0 < zx ? zx : render_priv->state.clip_x0;
2594         render_priv->state.clip_y0 = render_priv->state.clip_y0 < zy ? zy : render_priv->state.clip_y0;
2595         render_priv->state.clip_x1 = render_priv->state.clip_x1 > sx ? sx : render_priv->state.clip_x1;
2596         render_priv->state.clip_y1 = render_priv->state.clip_y1 > sy ? sy : render_priv->state.clip_y1;
2597     }
2598
2599     calculate_rotation_params(render_priv, &bbox, device_x, device_y);
2600
2601     render_and_combine_glyphs(render_priv, device_x, device_y);
2602
2603     memset(event_images, 0, sizeof(*event_images));
2604     event_images->top = device_y - text_info->lines[0].asc;
2605     event_images->height = text_info->height;
2606     event_images->left =
2607         (device_x + bbox.xMin * render_priv->font_scale_x) + 0.5;
2608     event_images->width =
2609         (bbox.xMax - bbox.xMin) * render_priv->font_scale_x + 0.5;
2610     event_images->detect_collisions = render_priv->state.detect_collisions;
2611     event_images->shift_direction = (valign == VALIGN_TOP) ? 1 : -1;
2612     event_images->event = event;
2613     event_images->imgs = render_text(render_priv);
2614
2615     if (render_priv->state.border_style == 4)
2616         add_background(render_priv, event_images);
2617
2618     ass_shaper_cleanup(render_priv->shaper, text_info);
2619     free_render_context(render_priv);
2620
2621     return 0;
2622 }
2623
2624 /**
2625  * \brief deallocate image list
2626  * \param img list pointer
2627  */
2628 void ass_free_images(ASS_Image *img)
2629 {
2630     while (img) {
2631         ASS_Image *next = img->next;
2632         free(img);
2633         img = next;
2634     }
2635 }
2636
2637 /**
2638  * \brief Check cache limits and reset cache if they are exceeded
2639  */
2640 static void check_cache_limits(ASS_Renderer *priv, CacheStore *cache)
2641 {
2642     if (ass_cache_empty(cache->bitmap_cache, cache->bitmap_max_size)) {
2643         ass_cache_empty(cache->composite_cache, 0);
2644         ass_free_images(priv->prev_images_root);
2645         priv->prev_images_root = 0;
2646         priv->cache_cleared = 1;
2647     }
2648     if (ass_cache_empty(cache->outline_cache, cache->glyph_max)) {
2649         ass_cache_empty(cache->bitmap_cache, 0);
2650         ass_cache_empty(cache->composite_cache, 0);
2651         ass_free_images(priv->prev_images_root);
2652         priv->prev_images_root = 0;
2653         priv->cache_cleared = 1;
2654     }
2655     if (ass_cache_empty(cache->composite_cache, cache->composite_max_size)) {
2656         ass_free_images(priv->prev_images_root);
2657         priv->prev_images_root = 0;
2658         priv->cache_cleared = 1;
2659     }
2660 }
2661
2662 /**
2663  * \brief Start a new frame
2664  */
2665 static int
2666 ass_start_frame(ASS_Renderer *render_priv, ASS_Track *track,
2667                 long long now)
2668 {
2669     ASS_Settings *settings_priv = &render_priv->settings;
2670
2671     if (!render_priv->settings.frame_width
2672         && !render_priv->settings.frame_height)
2673         return 1;               // library not initialized
2674
2675     if (!render_priv->fontselect)
2676         return 1;
2677
2678     if (render_priv->library != track->library)
2679         return 1;
2680
2681     free_list_clear(render_priv);
2682
2683     if (track->n_events == 0)
2684         return 1;               // nothing to do
2685
2686     render_priv->track = track;
2687     render_priv->time = now;
2688
2689     ass_lazy_track_init(render_priv->library, render_priv->track);
2690
2691     ass_shaper_set_kerning(render_priv->shaper, track->Kerning);
2692     ass_shaper_set_language(render_priv->shaper, track->Language);
2693     ass_shaper_set_level(render_priv->shaper, render_priv->settings.shaper);
2694
2695     // PAR correction
2696     double par = render_priv->settings.par;
2697     if (par == 0.) {
2698         if (settings_priv->frame_width && settings_priv->frame_height &&
2699             settings_priv->storage_width && settings_priv->storage_height) {
2700             double dar = ((double) settings_priv->frame_width) /
2701                          settings_priv->frame_height;
2702             double sar = ((double) settings_priv->storage_width) /
2703                          settings_priv->storage_height;
2704             par = sar / dar;
2705         } else
2706             par = 1.0;
2707     }
2708     render_priv->font_scale_x = par;
2709
2710     render_priv->prev_images_root = render_priv->images_root;
2711     render_priv->images_root = 0;
2712
2713     check_cache_limits(render_priv, &render_priv->cache);
2714
2715     return 0;
2716 }
2717
2718 static int cmp_event_layer(const void *p1, const void *p2)
2719 {
2720     ASS_Event *e1 = ((EventImages *) p1)->event;
2721     ASS_Event *e2 = ((EventImages *) p2)->event;
2722     if (e1->Layer < e2->Layer)
2723         return -1;
2724     if (e1->Layer > e2->Layer)
2725         return 1;
2726     if (e1->ReadOrder < e2->ReadOrder)
2727         return -1;
2728     if (e1->ReadOrder > e2->ReadOrder)
2729         return 1;
2730     return 0;
2731 }
2732
2733 static ASS_RenderPriv *get_render_priv(ASS_Renderer *render_priv,
2734                                        ASS_Event *event)
2735 {
2736     if (!event->render_priv) {
2737         event->render_priv = calloc(1, sizeof(ASS_RenderPriv));
2738         if (!event->render_priv)
2739             return NULL;
2740     }
2741     if (render_priv->render_id != event->render_priv->render_id) {
2742         memset(event->render_priv, 0, sizeof(ASS_RenderPriv));
2743         event->render_priv->render_id = render_priv->render_id;
2744     }
2745
2746     return event->render_priv;
2747 }
2748
2749 static int overlap(Segment *s1, Segment *s2)
2750 {
2751     if (s1->a >= s2->b || s2->a >= s1->b ||
2752         s1->ha >= s2->hb || s2->ha >= s1->hb)
2753         return 0;
2754     return 1;
2755 }
2756
2757 static int cmp_segment(const void *p1, const void *p2)
2758 {
2759     return ((Segment *) p1)->a - ((Segment *) p2)->a;
2760 }
2761
2762 static void
2763 shift_event(ASS_Renderer *render_priv, EventImages *ei, int shift)
2764 {
2765     ASS_Image *cur = ei->imgs;
2766     while (cur) {
2767         cur->dst_y += shift;
2768         // clip top and bottom
2769         if (cur->dst_y < 0) {
2770             int clip = -cur->dst_y;
2771             cur->h -= clip;
2772             cur->bitmap += clip * cur->stride;
2773             cur->dst_y = 0;
2774         }
2775         if (cur->dst_y + cur->h >= render_priv->height) {
2776             int clip = cur->dst_y + cur->h - render_priv->height;
2777             cur->h -= clip;
2778         }
2779         if (cur->h <= 0) {
2780             cur->h = 0;
2781             cur->dst_y = 0;
2782         }
2783         cur = cur->next;
2784     }
2785     ei->top += shift;
2786 }
2787
2788 // dir: 1 - move down
2789 //      -1 - move up
2790 static int fit_segment(Segment *s, Segment *fixed, int *cnt, int dir)
2791 {
2792     int i;
2793     int shift = 0;
2794
2795     if (dir == 1)               // move down
2796         for (i = 0; i < *cnt; ++i) {
2797             if (s->b + shift <= fixed[i].a || s->a + shift >= fixed[i].b ||
2798                 s->hb <= fixed[i].ha || s->ha >= fixed[i].hb)
2799                 continue;
2800             shift = fixed[i].b - s->a;
2801     } else                      // dir == -1, move up
2802         for (i = *cnt - 1; i >= 0; --i) {
2803             if (s->b + shift <= fixed[i].a || s->a + shift >= fixed[i].b ||
2804                 s->hb <= fixed[i].ha || s->ha >= fixed[i].hb)
2805                 continue;
2806             shift = fixed[i].a - s->b;
2807         }
2808
2809     fixed[*cnt].a = s->a + shift;
2810     fixed[*cnt].b = s->b + shift;
2811     fixed[*cnt].ha = s->ha;
2812     fixed[*cnt].hb = s->hb;
2813     (*cnt)++;
2814     qsort(fixed, *cnt, sizeof(Segment), cmp_segment);
2815
2816     return shift;
2817 }
2818
2819 static void
2820 fix_collisions(ASS_Renderer *render_priv, EventImages *imgs, int cnt)
2821 {
2822     Segment *used = ass_realloc_array(NULL, cnt, sizeof(*used));
2823     int cnt_used = 0;
2824     int i, j;
2825
2826     if (!used)
2827         return;
2828
2829     // fill used[] with fixed events
2830     for (i = 0; i < cnt; ++i) {
2831         ASS_RenderPriv *priv;
2832         if (!imgs[i].detect_collisions)
2833             continue;
2834         priv = get_render_priv(render_priv, imgs[i].event);
2835         if (priv && priv->height > 0) { // it's a fixed event
2836             Segment s;
2837             s.a = priv->top;
2838             s.b = priv->top + priv->height;
2839             s.ha = priv->left;
2840             s.hb = priv->left + priv->width;
2841             if (priv->height != imgs[i].height) {       // no, it's not
2842                 ass_msg(render_priv->library, MSGL_WARN,
2843                         "Event height has changed");
2844                 priv->top = 0;
2845                 priv->height = 0;
2846                 priv->left = 0;
2847                 priv->width = 0;
2848             }
2849             for (j = 0; j < cnt_used; ++j)
2850                 if (overlap(&s, used + j)) {    // no, it's not
2851                     priv->top = 0;
2852                     priv->height = 0;
2853                     priv->left = 0;
2854                     priv->width = 0;
2855                 }
2856             if (priv->height > 0) {     // still a fixed event
2857                 used[cnt_used].a = priv->top;
2858                 used[cnt_used].b = priv->top + priv->height;
2859                 used[cnt_used].ha = priv->left;
2860                 used[cnt_used].hb = priv->left + priv->width;
2861                 cnt_used++;
2862                 shift_event(render_priv, imgs + i, priv->top - imgs[i].top);
2863             }
2864         }
2865     }
2866     qsort(used, cnt_used, sizeof(Segment), cmp_segment);
2867
2868     // try to fit other events in free spaces
2869     for (i = 0; i < cnt; ++i) {
2870         ASS_RenderPriv *priv;
2871         if (!imgs[i].detect_collisions)
2872             continue;
2873         priv = get_render_priv(render_priv, imgs[i].event);
2874         if (priv && priv->height == 0) {        // not a fixed event
2875             int shift;
2876             Segment s;
2877             s.a = imgs[i].top;
2878             s.b = imgs[i].top + imgs[i].height;
2879             s.ha = imgs[i].left;
2880             s.hb = imgs[i].left + imgs[i].width;
2881             shift = fit_segment(&s, used, &cnt_used, imgs[i].shift_direction);
2882             if (shift)
2883                 shift_event(render_priv, imgs + i, shift);
2884             // make it fixed
2885             priv->top = imgs[i].top;
2886             priv->height = imgs[i].height;
2887             priv->left = imgs[i].left;
2888             priv->width = imgs[i].width;
2889         }
2890
2891     }
2892
2893     free(used);
2894 }
2895
2896 /**
2897  * \brief compare two images
2898  * \param i1 first image
2899  * \param i2 second image
2900  * \return 0 if identical, 1 if different positions, 2 if different content
2901  */
2902 static int ass_image_compare(ASS_Image *i1, ASS_Image *i2)
2903 {
2904     if (i1->w != i2->w)
2905         return 2;
2906     if (i1->h != i2->h)
2907         return 2;
2908     if (i1->stride != i2->stride)
2909         return 2;
2910     if (i1->color != i2->color)
2911         return 2;
2912     if (i1->bitmap != i2->bitmap)
2913         return 2;
2914     if (i1->dst_x != i2->dst_x)
2915         return 1;
2916     if (i1->dst_y != i2->dst_y)
2917         return 1;
2918     return 0;
2919 }
2920
2921 /**
2922  * \brief compare current and previous image list
2923  * \param priv library handle
2924  * \return 0 if identical, 1 if different positions, 2 if different content
2925  */
2926 static int ass_detect_change(ASS_Renderer *priv)
2927 {
2928     ASS_Image *img, *img2;
2929     int diff;
2930
2931     if (priv->cache_cleared || priv->state.has_clips)
2932         return 2;
2933
2934     img = priv->prev_images_root;
2935     img2 = priv->images_root;
2936     diff = 0;
2937     while (img && diff < 2) {
2938         ASS_Image *next, *next2;
2939         next = img->next;
2940         if (img2) {
2941             int d = ass_image_compare(img, img2);
2942             if (d > diff)
2943                 diff = d;
2944             next2 = img2->next;
2945         } else {
2946             // previous list is shorter
2947             diff = 2;
2948             break;
2949         }
2950         img = next;
2951         img2 = next2;
2952     }
2953
2954     // is the previous list longer?
2955     if (img2)
2956         diff = 2;
2957
2958     return diff;
2959 }
2960
2961 /**
2962  * \brief render a frame
2963  * \param priv library handle
2964  * \param track track
2965  * \param now current video timestamp (ms)
2966  * \param detect_change a value describing how the new images differ from the previous ones will be written here:
2967  *        0 if identical, 1 if different positions, 2 if different content.
2968  *        Can be NULL, in that case no detection is performed.
2969  */
2970 ASS_Image *ass_render_frame(ASS_Renderer *priv, ASS_Track *track,
2971                             long long now, int *detect_change)
2972 {
2973     int i, cnt, rc;
2974     EventImages *last;
2975     ASS_Image **tail;
2976
2977     // init frame
2978     rc = ass_start_frame(priv, track, now);
2979     if (rc != 0) {
2980         if (detect_change) {
2981             *detect_change = 2;
2982         }
2983         return 0;
2984     }
2985
2986     // render events separately
2987     cnt = 0;
2988     for (i = 0; i < track->n_events; ++i) {
2989         ASS_Event *event = track->events + i;
2990         if ((event->Start <= now)
2991             && (now < (event->Start + event->Duration))) {
2992             if (cnt >= priv->eimg_size) {
2993                 priv->eimg_size += 100;
2994                 priv->eimg =
2995                     realloc(priv->eimg,
2996                             priv->eimg_size * sizeof(EventImages));
2997             }
2998             rc = ass_render_event(priv, event, priv->eimg + cnt);
2999             if (!rc)
3000                 ++cnt;
3001         }
3002     }
3003
3004     // sort by layer
3005     qsort(priv->eimg, cnt, sizeof(EventImages), cmp_event_layer);
3006
3007     // call fix_collisions for each group of events with the same layer
3008     last = priv->eimg;
3009     for (i = 1; i < cnt; ++i)
3010         if (last->event->Layer != priv->eimg[i].event->Layer) {
3011             fix_collisions(priv, last, priv->eimg + i - last);
3012             last = priv->eimg + i;
3013         }
3014     if (cnt > 0)
3015         fix_collisions(priv, last, priv->eimg + cnt - last);
3016
3017     // concat lists
3018     tail = &priv->images_root;
3019     for (i = 0; i < cnt; ++i) {
3020         ASS_Image *cur = priv->eimg[i].imgs;
3021         while (cur) {
3022             *tail = cur;
3023             tail = &cur->next;
3024             cur = cur->next;
3025         }
3026     }
3027
3028     if (detect_change)
3029         *detect_change = ass_detect_change(priv);
3030
3031     // free the previous image list
3032     ass_free_images(priv->prev_images_root);
3033     priv->prev_images_root = 0;
3034     priv->cache_cleared = 0;
3035
3036     return priv->images_root;
3037 }