2 * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
4 * This file is part of libass.
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.
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.
20 #include "ass_compat.h"
27 #include "ass_outline.h"
28 #include "ass_render.h"
29 #include "ass_parse.h"
30 #include "ass_shaper.h"
32 #define MAX_GLYPHS_INITIAL 1024
33 #define MAX_LINES_INITIAL 64
34 #define MAX_BITMAPS_INITIAL 16
35 #define MAX_SUB_BITMAPS_INITIAL 64
36 #define SUBPIXEL_MASK 63
37 #define SUBPIXEL_ACCURACY 7
40 ASS_Renderer *ass_renderer_init(ASS_Library *library)
44 ASS_Renderer *priv = 0;
45 int vmajor, vminor, vpatch;
47 error = FT_Init_FreeType(&ft);
49 ass_msg(library, MSGL_FATAL, "%s failed", "FT_Init_FreeType");
53 FT_Library_Version(ft, &vmajor, &vminor, &vpatch);
54 ass_msg(library, MSGL_V, "Raster: FreeType %d.%d.%d",
55 vmajor, vminor, vpatch);
57 priv = calloc(1, sizeof(ASS_Renderer));
63 priv->library = library;
65 // images_root and related stuff is zero-filled in calloc
67 #if (defined(__i386__) || defined(__x86_64__)) && CONFIG_ASM
69 priv->engine = &ass_bitmap_engine_avx2;
71 priv->engine = &ass_bitmap_engine_sse2;
73 priv->engine = &ass_bitmap_engine_c;
75 priv->engine = &ass_bitmap_engine_c;
78 if (!rasterizer_init(&priv->rasterizer, priv->engine->tile_order, 16)) {
83 priv->cache.font_cache = ass_font_cache_create();
84 priv->cache.bitmap_cache = ass_bitmap_cache_create();
85 priv->cache.composite_cache = ass_composite_cache_create();
86 priv->cache.outline_cache = ass_outline_cache_create();
87 priv->cache.glyph_max = GLYPH_CACHE_MAX;
88 priv->cache.bitmap_max_size = BITMAP_CACHE_MAX_SIZE;
89 priv->cache.composite_max_size = COMPOSITE_CACHE_MAX_SIZE;
91 priv->text_info.max_bitmaps = MAX_BITMAPS_INITIAL;
92 priv->text_info.max_glyphs = MAX_GLYPHS_INITIAL;
93 priv->text_info.max_lines = MAX_LINES_INITIAL;
94 priv->text_info.n_bitmaps = 0;
95 priv->text_info.combined_bitmaps = calloc(MAX_BITMAPS_INITIAL, sizeof(CombinedBitmapInfo));
96 priv->text_info.glyphs = calloc(MAX_GLYPHS_INITIAL, sizeof(GlyphInfo));
97 priv->text_info.lines = calloc(MAX_LINES_INITIAL, sizeof(LineInfo));
99 priv->settings.font_size_coeff = 1.;
100 priv->settings.selective_style_overrides = ASS_OVERRIDE_BIT_SELECTIVE_FONT_SCALE;
102 priv->shaper = ass_shaper_new(0);
103 ass_shaper_info(library);
104 #ifdef CONFIG_HARFBUZZ
105 priv->settings.shaper = ASS_SHAPING_COMPLEX;
107 priv->settings.shaper = ASS_SHAPING_SIMPLE;
112 ass_msg(library, MSGL_V, "Initialized");
114 ass_msg(library, MSGL_ERR, "Initialization failed");
119 void ass_renderer_done(ASS_Renderer *render_priv)
121 ass_frame_unref(render_priv->images_root);
122 ass_frame_unref(render_priv->prev_images_root);
124 ass_cache_done(render_priv->cache.composite_cache);
125 ass_cache_done(render_priv->cache.bitmap_cache);
126 ass_cache_done(render_priv->cache.outline_cache);
127 ass_shaper_free(render_priv->shaper);
128 ass_cache_done(render_priv->cache.font_cache);
130 rasterizer_done(&render_priv->rasterizer);
132 if (render_priv->fontselect)
133 ass_fontselect_free(render_priv->fontselect);
134 if (render_priv->ftlibrary)
135 FT_Done_FreeType(render_priv->ftlibrary);
136 free(render_priv->eimg);
137 free(render_priv->text_info.glyphs);
138 free(render_priv->text_info.lines);
140 free(render_priv->text_info.combined_bitmaps);
142 free(render_priv->settings.default_font);
143 free(render_priv->settings.default_family);
145 free(render_priv->user_override_style.FontName);
151 * \brief Create a new ASS_Image
152 * Parameters are the same as ASS_Image fields.
154 static ASS_Image *my_draw_bitmap(unsigned char *bitmap, int bitmap_w,
155 int bitmap_h, int stride, int dst_x,
156 int dst_y, uint32_t color,
157 CompositeHashValue *source)
159 ASS_ImagePriv *img = malloc(sizeof(ASS_ImagePriv));
162 ass_aligned_free(bitmap);
166 img->result.w = bitmap_w;
167 img->result.h = bitmap_h;
168 img->result.stride = stride;
169 img->result.bitmap = bitmap;
170 img->result.color = color;
171 img->result.dst_x = dst_x;
172 img->result.dst_y = dst_y;
174 img->source = source;
175 ass_cache_inc_ref(source);
182 * \brief Mapping between script and screen coordinates
184 static double x2scr_pos(ASS_Renderer *render_priv, double x)
186 return x * render_priv->orig_width / render_priv->font_scale_x / render_priv->track->PlayResX +
187 render_priv->settings.left_margin;
189 static double x2scr(ASS_Renderer *render_priv, double x)
191 if (render_priv->state.explicit)
192 return x2scr_pos(render_priv, x);
193 return x * render_priv->orig_width_nocrop / render_priv->font_scale_x /
194 render_priv->track->PlayResX +
195 FFMAX(render_priv->settings.left_margin, 0);
197 static double x2scr_pos_scaled(ASS_Renderer *render_priv, double x)
199 return x * render_priv->orig_width / render_priv->track->PlayResX +
200 render_priv->settings.left_margin;
202 static double x2scr_scaled(ASS_Renderer *render_priv, double x)
204 if (render_priv->state.explicit)
205 return x2scr_pos_scaled(render_priv, x);
206 return x * render_priv->orig_width_nocrop /
207 render_priv->track->PlayResX +
208 FFMAX(render_priv->settings.left_margin, 0);
211 * \brief Mapping between script and screen coordinates
213 static double y2scr_pos(ASS_Renderer *render_priv, double y)
215 return y * render_priv->orig_height / render_priv->track->PlayResY +
216 render_priv->settings.top_margin;
218 static double y2scr(ASS_Renderer *render_priv, double y)
220 if (render_priv->state.explicit)
221 return y2scr_pos(render_priv, y);
222 return y * render_priv->orig_height_nocrop /
223 render_priv->track->PlayResY +
224 FFMAX(render_priv->settings.top_margin, 0);
227 // the same for toptitles
228 static double y2scr_top(ASS_Renderer *render_priv, double y)
230 if (render_priv->state.explicit)
231 return y2scr_pos(render_priv, y);
232 if (render_priv->settings.use_margins)
233 return y * render_priv->orig_height_nocrop /
234 render_priv->track->PlayResY;
236 return y * render_priv->orig_height_nocrop /
237 render_priv->track->PlayResY +
238 FFMAX(render_priv->settings.top_margin, 0);
240 // the same for subtitles
241 static double y2scr_sub(ASS_Renderer *render_priv, double y)
243 if (render_priv->state.explicit)
244 return y2scr_pos(render_priv, y);
245 if (render_priv->settings.use_margins)
246 return y * render_priv->orig_height_nocrop /
247 render_priv->track->PlayResY +
248 FFMAX(render_priv->settings.top_margin, 0)
249 + FFMAX(render_priv->settings.bottom_margin, 0);
251 return y * render_priv->orig_height_nocrop /
252 render_priv->track->PlayResY +
253 FFMAX(render_priv->settings.top_margin, 0);
257 * \brief Convert bitmap glyphs into ASS_Image list with inverse clipping
259 * Inverse clipping with the following strategy:
260 * - find rectangle from (x0, y0) to (cx0, y1)
261 * - find rectangle from (cx0, y0) to (cx1, cy0)
262 * - find rectangle from (cx0, cy1) to (cx1, y1)
263 * - find rectangle from (cx1, y0) to (x1, y1)
264 * These rectangles can be invalid and in this case are discarded.
265 * Afterwards, they are clipped against the screen coordinates.
266 * In an additional pass, the rectangles need to be split up left/right for
267 * karaoke effects. This can result in a lot of bitmaps (6 to be exact).
269 static ASS_Image **render_glyph_i(ASS_Renderer *render_priv,
270 Bitmap *bm, int dst_x, int dst_y,
271 uint32_t color, uint32_t color2, int brk,
272 ASS_Image **tail, unsigned type,
273 CompositeHashValue *source)
275 int i, j, x0, y0, x1, y1, cx0, cy0, cx1, cy1, sx, sy, zx, zy;
282 // we still need to clip against screen boundaries
283 zx = x2scr_pos_scaled(render_priv, 0);
284 zy = y2scr_pos(render_priv, 0);
285 sx = x2scr_pos_scaled(render_priv, render_priv->track->PlayResX);
286 sy = y2scr_pos(render_priv, render_priv->track->PlayResY);
292 cx0 = render_priv->state.clip_x0 - dst_x;
293 cy0 = render_priv->state.clip_y0 - dst_y;
294 cx1 = render_priv->state.clip_x1 - dst_x;
295 cy1 = render_priv->state.clip_y1 - dst_y;
297 // calculate rectangles and discard invalid ones while we're at it.
301 r[i].x1 = (cx0 > x1) ? x1 : cx0;
303 if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++;
304 r[i].x0 = (cx0 < 0) ? x0 : cx0;
306 r[i].x1 = (cx1 > x1) ? x1 : cx1;
307 r[i].y1 = (cy0 > y1) ? y1 : cy0;
308 if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++;
309 r[i].x0 = (cx0 < 0) ? x0 : cx0;
310 r[i].y0 = (cy1 < 0) ? y0 : cy1;
311 r[i].x1 = (cx1 > x1) ? x1 : cx1;
313 if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++;
314 r[i].x0 = (cx1 < 0) ? x0 : cx1;
318 if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++;
320 // clip each rectangle to screen coordinates
321 for (j = 0; j < i; j++) {
322 r[j].x0 = (r[j].x0 + dst_x < zx) ? zx - dst_x : r[j].x0;
323 r[j].y0 = (r[j].y0 + dst_y < zy) ? zy - dst_y : r[j].y0;
324 r[j].x1 = (r[j].x1 + dst_x > sx) ? sx - dst_x : r[j].x1;
325 r[j].y1 = (r[j].y1 + dst_y > sy) ? sy - dst_y : r[j].y1;
328 // draw the rectangles
329 for (j = 0; j < i; j++) {
331 // kick out rectangles that are invalid now
332 if (r[j].x1 <= r[j].x0 || r[j].y1 <= r[j].y0)
334 // split up into left and right for karaoke, if needed
335 if (lbrk > r[j].x0) {
336 if (lbrk > r[j].x1) lbrk = r[j].x1;
337 img = my_draw_bitmap(bm->buffer + r[j].y0 * bm->stride + r[j].x0,
338 lbrk - r[j].x0, r[j].y1 - r[j].y0, bm->stride,
339 dst_x + r[j].x0, dst_y + r[j].y0, color, source);
345 if (lbrk < r[j].x1) {
346 if (lbrk < r[j].x0) lbrk = r[j].x0;
347 img = my_draw_bitmap(bm->buffer + r[j].y0 * bm->stride + lbrk,
348 r[j].x1 - lbrk, r[j].y1 - r[j].y0, bm->stride,
349 dst_x + lbrk, dst_y + r[j].y0, color2, source);
361 * \brief convert bitmap glyph into ASS_Image struct(s)
362 * \param bit freetype bitmap glyph, FT_PIXEL_MODE_GRAY
363 * \param dst_x bitmap x coordinate in video frame
364 * \param dst_y bitmap y coordinate in video frame
365 * \param color first color, RGBA
366 * \param color2 second color, RGBA
367 * \param brk x coordinate relative to glyph origin, color is used to the left of brk, color2 - to the right
368 * \param tail pointer to the last image's next field, head of the generated list should be stored here
369 * \return pointer to the new list tail
370 * Performs clipping. Uses my_draw_bitmap for actual bitmap convertion.
373 render_glyph(ASS_Renderer *render_priv, Bitmap *bm, int dst_x, int dst_y,
374 uint32_t color, uint32_t color2, int brk, ASS_Image **tail,
375 unsigned type, CompositeHashValue *source)
377 // Inverse clipping in use?
378 if (render_priv->state.clip_mode)
379 return render_glyph_i(render_priv, bm, dst_x, dst_y, color, color2,
380 brk, tail, type, source);
382 // brk is relative to dst_x
383 // color = color left of brk
384 // color2 = color right of brk
385 int b_x0, b_y0, b_x1, b_y1; // visible part of the bitmap
386 int clip_x0, clip_y0, clip_x1, clip_y1;
395 clip_x0 = FFMINMAX(render_priv->state.clip_x0, 0, render_priv->width);
396 clip_y0 = FFMINMAX(render_priv->state.clip_y0, 0, render_priv->height);
397 clip_x1 = FFMINMAX(render_priv->state.clip_x1, 0, render_priv->width);
398 clip_y1 = FFMINMAX(render_priv->state.clip_y1, 0, render_priv->height);
404 tmp = dst_x - clip_x0;
407 tmp = dst_y - clip_y0;
410 tmp = clip_x1 - dst_x - bm->w;
413 tmp = clip_y1 - dst_y - bm->h;
417 if ((b_y0 >= b_y1) || (b_x0 >= b_x1))
420 if (brk > b_x0) { // draw left part
423 img = my_draw_bitmap(bm->buffer + bm->stride * b_y0 + b_x0,
424 brk - b_x0, b_y1 - b_y0, bm->stride,
425 dst_x + b_x0, dst_y + b_y0, color, source);
426 if (!img) return tail;
431 if (brk < b_x1) { // draw right part
434 img = my_draw_bitmap(bm->buffer + bm->stride * b_y0 + brk,
435 b_x1 - brk, b_y1 - b_y0, bm->stride,
436 dst_x + brk, dst_y + b_y0, color2, source);
437 if (!img) return tail;
445 // Calculate bitmap memory footprint
446 static inline size_t bitmap_size(Bitmap *bm)
448 return bm ? sizeof(Bitmap) + bm->stride * bm->h : 0;
452 * Iterate through a list of bitmaps and blend with clip vector, if
453 * applicable. The blended bitmaps are added to a free list which is freed
454 * at the start of a new frame.
456 static void blend_vector_clip(ASS_Renderer *render_priv,
459 ASS_Drawing *drawing = render_priv->state.clip_drawing;
463 // Try to get mask from cache
465 memset(&key, 0, sizeof(key));
466 key.type = BITMAP_CLIP;
467 key.u.clip.text = drawing->text;
469 BitmapHashValue *val;
470 if (!ass_cache_get(render_priv->cache.bitmap_cache, &key, &val)) {
473 val->bm = val->bm_o = NULL;
475 // Not found in cache, parse and rasterize it
476 ASS_Outline *outline = ass_drawing_parse(drawing, 1);
478 ass_msg(render_priv->library, MSGL_WARN,
479 "Clip vector parsing failed. Skipping.");
480 ass_cache_commit(val, sizeof(BitmapHashKey) + sizeof(BitmapHashValue));
481 ass_cache_dec_ref(val);
485 // We need to translate the clip according to screen borders
486 if (render_priv->settings.left_margin != 0 ||
487 render_priv->settings.top_margin != 0) {
489 .x = int_to_d6(render_priv->settings.left_margin),
490 .y = -int_to_d6(render_priv->settings.top_margin),
492 outline_translate(outline, trans.x, trans.y);
495 val->bm = outline_to_bitmap(render_priv, outline, NULL, 1);
496 ass_cache_commit(val, bitmap_size(val->bm) +
497 sizeof(BitmapHashKey) + sizeof(BitmapHashValue));
500 Bitmap *clip_bm = val->bm;
502 ass_cache_dec_ref(val);
506 // Iterate through bitmaps and blend/clip them
507 for (ASS_Image *cur = head; cur; cur = cur->next) {
508 int left, top, right, bottom, w, h;
509 int ax, ay, aw, ah, as;
510 int bx, by, bw, bh, bs;
511 int aleft, atop, bleft, btop;
512 unsigned char *abuffer, *bbuffer, *nbuffer;
514 abuffer = cur->bitmap;
515 bbuffer = clip_bm->buffer;
525 bs = clip_bm->stride;
527 // Calculate overlap coordinates
528 left = (ax > bx) ? ax : bx;
529 top = (ay > by) ? ay : by;
530 right = ((ax + aw) < (bx + bw)) ? (ax + aw) : (bx + bw);
531 bottom = ((ay + ah) < (by + bh)) ? (ay + ah) : (by + bh);
539 if (render_priv->state.clip_drawing_mode) {
541 if (ax + aw < bx || ay + ah < by || ax > bx + bw ||
542 ay > by + bh || !h || !w) {
546 // Allocate new buffer and add to free list
547 nbuffer = ass_aligned_alloc(32, as * ah, false);
552 memcpy(nbuffer, abuffer, ((ah - 1) * as) + aw);
553 render_priv->engine->sub_bitmaps(nbuffer + atop * as + aleft, as,
554 bbuffer + btop * bs + bleft, bs,
558 if (ax + aw < bx || ay + ah < by || ax > bx + bw ||
559 ay > by + bh || !h || !w) {
560 cur->w = cur->h = cur->stride = 0;
564 // Allocate new buffer and add to free list
565 unsigned align = (w >= 16) ? 16 : ((w >= 8) ? 8 : 1);
566 unsigned ns = ass_align(align, w);
567 nbuffer = ass_aligned_alloc(align, ns * h, false);
572 render_priv->engine->mul_bitmaps(nbuffer, ns,
573 abuffer + atop * as + aleft, as,
574 bbuffer + btop * bs + bleft, bs,
583 cur->bitmap = nbuffer;
584 ASS_ImagePriv *priv = (ASS_ImagePriv *) cur;
585 ass_cache_dec_ref(priv->source);
589 ass_cache_dec_ref(val);
593 * \brief Convert TextInfo struct to ASS_Image list
594 * Splits glyphs in halves when needed (for \kf karaoke).
596 static ASS_Image *render_text(ASS_Renderer *render_priv)
599 ASS_Image **tail = &head;
600 unsigned n_bitmaps = render_priv->text_info.n_bitmaps;
601 CombinedBitmapInfo *bitmaps = render_priv->text_info.combined_bitmaps;
603 for (unsigned i = 0; i < n_bitmaps; i++) {
604 CombinedBitmapInfo *info = &bitmaps[i];
605 if (!info->bm_s || render_priv->state.border_style == 4)
609 render_glyph(render_priv, info->bm_s, info->x, info->y, info->c[3], 0,
610 1000000, tail, IMAGE_TYPE_SHADOW, info->image);
613 for (unsigned i = 0; i < n_bitmaps; i++) {
614 CombinedBitmapInfo *info = &bitmaps[i];
618 if ((info->effect_type == EF_KARAOKE_KO)
619 && (info->effect_timing <= info->first_pos_x)) {
623 render_glyph(render_priv, info->bm_o, info->x, info->y, info->c[2],
624 0, 1000000, tail, IMAGE_TYPE_OUTLINE, info->image);
628 for (unsigned i = 0; i < n_bitmaps; i++) {
629 CombinedBitmapInfo *info = &bitmaps[i];
633 if ((info->effect_type == EF_KARAOKE)
634 || (info->effect_type == EF_KARAOKE_KO)) {
635 if (info->effect_timing > info->first_pos_x)
637 render_glyph(render_priv, info->bm, info->x, info->y,
638 info->c[0], 0, 1000000, tail,
639 IMAGE_TYPE_CHARACTER, info->image);
642 render_glyph(render_priv, info->bm, info->x, info->y,
643 info->c[1], 0, 1000000, tail,
644 IMAGE_TYPE_CHARACTER, info->image);
645 } else if (info->effect_type == EF_KARAOKE_KF) {
647 render_glyph(render_priv, info->bm, info->x, info->y, info->c[0],
648 info->c[1], info->effect_timing, tail,
649 IMAGE_TYPE_CHARACTER, info->image);
652 render_glyph(render_priv, info->bm, info->x, info->y, info->c[0],
653 0, 1000000, tail, IMAGE_TYPE_CHARACTER, info->image);
656 for (unsigned i = 0; i < n_bitmaps; i++)
657 ass_cache_dec_ref(bitmaps[i].image);
660 blend_vector_clip(render_priv, head);
665 static void compute_string_bbox(TextInfo *text, DBBox *bbox)
669 if (text->length > 0) {
672 bbox->yMin = -1 * text->lines[0].asc + d6_to_double(text->glyphs[0].pos.y);
673 bbox->yMax = text->height - text->lines[0].asc +
674 d6_to_double(text->glyphs[0].pos.y);
676 for (i = 0; i < text->length; ++i) {
677 GlyphInfo *info = text->glyphs + i;
678 if (info->skip) continue;
679 double s = d6_to_double(info->pos.x);
680 double e = s + d6_to_double(info->cluster_advance.x);
681 bbox->xMin = FFMIN(bbox->xMin, s);
682 bbox->xMax = FFMAX(bbox->xMax, e);
685 bbox->xMin = bbox->xMax = bbox->yMin = bbox->yMax = 0.;
688 static ASS_Style *handle_selective_style_overrides(ASS_Renderer *render_priv,
691 // The script style is the one the event was declared with.
692 ASS_Style *script = render_priv->track->styles +
693 render_priv->state.event->Style;
694 // The user style was set with ass_set_selective_style_override().
695 ASS_Style *user = &render_priv->user_override_style;
696 ASS_Style *new = &render_priv->state.override_style_temp_storage;
697 int explicit = event_has_hard_overrides(render_priv->state.event->Text) ||
698 render_priv->state.evt_type != EVENT_NORMAL;
699 int requested = render_priv->settings.selective_style_overrides;
702 user->Name = "OverrideStyle"; // name insignificant
704 // Either the event's style, or the style forced with a \r tag.
708 // Create a new style that contains a mix of the original style and
709 // user_style (the user's override style). Copy only fields from the
710 // script's style that are deemed necessary.
713 render_priv->state.explicit = explicit;
715 render_priv->state.apply_font_scale =
716 !explicit || !(requested & ASS_OVERRIDE_BIT_SELECTIVE_FONT_SCALE);
718 // On positioned events, do not apply most overrides.
722 if (requested & ASS_OVERRIDE_BIT_STYLE)
723 requested |= ASS_OVERRIDE_BIT_FONT_NAME |
724 ASS_OVERRIDE_BIT_FONT_SIZE_FIELDS |
725 ASS_OVERRIDE_BIT_COLORS |
726 ASS_OVERRIDE_BIT_BORDER |
727 ASS_OVERRIDE_BIT_ATTRIBUTES;
729 // Copies fields even not covered by any of the other bits.
730 if (requested & ASS_OVERRIDE_FULL_STYLE)
733 // The user style is supposed to be independent of the script resolution.
734 // Treat the user style's values as if they were specified for a script with
735 // PlayResY=288, and rescale the values to the current script.
736 scale = render_priv->track->PlayResY / 288.0;
738 if (requested & ASS_OVERRIDE_BIT_FONT_SIZE_FIELDS) {
739 new->FontSize = user->FontSize * scale;
740 new->Spacing = user->Spacing * scale;
741 new->ScaleX = user->ScaleX;
742 new->ScaleY = user->ScaleY;
745 if (requested & ASS_OVERRIDE_BIT_FONT_NAME) {
746 new->FontName = user->FontName;
747 new->treat_fontname_as_pattern = user->treat_fontname_as_pattern;
750 if (requested & ASS_OVERRIDE_BIT_COLORS) {
751 new->PrimaryColour = user->PrimaryColour;
752 new->SecondaryColour = user->SecondaryColour;
753 new->OutlineColour = user->OutlineColour;
754 new->BackColour = user->BackColour;
757 if (requested & ASS_OVERRIDE_BIT_ATTRIBUTES) {
758 new->Bold = user->Bold;
759 new->Italic = user->Italic;
760 new->Underline = user->Underline;
761 new->StrikeOut = user->StrikeOut;
764 if (requested & ASS_OVERRIDE_BIT_BORDER) {
765 new->BorderStyle = user->BorderStyle;
766 new->Outline = user->Outline * scale;
767 new->Shadow = user->Shadow * scale;
770 if (requested & ASS_OVERRIDE_BIT_ALIGNMENT)
771 new->Alignment = user->Alignment;
773 if (requested & ASS_OVERRIDE_BIT_JUSTIFY)
774 new->Justify = user->Justify;
776 if (requested & ASS_OVERRIDE_BIT_MARGINS) {
777 new->MarginL = user->MarginL;
778 new->MarginR = user->MarginR;
779 new->MarginV = user->MarginV;
783 new->FontName = rstyle->FontName;
785 render_priv->state.style = new;
786 render_priv->state.overrides = requested;
791 static void init_font_scale(ASS_Renderer *render_priv)
793 ASS_Settings *settings_priv = &render_priv->settings;
795 render_priv->font_scale = ((double) render_priv->orig_height) /
796 render_priv->track->PlayResY;
797 if (settings_priv->storage_height)
798 render_priv->blur_scale = ((double) render_priv->orig_height) /
799 settings_priv->storage_height;
801 render_priv->blur_scale = 1.;
802 if (render_priv->track->ScaledBorderAndShadow)
803 render_priv->border_scale =
804 ((double) render_priv->orig_height) /
805 render_priv->track->PlayResY;
807 render_priv->border_scale = render_priv->blur_scale;
808 if (!settings_priv->storage_height)
809 render_priv->blur_scale = render_priv->border_scale;
811 if (render_priv->state.apply_font_scale) {
812 render_priv->font_scale *= settings_priv->font_size_coeff;
813 render_priv->border_scale *= settings_priv->font_size_coeff;
814 render_priv->blur_scale *= settings_priv->font_size_coeff;
819 * \brief partially reset render_context to style values
820 * Works like {\r}: resets some style overrides
822 void reset_render_context(ASS_Renderer *render_priv, ASS_Style *style)
824 style = handle_selective_style_overrides(render_priv, style);
826 init_font_scale(render_priv);
828 render_priv->state.c[0] = style->PrimaryColour;
829 render_priv->state.c[1] = style->SecondaryColour;
830 render_priv->state.c[2] = style->OutlineColour;
831 render_priv->state.c[3] = style->BackColour;
832 render_priv->state.flags =
833 (style->Underline ? DECO_UNDERLINE : 0) |
834 (style->StrikeOut ? DECO_STRIKETHROUGH : 0);
835 render_priv->state.font_size = style->FontSize;
837 free(render_priv->state.family);
838 render_priv->state.family = NULL;
839 render_priv->state.family = strdup(style->FontName);
840 render_priv->state.treat_family_as_pattern =
841 style->treat_fontname_as_pattern;
842 render_priv->state.bold = style->Bold;
843 render_priv->state.italic = style->Italic;
844 update_font(render_priv);
846 render_priv->state.border_style = style->BorderStyle;
847 render_priv->state.border_x = style->Outline;
848 render_priv->state.border_y = style->Outline;
849 render_priv->state.scale_x = style->ScaleX;
850 render_priv->state.scale_y = style->ScaleY;
851 render_priv->state.hspacing = style->Spacing;
852 render_priv->state.be = 0;
853 render_priv->state.blur = style->Blur;
854 render_priv->state.shadow_x = style->Shadow;
855 render_priv->state.shadow_y = style->Shadow;
856 render_priv->state.frx = render_priv->state.fry = 0.;
857 render_priv->state.frz = M_PI * style->Angle / 180.;
858 render_priv->state.fax = render_priv->state.fay = 0.;
859 render_priv->state.font_encoding = style->Encoding;
863 * \brief Start new event. Reset render_priv->state.
866 init_render_context(ASS_Renderer *render_priv, ASS_Event *event)
868 render_priv->state.event = event;
869 render_priv->state.parsed_tags = 0;
870 render_priv->state.evt_type = EVENT_NORMAL;
872 reset_render_context(render_priv, NULL);
873 render_priv->state.wrap_style = render_priv->track->WrapStyle;
875 render_priv->state.alignment = render_priv->state.style->Alignment;
876 render_priv->state.justify = render_priv->state.style->Justify;
877 render_priv->state.pos_x = 0;
878 render_priv->state.pos_y = 0;
879 render_priv->state.org_x = 0;
880 render_priv->state.org_y = 0;
881 render_priv->state.have_origin = 0;
882 render_priv->state.clip_x0 = 0;
883 render_priv->state.clip_y0 = 0;
884 render_priv->state.clip_x1 = render_priv->track->PlayResX;
885 render_priv->state.clip_y1 = render_priv->track->PlayResY;
886 render_priv->state.clip_mode = 0;
887 render_priv->state.detect_collisions = 1;
888 render_priv->state.fade = 0;
889 render_priv->state.drawing_scale = 0;
890 render_priv->state.pbo = 0;
891 render_priv->state.effect_type = EF_NONE;
892 render_priv->state.effect_timing = 0;
893 render_priv->state.effect_skip_timing = 0;
895 apply_transition_effects(render_priv, event);
898 static void free_render_context(ASS_Renderer *render_priv)
900 ass_cache_dec_ref(render_priv->state.font);
901 free(render_priv->state.family);
902 ass_drawing_free(render_priv->state.clip_drawing);
904 render_priv->state.font = NULL;
905 render_priv->state.family = NULL;
906 render_priv->state.clip_drawing = NULL;
908 TextInfo *text_info = &render_priv->text_info;
909 for (int n = 0; n < text_info->length; n++)
910 ass_drawing_free(text_info->glyphs[n].drawing);
911 text_info->length = 0;
915 * Replace the outline of a glyph by a contour which makes up a simple
918 static void draw_opaque_box(ASS_Renderer *render_priv, GlyphInfo *info,
919 int asc, int desc, ASS_Outline *ol,
920 FT_Vector advance, int sx, int sy)
923 double scale_y = info->orig_scale_y;
924 double scale_x = info->orig_scale_x;
930 // Emulate the WTFish behavior of VSFilter, i.e. double-scale
931 // the sizes of the opaque box.
932 adv += double_to_d6(info->hspacing * render_priv->font_scale * scale_x);
937 desc += asc * (scale_y - 1.0);
939 FT_Vector points[4] = {
940 { .x = -sx, .y = asc + sy },
941 { .x = adv + sx, .y = asc + sy },
942 { .x = adv + sx, .y = -desc - sy },
943 { .x = -sx, .y = -desc - sy },
946 ol->n_points = ol->n_contours = 0;
947 if (!outline_alloc(ol, 4, 1))
949 for (int i = 0; i < 4; ++i) {
950 ol->points[ol->n_points] = points[i];
951 ol->tags[ol->n_points++] = 1;
953 ol->contours[ol->n_contours++] = ol->n_points - 1;
957 * \brief Prepare glyph hash
960 fill_glyph_hash(ASS_Renderer *priv, OutlineHashKey *outline_key,
964 DrawingHashKey *key = &outline_key->u.drawing;
965 outline_key->type = OUTLINE_DRAWING;
966 key->scale_x = double_to_d16(info->scale_x);
967 key->scale_y = double_to_d16(info->scale_y);
968 key->outline.x = double_to_d6(info->border_x * priv->border_scale);
969 key->outline.y = double_to_d6(info->border_y * priv->border_scale);
970 key->border_style = info->border_style;
971 // hpacing only matters for opaque box borders (see draw_opaque_box),
972 // so for normal borders, maximize cache utility by ignoring it
974 info->border_style == 3 ? double_to_d16(info->hspacing) : 0;
975 key->hash = info->drawing->hash;
976 key->text = info->drawing->text;
977 key->pbo = info->drawing->pbo;
978 key->scale = info->drawing->scale;
980 GlyphHashKey *key = &outline_key->u.glyph;
981 outline_key->type = OUTLINE_GLYPH;
982 key->font = info->font;
983 key->size = info->font_size;
984 key->face_index = info->face_index;
985 key->glyph_index = info->glyph_index;
986 key->bold = info->bold;
987 key->italic = info->italic;
988 key->scale_x = double_to_d16(info->scale_x);
989 key->scale_y = double_to_d16(info->scale_y);
990 key->outline.x = double_to_d6(info->border_x * priv->border_scale);
991 key->outline.y = double_to_d6(info->border_y * priv->border_scale);
992 key->flags = info->flags;
993 key->border_style = info->border_style;
995 info->border_style == 3 ? double_to_d16(info->hspacing) : 0;
1000 * \brief Prepare combined-bitmap hash
1002 static void fill_composite_hash(CompositeHashKey *hk, CombinedBitmapInfo *info)
1004 hk->filter = info->filter;
1005 hk->bitmap_count = info->bitmap_count;
1006 hk->bitmaps = info->bitmaps;
1010 * \brief Get normal and outline (border) glyphs
1011 * \param info out: struct filled with extracted data
1012 * Tries to get both glyphs from cache.
1013 * If they can't be found, gets a glyph from font face, generates outline,
1014 * and add them to cache.
1015 * The glyphs are returned in info->glyph and info->outline_glyph
1018 get_outline_glyph(ASS_Renderer *priv, GlyphInfo *info)
1020 memset(&info->hash_key, 0, sizeof(info->hash_key));
1023 OutlineHashValue *val;
1024 fill_glyph_hash(priv, &key, info);
1025 if (!ass_cache_get(priv->cache.outline_cache, &key, &val)) {
1028 memset(val, 0, sizeof(*val));
1030 if (info->drawing) {
1031 ASS_Drawing *drawing = info->drawing;
1032 ass_drawing_hash(drawing);
1033 if(!ass_drawing_parse(drawing, 0) ||
1034 !outline_copy(&val->outline, &drawing->outline)) {
1035 ass_cache_commit(val, 1);
1036 ass_cache_dec_ref(val);
1039 val->advance.x = drawing->advance.x;
1040 val->advance.y = drawing->advance.y;
1041 val->asc = drawing->asc;
1042 val->desc = drawing->desc;
1044 ass_face_set_size(info->font->faces[info->face_index],
1046 ass_font_set_transform(info->font, info->scale_x,
1047 info->scale_y, NULL);
1049 ass_font_get_glyph(info->font,
1050 info->symbol, info->face_index, info->glyph_index,
1051 priv->settings.hinting, info->flags);
1052 if (glyph != NULL) {
1053 FT_Outline *src = &((FT_OutlineGlyph) glyph)->outline;
1054 if (!outline_convert(&val->outline, src)) {
1055 ass_cache_commit(val, 1);
1056 ass_cache_dec_ref(val);
1059 if (priv->settings.shaper == ASS_SHAPING_SIMPLE) {
1060 val->advance.x = d16_to_d6(glyph->advance.x);
1061 val->advance.y = d16_to_d6(glyph->advance.y);
1063 FT_Done_Glyph(glyph);
1064 ass_font_get_asc_desc(info->font, info->symbol,
1065 &val->asc, &val->desc);
1066 val->asc *= info->scale_y;
1067 val->desc *= info->scale_y;
1072 outline_get_cbox(&val->outline, &val->bbox_scaled);
1074 if (info->border_style == 3) {
1076 if (priv->settings.shaper == ASS_SHAPING_SIMPLE || info->drawing)
1077 advance = val->advance;
1079 advance = info->advance;
1081 draw_opaque_box(priv, info, val->asc, val->desc, &val->border[0], advance,
1082 double_to_d6(info->border_x * priv->border_scale),
1083 double_to_d6(info->border_y * priv->border_scale));
1085 } else if (val->outline.n_points && (info->border_x > 0 || info->border_y > 0)
1086 && double_to_d6(info->scale_x) && double_to_d6(info->scale_y)) {
1088 int xbord = double_to_d6(info->border_x * priv->border_scale);
1089 int ybord = double_to_d6(info->border_y * priv->border_scale);
1090 if(xbord >= eps || ybord >= eps) {
1091 outline_alloc(&val->border[0], 2 * val->outline.n_points, val->outline.n_contours);
1092 outline_alloc(&val->border[1], 2 * val->outline.n_points, val->outline.n_contours);
1093 if (!val->border[0].max_points || !val->border[1].max_points ||
1094 !outline_stroke(&val->border[0], &val->border[1],
1095 &val->outline, xbord, ybord, eps)) {
1096 ass_msg(priv->library, MSGL_WARN, "Cannot stoke outline");
1097 outline_free(&val->border[0]);
1098 outline_free(&val->border[1]);
1103 ass_cache_commit(val, 1);
1104 } else if (!val->valid) {
1105 ass_cache_dec_ref(val);
1109 info->hash_key.u.outline.outline = val;
1110 info->outline = &val->outline;
1111 info->border[0] = &val->border[0];
1112 info->border[1] = &val->border[1];
1113 info->bbox = val->bbox_scaled;
1114 if (info->drawing || priv->settings.shaper == ASS_SHAPING_SIMPLE) {
1115 info->cluster_advance.x = info->advance.x = val->advance.x;
1116 info->cluster_advance.y = info->advance.y = val->advance.y;
1118 info->asc = val->asc;
1119 info->desc = val->desc;
1123 * \brief Apply transformation to outline points of a glyph
1124 * Applies rotations given by frx, fry and frz and projects the points back
1125 * onto the screen plane.
1128 transform_3d_points(FT_Vector shift, ASS_Outline *outline, double frx, double fry,
1129 double frz, double fax, double fay, double scale,
1132 double sx = sin(frx);
1133 double sy = sin(fry);
1134 double sz = sin(frz);
1135 double cx = cos(frx);
1136 double cy = cos(fry);
1137 double cz = cos(frz);
1138 FT_Vector *p = outline->points;
1139 double x, y, z, xx, yy, zz;
1142 dist = 20000 * scale;
1143 for (size_t i = 0; i < outline->n_points; ++i) {
1144 x = (double) p[i].x + shift.x + (fax * (yshift - p[i].y));
1145 y = (double) p[i].y + shift.y + (-fay * p[i].x);
1148 xx = x * cz + y * sz;
1149 yy = -(x * sz - y * cz);
1153 y = yy * cx + zz * sx;
1154 z = yy * sx - zz * cx;
1156 xx = x * cy + z * sy;
1158 zz = x * sy - z * cy;
1160 zz = FFMAX(zz, 1000 - dist);
1162 x = (xx * dist) / (zz + dist);
1163 y = (yy * dist) / (zz + dist);
1164 p[i].x = x - shift.x + 0.5;
1165 p[i].y = y - shift.y + 0.5;
1170 * \brief Apply 3d transformation to several objects
1171 * \param shift FreeType vector
1172 * \param glyph FreeType glyph
1173 * \param glyph2 FreeType glyph
1174 * \param frx x-axis rotation angle
1175 * \param fry y-axis rotation angle
1176 * \param frz z-axis rotation angle
1177 * Rotates both glyphs by frx, fry and frz. Shift vector is added before rotation and subtracted after it.
1180 transform_3d(FT_Vector shift, ASS_Outline *outline, int n_outlines,
1181 double frx, double fry, double frz, double fax, double fay,
1182 double scale, int yshift)
1186 if (frx != 0. || fry != 0. || frz != 0. || fax != 0. || fay != 0.)
1187 for (int i = 0; i < n_outlines; i++)
1188 transform_3d_points(shift, &outline[i], frx, fry, frz,
1189 fax, fay, scale, yshift);
1193 * \brief Get bitmaps for a glyph
1194 * \param info glyph info
1195 * Tries to get glyph bitmaps from bitmap cache.
1196 * If they can't be found, they are generated by rotating and rendering the glyph.
1197 * After that, bitmaps are added to the cache.
1198 * They are returned in info->bm (glyph), info->bm_o (outline) and info->bm_s (shadow).
1201 get_bitmap_glyph(ASS_Renderer *render_priv, GlyphInfo *info)
1203 if (!info->outline || info->symbol == '\n' || info->symbol == 0 || info->skip)
1206 BitmapHashValue *val;
1207 OutlineBitmapHashKey *key = &info->hash_key.u.outline;
1208 if (ass_cache_get(render_priv->cache.bitmap_cache, &info->hash_key, &val)) {
1218 if (!info->outline) {
1219 memset(val, 0, sizeof(*val));
1220 ass_cache_commit(val, sizeof(BitmapHashKey) + sizeof(BitmapHashValue));
1226 const int n_outlines = 3;
1227 ASS_Outline outline[n_outlines];
1228 outline_copy(&outline[0], info->outline);
1229 outline_copy(&outline[1], info->border[0]);
1230 outline_copy(&outline[2], info->border[1]);
1232 // calculating rotation shift vector (from rotation origin to the glyph basepoint)
1233 FT_Vector shift = { key->shift_x, key->shift_y };
1234 double scale_x = render_priv->font_scale_x;
1235 double fax_scaled = info->fax / info->scale_y * info->scale_x;
1236 double fay_scaled = info->fay / info->scale_x * info->scale_y;
1239 // use blur_scale because, like blurs, VSFilter forgets to scale this
1240 transform_3d(shift, outline, n_outlines,
1241 info->frx, info->fry, info->frz, fax_scaled,
1242 fay_scaled, render_priv->blur_scale, info->asc);
1244 // PAR correction scaling
1245 FT_Matrix m = { double_to_d16(scale_x), 0,
1246 0, double_to_d16(1.0) };
1250 for (int i = 0; i < n_outlines; i++)
1251 outline_transform(&outline[i], &m);
1252 for (int i = 0; i < n_outlines; i++)
1253 outline_translate(&outline[i], key->advance.x, -key->advance.y);
1256 val->valid = outline_to_bitmap2(render_priv,
1257 &outline[0], &outline[1], &outline[2],
1258 &val->bm, &val->bm_o);
1262 ass_cache_commit(val, bitmap_size(val->bm) + bitmap_size(val->bm_o) +
1263 sizeof(BitmapHashKey) + sizeof(BitmapHashValue));
1266 for (int i = 0; i < n_outlines; i++)
1267 outline_free(&outline[i]);
1271 * This function goes through text_info and calculates text parameters.
1272 * The following text_info fields are filled:
1278 static void measure_text(ASS_Renderer *render_priv)
1280 TextInfo *text_info = &render_priv->text_info;
1282 double max_asc = 0., max_desc = 0.;
1283 GlyphInfo *last = NULL;
1286 text_info->height = 0.;
1287 for (i = 0; i < text_info->length + 1; ++i) {
1288 if ((i == text_info->length) || text_info->glyphs[i].linebreak) {
1289 if (empty_line && cur_line > 0 && last) {
1290 max_asc = d6_to_double(last->asc) / 2.0;
1291 max_desc = d6_to_double(last->desc) / 2.0;
1293 text_info->lines[cur_line].asc = max_asc;
1294 text_info->lines[cur_line].desc = max_desc;
1295 text_info->height += max_asc + max_desc;
1297 max_asc = max_desc = 0.;
1300 if (i < text_info->length) {
1301 GlyphInfo *cur = text_info->glyphs + i;
1302 if (d6_to_double(cur->asc) > max_asc)
1303 max_asc = d6_to_double(cur->asc);
1304 if (d6_to_double(cur->desc) > max_desc)
1305 max_desc = d6_to_double(cur->desc);
1306 if (cur->symbol != '\n' && cur->symbol != 0) {
1312 text_info->height +=
1313 (text_info->n_lines -
1314 1) * render_priv->settings.line_spacing;
1318 * Mark extra whitespace for later removal.
1320 #define IS_WHITESPACE(x) ((x->symbol == ' ' || x->symbol == '\n') \
1322 static void trim_whitespace(ASS_Renderer *render_priv)
1326 TextInfo *ti = &render_priv->text_info;
1328 // Mark trailing spaces
1330 cur = ti->glyphs + i;
1331 while (i && IS_WHITESPACE(cur)) {
1333 cur = ti->glyphs + --i;
1336 // Mark leading whitespace
1339 while (i < ti->length && IS_WHITESPACE(cur)) {
1341 cur = ti->glyphs + ++i;
1344 // Mark all extraneous whitespace inbetween
1345 for (i = 0; i < ti->length; ++i) {
1346 cur = ti->glyphs + i;
1347 if (cur->linebreak) {
1348 // Mark whitespace before
1350 cur = ti->glyphs + j;
1351 while (j && IS_WHITESPACE(cur)) {
1353 cur = ti->glyphs + --j;
1355 // A break itself can contain a whitespace, too
1356 cur = ti->glyphs + i;
1357 if (cur->symbol == ' ' || cur->symbol == '\n') {
1359 // Mark whitespace after
1361 cur = ti->glyphs + j;
1362 while (j < ti->length && IS_WHITESPACE(cur)) {
1364 cur = ti->glyphs + ++j;
1371 #undef IS_WHITESPACE
1374 * \brief rearrange text between lines
1375 * \param max_text_width maximal text line width in pixels
1376 * The algo is similar to the one in libvo/sub.c:
1377 * 1. Place text, wrapping it when current line is full
1378 * 2. Try moving words from the end of a line to the beginning of the next one while it reduces
1379 * the difference in lengths between this two lines.
1380 * The result may not be optimal, but usually is good enough.
1382 * FIXME: implement style 0 and 3 correctly
1385 wrap_lines_smart(ASS_Renderer *render_priv, double max_text_width)
1388 GlyphInfo *cur, *s1, *e1, *s2, *s3;
1395 TextInfo *text_info = &render_priv->text_info;
1398 text_info->n_lines = 1;
1400 s1 = text_info->glyphs; // current line start
1401 for (i = 0; i < text_info->length; ++i) {
1403 double s_offset, len;
1404 cur = text_info->glyphs + i;
1405 s_offset = d6_to_double(s1->bbox.xMin + s1->pos.x);
1406 len = d6_to_double(cur->bbox.xMax + cur->pos.x) - s_offset;
1408 if (cur->symbol == '\n') {
1411 ass_msg(render_priv->library, MSGL_DBG2,
1412 "forced line break at %d", break_at);
1413 } else if (cur->symbol == ' ') {
1415 } else if (len >= max_text_width
1416 && (render_priv->state.wrap_style != 2)) {
1418 break_at = last_space;
1420 ass_msg(render_priv->library, MSGL_DBG2, "line break at %d",
1424 if (break_at != -1) {
1425 // need to use one more line
1426 // marking break_at+1 as start of a new line
1427 int lead = break_at + 1; // the first symbol of the new line
1428 if (text_info->n_lines >= text_info->max_lines) {
1429 // Raise maximum number of lines
1430 text_info->max_lines *= 2;
1431 text_info->lines = realloc(text_info->lines,
1433 text_info->max_lines);
1435 if (lead < text_info->length) {
1436 text_info->glyphs[lead].linebreak = break_type;
1438 s1 = text_info->glyphs + lead;
1439 text_info->n_lines++;
1443 #define DIFF(x,y) (((x) < (y)) ? (y - x) : (x - y))
1445 while (!exit && render_priv->state.wrap_style != 1) {
1447 s3 = text_info->glyphs;
1449 for (i = 0; i <= text_info->length; ++i) {
1450 cur = text_info->glyphs + i;
1451 if ((i == text_info->length) || cur->linebreak) {
1455 if (s1 && (s2->linebreak == 1)) { // have at least 2 lines, and linebreak is 'soft'
1456 double l1, l2, l1_new, l2_new;
1461 } while ((w > s1) && (w->symbol == ' '));
1462 while ((w > s1) && (w->symbol != ' ')) {
1466 while ((e1 > s1) && (e1->symbol == ' ')) {
1469 if (w->symbol == ' ')
1472 l1 = d6_to_double(((s2 - 1)->bbox.xMax + (s2 - 1)->pos.x) -
1473 (s1->bbox.xMin + s1->pos.x));
1474 l2 = d6_to_double(((s3 - 1)->bbox.xMax + (s3 - 1)->pos.x) -
1475 (s2->bbox.xMin + s2->pos.x));
1476 l1_new = d6_to_double(
1477 (e1->bbox.xMax + e1->pos.x) -
1478 (s1->bbox.xMin + s1->pos.x));
1479 l2_new = d6_to_double(
1480 ((s3 - 1)->bbox.xMax + (s3 - 1)->pos.x) -
1481 (w->bbox.xMin + w->pos.x));
1483 if (DIFF(l1_new, l2_new) < DIFF(l1, l2)) {
1484 if (w->linebreak || w == text_info->glyphs)
1485 text_info->n_lines--;
1486 if (w != text_info->glyphs)
1493 if (i == text_info->length)
1498 assert(text_info->n_lines >= 1);
1501 measure_text(render_priv);
1502 trim_whitespace(render_priv);
1507 cur = text_info->glyphs + i;
1508 while (i < text_info->length && cur->skip)
1509 cur = text_info->glyphs + ++i;
1510 pen_shift_x = d6_to_double(-cur->pos.x);
1513 for (i = 0; i < text_info->length; ++i) {
1514 cur = text_info->glyphs + i;
1515 if (cur->linebreak) {
1516 while (i < text_info->length && cur->skip && cur->symbol != '\n')
1517 cur = text_info->glyphs + ++i;
1519 text_info->lines[cur_line - 1].desc +
1520 text_info->lines[cur_line].asc;
1521 text_info->lines[cur_line - 1].len = i -
1522 text_info->lines[cur_line - 1].offset;
1523 text_info->lines[cur_line].offset = i;
1525 pen_shift_x = d6_to_double(-cur->pos.x);
1526 pen_shift_y += height + render_priv->settings.line_spacing;
1528 cur->pos.x += double_to_d6(pen_shift_x);
1529 cur->pos.y += double_to_d6(pen_shift_y);
1531 text_info->lines[cur_line - 1].len =
1532 text_info->length - text_info->lines[cur_line - 1].offset;
1536 for (i = 0; i < text_info->n_lines; i++) {
1537 printf("line %d offset %d length %d\n", i, text_info->lines[i].offset,
1538 text_info->lines[i].len);
1544 * \brief Calculate base point for positioning and rotation
1545 * \param bbox text bbox
1546 * \param alignment alignment
1547 * \param bx, by out: base point coordinates
1549 static void get_base_point(DBBox *bbox, int alignment, double *bx, double *by)
1551 const int halign = alignment & 3;
1552 const int valign = alignment & 12;
1559 *bx = (bbox->xMax + bbox->xMin) / 2.0;
1571 *by = (bbox->yMax + bbox->yMin) / 2.0;
1580 * Prepare bitmap hash key of a glyph
1583 fill_bitmap_hash(ASS_Renderer *priv, GlyphInfo *info,
1584 OutlineBitmapHashKey *hash_key)
1586 hash_key->frx = rot_key(info->frx);
1587 hash_key->fry = rot_key(info->fry);
1588 hash_key->frz = rot_key(info->frz);
1589 hash_key->fax = double_to_d16(info->fax);
1590 hash_key->fay = double_to_d16(info->fay);
1594 * \brief Adjust the glyph's font size and scale factors to ensure smooth
1595 * scaling and handle pathological font sizes. The main problem here is
1596 * freetype's grid fitting, which destroys animations by font size, or will
1597 * result in incorrect final text size if font sizes are very small and
1598 * scale factors very large. See Google Code issue #46.
1599 * \param priv guess what
1600 * \param glyph the glyph to be modified
1603 fix_glyph_scaling(ASS_Renderer *priv, GlyphInfo *glyph)
1606 if (priv->settings.hinting == ASS_HINTING_NONE) {
1607 // arbitrary, not too small to prevent grid fitting rounding effects
1608 // XXX: this is a rather crude hack
1611 // If hinting is enabled, we want to pass the real font size
1612 // to freetype. Normalize scale_y to 1.0.
1613 ft_size = glyph->scale_y * glyph->font_size;
1615 glyph->scale_x = glyph->scale_x * glyph->font_size / ft_size;
1616 glyph->scale_y = glyph->scale_y * glyph->font_size / ft_size;
1617 glyph->font_size = ft_size;
1621 * \brief Checks whether a glyph should start a new bitmap run
1622 * \param info Pointer to new GlyphInfo to check
1623 * \param current_info Pointer to CombinedBitmapInfo for current run (may be NULL)
1624 * \return 1 if a new run should be started
1626 static int is_new_bm_run(GlyphInfo *info, GlyphInfo *last)
1628 // FIXME: Don't break on glyph substitutions
1629 return !last || info->effect || info->drawing || last->drawing ||
1630 strcmp(last->font->desc.family, info->font->desc.family) ||
1631 last->font->desc.vertical != info->font->desc.vertical ||
1632 last->face_index != info->face_index ||
1633 last->font_size != info->font_size ||
1634 last->c[0] != info->c[0] ||
1635 last->c[1] != info->c[1] ||
1636 last->c[2] != info->c[2] ||
1637 last->c[3] != info->c[3] ||
1638 last->be != info->be ||
1639 last->blur != info->blur ||
1640 last->shadow_x != info->shadow_x ||
1641 last->shadow_y != info->shadow_y ||
1642 last->frx != info->frx ||
1643 last->fry != info->fry ||
1644 last->frz != info->frz ||
1645 last->fax != info->fax ||
1646 last->fay != info->fay ||
1647 last->scale_x != info->scale_x ||
1648 last->scale_y != info->scale_y ||
1649 last->border_style != info->border_style ||
1650 last->border_x != info->border_x ||
1651 last->border_y != info->border_y ||
1652 last->hspacing != info->hspacing ||
1653 last->italic != info->italic ||
1654 last->bold != info->bold ||
1655 last->flags != info->flags;
1658 static void make_shadow_bitmap(CombinedBitmapInfo *info, ASS_Renderer *render_priv)
1660 if (!(info->filter.flags & FILTER_NONZERO_SHADOW)) {
1661 if (info->bm && info->bm_o && !(info->filter.flags & FILTER_BORDER_STYLE_3)) {
1662 fix_outline(info->bm, info->bm_o);
1663 } else if (info->bm_o && !(info->filter.flags & FILTER_NONZERO_BORDER)) {
1664 ass_free_bitmap(info->bm_o);
1670 // Create shadow and fix outline as needed
1671 if (info->bm && info->bm_o && !(info->filter.flags & FILTER_BORDER_STYLE_3)) {
1672 info->bm_s = copy_bitmap(render_priv->engine, info->bm_o);
1673 fix_outline(info->bm, info->bm_o);
1674 } else if (info->bm_o && (info->filter.flags & FILTER_NONZERO_BORDER)) {
1675 info->bm_s = copy_bitmap(render_priv->engine, info->bm_o);
1676 } else if (info->bm_o) {
1677 info->bm_s = info->bm_o;
1679 } else if (info->bm)
1680 info->bm_s = copy_bitmap(render_priv->engine, info->bm);
1685 // Works right even for negative offsets
1686 // '>>' rounds toward negative infinity, '&' returns correct remainder
1687 info->bm_s->left += info->filter.shadow.x >> 6;
1688 info->bm_s->top += info->filter.shadow.y >> 6;
1689 shift_bitmap(info->bm_s, info->filter.shadow.x & SUBPIXEL_MASK, info->filter.shadow.y & SUBPIXEL_MASK);
1692 // Parse event text.
1693 // Fill render_priv->text_info.
1694 static int parse_events(ASS_Renderer *render_priv, ASS_Event *event)
1696 TextInfo *text_info = &render_priv->text_info;
1697 ASS_Drawing *drawing = NULL;
1706 // get next char, executing style override
1707 // this affects render_context
1710 if ((*p == '{') && (q = strchr(p, '}'))) {
1712 p = parse_tag(render_priv, p, q, 1.);
1715 } else if (render_priv->state.drawing_scale) {
1719 while ((*q != '{') && (*q != 0))
1722 drawing = ass_drawing_new(render_priv->library,
1723 render_priv->ftlibrary);
1727 ass_drawing_set_text(drawing, p, q - p);
1728 code = 0xfffc; // object replacement character
1732 code = get_next_char(render_priv, &p);
1740 // face could have been changed in get_next_char
1741 if (!render_priv->state.font) {
1742 free_render_context(render_priv);
1743 ass_drawing_free(drawing);
1747 if (text_info->length >= text_info->max_glyphs) {
1748 // Raise maximum number of glyphs
1749 text_info->max_glyphs *= 2;
1751 realloc(text_info->glyphs,
1752 sizeof(GlyphInfo) * text_info->max_glyphs);
1755 GlyphInfo *info = &text_info->glyphs[text_info->length];
1757 // Clear current GlyphInfo
1758 memset(info, 0, sizeof(GlyphInfo));
1761 if (drawing && drawing->text) {
1762 drawing->scale_x = render_priv->state.scale_x *
1763 render_priv->font_scale;
1764 drawing->scale_y = render_priv->state.scale_y *
1765 render_priv->font_scale;
1766 drawing->scale = render_priv->state.drawing_scale;
1767 drawing->pbo = render_priv->state.pbo;
1768 info->drawing = drawing;
1772 // Fill glyph information
1773 info->symbol = code;
1774 info->font = render_priv->state.font;
1776 ass_cache_inc_ref(info->font);
1777 for (i = 0; i < 4; ++i) {
1778 uint32_t clr = render_priv->state.c[i];
1779 // VSFilter compatibility: apply fade only when it's positive
1780 if (render_priv->state.fade > 0)
1782 mult_alpha(_a(clr), render_priv->state.fade), 1.);
1786 info->effect_type = render_priv->state.effect_type;
1787 info->effect_timing = render_priv->state.effect_timing;
1788 info->effect_skip_timing = render_priv->state.effect_skip_timing;
1790 render_priv->state.font_size * render_priv->font_scale;
1791 info->be = render_priv->state.be;
1792 info->blur = render_priv->state.blur;
1793 info->shadow_x = render_priv->state.shadow_x;
1794 info->shadow_y = render_priv->state.shadow_y;
1795 info->scale_x = info->orig_scale_x = render_priv->state.scale_x;
1796 info->scale_y = info->orig_scale_y = render_priv->state.scale_y;
1797 info->border_style = render_priv->state.border_style;
1798 info->border_x= render_priv->state.border_x;
1799 info->border_y = render_priv->state.border_y;
1800 info->hspacing = render_priv->state.hspacing;
1801 info->bold = render_priv->state.bold;
1802 info->italic = render_priv->state.italic;
1803 info->flags = render_priv->state.flags;
1804 info->frx = render_priv->state.frx;
1805 info->fry = render_priv->state.fry;
1806 info->frz = render_priv->state.frz;
1807 info->fax = render_priv->state.fax;
1808 info->fay = render_priv->state.fay;
1811 fix_glyph_scaling(render_priv, info);
1813 text_info->length++;
1815 render_priv->state.effect_type = EF_NONE;
1816 render_priv->state.effect_timing = 0;
1817 render_priv->state.effect_skip_timing = 0;
1820 ass_drawing_free(drawing);
1825 // Process render_priv->text_info and load glyph outlines.
1826 static void retrieve_glyphs(ASS_Renderer *render_priv)
1828 GlyphInfo *glyphs = render_priv->text_info.glyphs;
1831 for (i = 0; i < render_priv->text_info.length; i++) {
1832 GlyphInfo *info = glyphs + i;
1834 get_outline_glyph(render_priv, info);
1839 // Add additional space after italic to non-italic style changes
1840 if (i && glyphs[i - 1].italic && !info->italic) {
1842 GlyphInfo *og = &glyphs[back];
1843 while (back && og->bbox.xMax - og->bbox.xMin == 0
1845 og = &glyphs[--back];
1846 if (og->bbox.xMax > og->cluster_advance.x)
1847 og->cluster_advance.x = og->bbox.xMax;
1850 // add horizontal letter spacing
1851 info->cluster_advance.x += double_to_d6(info->hspacing *
1852 render_priv->font_scale * info->orig_scale_x);
1854 // add displacement for vertical shearing
1855 info->cluster_advance.y += (info->fay / info->scale_x * info->scale_y) * info->cluster_advance.x;
1859 // Preliminary layout (for line wrapping)
1860 static void preliminary_layout(ASS_Renderer *render_priv)
1867 for (i = 0; i < render_priv->text_info.length; i++) {
1868 GlyphInfo *info = render_priv->text_info.glyphs + i;
1869 FT_Vector cluster_pen = pen;
1871 info->pos.x = cluster_pen.x;
1872 info->pos.y = cluster_pen.y;
1874 cluster_pen.x += info->advance.x;
1875 cluster_pen.y += info->advance.y;
1878 info->hash_key.type = BITMAP_OUTLINE;
1879 fill_bitmap_hash(render_priv, info, &info->hash_key.u.outline);
1883 info = render_priv->text_info.glyphs + i;
1884 pen.x += info->cluster_advance.x;
1885 pen.y += info->cluster_advance.y;
1889 // Reorder text into visual order
1890 static void reorder_text(ASS_Renderer *render_priv)
1892 TextInfo *text_info = &render_priv->text_info;
1896 FriBidiStrIndex *cmap = ass_shaper_reorder(render_priv->shaper, text_info);
1898 ass_msg(render_priv->library, MSGL_ERR, "Failed to reorder text");
1899 ass_shaper_cleanup(render_priv->shaper, text_info);
1900 free_render_context(render_priv);
1904 // Reposition according to the map
1908 double last_pen_x = 0;
1909 double last_fay = 0;
1910 for (i = 0; i < text_info->length; i++) {
1911 GlyphInfo *info = text_info->glyphs + cmap[i];
1912 if (text_info->glyphs[i].linebreak) {
1913 pen.y -= (last_fay / info->scale_x * info->scale_y) * (pen.x - last_pen_x);
1914 last_pen_x = pen.x = 0;
1915 pen.y += double_to_d6(text_info->lines[lineno-1].desc);
1916 pen.y += double_to_d6(text_info->lines[lineno].asc);
1917 pen.y += double_to_d6(render_priv->settings.line_spacing);
1920 else if (last_fay != info->fay) {
1921 pen.y -= (last_fay / info->scale_x * info->scale_y) * (pen.x - last_pen_x);
1924 last_fay = info->fay;
1925 if (info->skip) continue;
1926 FT_Vector cluster_pen = pen;
1928 info->pos.x = info->offset.x + cluster_pen.x;
1929 info->pos.y = info->offset.y + cluster_pen.y;
1930 cluster_pen.x += info->advance.x;
1931 cluster_pen.y += info->advance.y;
1934 info = text_info->glyphs + cmap[i];
1935 pen.x += info->cluster_advance.x;
1936 pen.y += info->cluster_advance.y;
1940 static void align_lines(ASS_Renderer *render_priv, double max_text_width)
1942 TextInfo *text_info = &render_priv->text_info;
1943 GlyphInfo *glyphs = text_info->glyphs;
1946 int last_break = -1;
1947 int halign = render_priv->state.alignment & 3;
1948 int justify = render_priv->state.justify;
1949 double max_width = 0;
1951 if (render_priv->state.evt_type == EVENT_HSCROLL)
1954 for (i = 0; i <= text_info->length; ++i) { // (text_info->length + 1) is the end of the last line
1955 if ((i == text_info->length) || glyphs[i].linebreak) {
1956 max_width = FFMAX(max_width,width);
1959 if (i < text_info->length && !glyphs[i].skip &&
1960 glyphs[i].symbol != '\n' && glyphs[i].symbol != 0) {
1961 width += d6_to_double(glyphs[i].cluster_advance.x);
1964 for (i = 0; i <= text_info->length; ++i) { // (text_info->length + 1) is the end of the last line
1965 if ((i == text_info->length) || glyphs[i].linebreak) {
1967 if (halign == HALIGN_LEFT) { // left aligned, no action
1968 if (justify == ASS_JUSTIFY_RIGHT) {
1969 shift = max_width - width;
1970 } else if (justify == ASS_JUSTIFY_CENTER) {
1971 shift = (max_width - width) / 2.0;
1975 } else if (halign == HALIGN_RIGHT) { // right aligned
1976 if (justify == ASS_JUSTIFY_LEFT) {
1977 shift = max_text_width - max_width;
1978 } else if (justify == ASS_JUSTIFY_CENTER) {
1979 shift = max_text_width - max_width + (max_width - width) / 2.0;
1981 shift = max_text_width - width;
1983 } else if (halign == HALIGN_CENTER) { // centered
1984 if (justify == ASS_JUSTIFY_LEFT) {
1985 shift = (max_text_width - max_width) / 2.0;
1986 } else if (justify == ASS_JUSTIFY_RIGHT) {
1987 shift = (max_text_width - max_width) / 2.0 + max_width - width;
1989 shift = (max_text_width - width) / 2.0;
1992 for (j = last_break + 1; j < i; ++j) {
1993 GlyphInfo *info = glyphs + j;
1995 info->pos.x += double_to_d6(shift);
2002 if (i < text_info->length && !glyphs[i].skip &&
2003 glyphs[i].symbol != '\n' && glyphs[i].symbol != 0) {
2004 width += d6_to_double(glyphs[i].cluster_advance.x);
2009 static void calculate_rotation_params(ASS_Renderer *render_priv, DBBox *bbox,
2010 double device_x, double device_y)
2012 TextInfo *text_info = &render_priv->text_info;
2016 if (render_priv->state.have_origin) {
2017 center.x = x2scr(render_priv, render_priv->state.org_x);
2018 center.y = y2scr(render_priv, render_priv->state.org_y);
2020 double bx = 0., by = 0.;
2021 get_base_point(bbox, render_priv->state.alignment, &bx, &by);
2022 center.x = device_x + bx;
2023 center.y = device_y + by;
2026 for (i = 0; i < text_info->length; ++i) {
2027 GlyphInfo *info = text_info->glyphs + i;
2029 OutlineBitmapHashKey *key = &info->hash_key.u.outline;
2031 if (key->frx || key->fry || key->frz || key->fax || key->fay) {
2032 key->shift_x = info->pos.x + double_to_d6(device_x - center.x);
2033 key->shift_y = -(info->pos.y + double_to_d6(device_y - center.y));
2044 static inline void rectangle_reset(Rectangle *rect)
2046 rect->x_min = rect->y_min = INT_MAX;
2047 rect->x_max = rect->y_max = INT_MIN;
2050 static inline void rectangle_combine(Rectangle *rect, const Bitmap *bm, int x, int y)
2052 rect->x_min = FFMIN(rect->x_min, x + bm->left);
2053 rect->y_min = FFMIN(rect->y_min, y + bm->top);
2054 rect->x_max = FFMAX(rect->x_max, x + bm->left + bm->w);
2055 rect->y_max = FFMAX(rect->y_max, y + bm->top + bm->h);
2058 // Convert glyphs to bitmaps, combine them, apply blur, generate shadows.
2059 static void render_and_combine_glyphs(ASS_Renderer *render_priv,
2060 double device_x, double device_y)
2062 TextInfo *text_info = &render_priv->text_info;
2063 int left = render_priv->settings.left_margin;
2064 device_x = (device_x - left) * render_priv->font_scale_x + left;
2065 unsigned nb_bitmaps = 0;
2067 CombinedBitmapInfo *combined_info = text_info->combined_bitmaps;
2068 CombinedBitmapInfo *current_info = NULL;
2069 GlyphInfo *last_info = NULL;
2070 for (int i = 0; i < text_info->length; i++) {
2071 GlyphInfo *info = text_info->glyphs + i;
2072 if (info->linebreak) linebreak = 1;
2074 for (; info; info = info->next)
2075 ass_cache_dec_ref(info->hash_key.u.outline.outline);
2078 for (; info; info = info->next) {
2079 OutlineBitmapHashKey *key = &info->hash_key.u.outline;
2081 info->pos.x = double_to_d6(device_x + d6_to_double(info->pos.x) * render_priv->font_scale_x);
2082 info->pos.y = double_to_d6(device_y) + info->pos.y;
2083 key->advance.x = info->pos.x & (SUBPIXEL_MASK & ~SUBPIXEL_ACCURACY);
2084 key->advance.y = info->pos.y & (SUBPIXEL_MASK & ~SUBPIXEL_ACCURACY);
2085 int x = info->pos.x >> 6, y = info->pos.y >> 6;
2086 get_bitmap_glyph(render_priv, info);
2088 if(linebreak || is_new_bm_run(info, last_info)) {
2091 if (nb_bitmaps >= text_info->max_bitmaps) {
2092 size_t new_size = 2 * text_info->max_bitmaps;
2093 if (!ASS_REALLOC_ARRAY(text_info->combined_bitmaps, new_size)) {
2094 ass_cache_dec_ref(info->image);
2097 text_info->max_bitmaps = new_size;
2098 combined_info = text_info->combined_bitmaps;
2100 current_info = &combined_info[nb_bitmaps];
2102 memcpy(¤t_info->c, &info->c, sizeof(info->c));
2103 current_info->effect_type = info->effect_type;
2104 current_info->effect_timing = info->effect_timing;
2105 current_info->first_pos_x = info->bbox.xMax >> 6;
2107 current_info->filter.flags = 0;
2108 if (info->border_style == 3)
2109 current_info->filter.flags |= FILTER_BORDER_STYLE_3;
2110 if (info->border_x || info->border_y)
2111 current_info->filter.flags |= FILTER_NONZERO_BORDER;
2112 if (info->shadow_x || info->shadow_y)
2113 current_info->filter.flags |= FILTER_NONZERO_SHADOW;
2114 // VSFilter compatibility: invisible fill and no border?
2115 // In this case no shadow is supposed to be rendered.
2116 if (info->border || (info->c[0] & 0xFF) != 0xFF)
2117 current_info->filter.flags |= FILTER_DRAW_SHADOW;
2119 current_info->filter.be = info->be;
2120 current_info->filter.blur = 2 * info->blur * render_priv->blur_scale;
2121 current_info->filter.shadow.x = double_to_d6(info->shadow_x * render_priv->border_scale);
2122 current_info->filter.shadow.y = double_to_d6(info->shadow_y * render_priv->border_scale);
2124 current_info->x = current_info->y = INT_MAX;
2125 rectangle_reset(¤t_info->rect);
2126 rectangle_reset(¤t_info->rect_o);
2127 current_info->n_bm = current_info->n_bm_o = 0;
2128 current_info->bm = current_info->bm_o = current_info->bm_s = NULL;
2129 current_info->image = NULL;
2131 current_info->bitmap_count = current_info->max_bitmap_count = 0;
2132 current_info->bitmaps = malloc(MAX_SUB_BITMAPS_INITIAL * sizeof(BitmapRef));
2133 if (!current_info->bitmaps) {
2134 ass_cache_dec_ref(info->image);
2137 current_info->max_bitmap_count = MAX_SUB_BITMAPS_INITIAL;
2143 if (!info->image || !current_info) {
2144 ass_cache_dec_ref(info->image);
2148 if (current_info->bitmap_count >= current_info->max_bitmap_count) {
2149 size_t new_size = 2 * current_info->max_bitmap_count;
2150 if (!ASS_REALLOC_ARRAY(current_info->bitmaps, new_size)) {
2151 ass_cache_dec_ref(info->image);
2154 current_info->max_bitmap_count = new_size;
2156 current_info->bitmaps[current_info->bitmap_count].image = info->image;
2157 current_info->bitmaps[current_info->bitmap_count].x = x;
2158 current_info->bitmaps[current_info->bitmap_count].y = y;
2159 current_info->bitmap_count++;
2161 current_info->x = FFMIN(current_info->x, x);
2162 current_info->y = FFMIN(current_info->y, y);
2163 if (info->image->bm) {
2164 rectangle_combine(¤t_info->rect, info->image->bm, x, y);
2165 current_info->n_bm++;
2167 if (info->image->bm_o) {
2168 rectangle_combine(¤t_info->rect_o, info->image->bm_o, x, y);
2169 current_info->n_bm_o++;
2174 for (int i = 0; i < nb_bitmaps; i++) {
2175 CombinedBitmapInfo *info = &combined_info[i];
2176 for (int j = 0; j < info->bitmap_count; j++) {
2177 info->bitmaps[j].x -= info->x;
2178 info->bitmaps[j].y -= info->y;
2181 CompositeHashKey hk;
2182 CompositeHashValue *hv;
2183 fill_composite_hash(&hk, info);
2184 if (ass_cache_get(render_priv->cache.composite_cache, &hk, &hv)) {
2186 info->bm_o = hv->bm_o;
2187 info->bm_s = hv->bm_s;
2194 int bord = be_padding(info->filter.be);
2195 if (!bord && info->n_bm == 1) {
2196 for (int j = 0; j < info->bitmap_count; j++) {
2197 if (!info->bitmaps[j].image->bm)
2199 info->bm = copy_bitmap(render_priv->engine, info->bitmaps[j].image->bm);
2201 info->bm->left += info->bitmaps[j].x;
2202 info->bm->top += info->bitmaps[j].y;
2206 } else if (info->n_bm) {
2207 info->bm = alloc_bitmap(render_priv->engine,
2208 info->rect.x_max - info->rect.x_min + 2 * bord,
2209 info->rect.y_max - info->rect.y_min + 2 * bord, true);
2210 Bitmap *dst = info->bm;
2212 dst->left = info->rect.x_min - info->x - bord;
2213 dst->top = info->rect.y_min - info->y - bord;
2214 for (int j = 0; j < info->bitmap_count; j++) {
2215 Bitmap *src = info->bitmaps[j].image->bm;
2218 int x = info->bitmaps[j].x + src->left - dst->left;
2219 int y = info->bitmaps[j].y + src->top - dst->top;
2220 assert(x >= 0 && x + src->w <= dst->w);
2221 assert(y >= 0 && y + src->h <= dst->h);
2222 unsigned char *buf = dst->buffer + y * dst->stride + x;
2223 render_priv->engine->add_bitmaps(buf, dst->stride,
2224 src->buffer, src->stride,
2229 if (!bord && info->n_bm_o == 1) {
2230 for (int j = 0; j < info->bitmap_count; j++) {
2231 if (!info->bitmaps[j].image->bm_o)
2233 info->bm_o = copy_bitmap(render_priv->engine, info->bitmaps[j].image->bm_o);
2235 info->bm_o->left += info->bitmaps[j].x;
2236 info->bm_o->top += info->bitmaps[j].y;
2240 } else if (info->n_bm_o) {
2241 info->bm_o = alloc_bitmap(render_priv->engine,
2242 info->rect_o.x_max - info->rect_o.x_min + 2 * bord,
2243 info->rect_o.y_max - info->rect_o.y_min + 2 * bord,
2245 Bitmap *dst = info->bm_o;
2247 dst->left = info->rect_o.x_min - info->x - bord;
2248 dst->top = info->rect_o.y_min - info->y - bord;
2249 for (int j = 0; j < info->bitmap_count; j++) {
2250 Bitmap *src = info->bitmaps[j].image->bm_o;
2253 int x = info->bitmaps[j].x + src->left - dst->left;
2254 int y = info->bitmaps[j].y + src->top - dst->top;
2255 assert(x >= 0 && x + src->w <= dst->w);
2256 assert(y >= 0 && y + src->h <= dst->h);
2257 unsigned char *buf = dst->buffer + y * dst->stride + x;
2258 render_priv->engine->add_bitmaps(buf, dst->stride,
2259 src->buffer, src->stride,
2265 if (info->bm || info->bm_o) {
2266 ass_synth_blur(render_priv->engine, info->filter.flags & FILTER_BORDER_STYLE_3,
2267 info->filter.be, info->filter.blur, info->bm, info->bm_o);
2268 if (info->filter.flags & FILTER_DRAW_SHADOW)
2269 make_shadow_bitmap(info, render_priv);
2273 hv->bm_o = info->bm_o;
2274 hv->bm_s = info->bm_s;
2275 ass_cache_commit(hv, bitmap_size(hv->bm) +
2276 bitmap_size(hv->bm_o) + bitmap_size(hv->bm_s) +
2277 sizeof(CompositeHashKey) + sizeof(CompositeHashValue));
2281 text_info->n_bitmaps = nb_bitmaps;
2284 static void add_background(ASS_Renderer *render_priv, EventImages *event_images)
2286 double size_x = render_priv->state.shadow_x > 0 ?
2287 render_priv->state.shadow_x * render_priv->border_scale : 0;
2288 double size_y = render_priv->state.shadow_y > 0 ?
2289 render_priv->state.shadow_y * render_priv->border_scale : 0;
2290 int left = event_images->left - size_x;
2291 int top = event_images->top - size_y;
2292 int right = event_images->left + event_images->width + size_x;
2293 int bottom = event_images->top + event_images->height + size_y;
2294 left = FFMINMAX(left, 0, render_priv->width);
2295 top = FFMINMAX(top, 0, render_priv->height);
2296 right = FFMINMAX(right, 0, render_priv->width);
2297 bottom = FFMINMAX(bottom, 0, render_priv->height);
2298 int w = right - left;
2299 int h = bottom - top;
2302 void *nbuffer = ass_aligned_alloc(1, w * h, false);
2305 memset(nbuffer, 0xFF, w * h);
2306 ASS_Image *img = my_draw_bitmap(nbuffer, w, h, w, left, top,
2307 render_priv->state.c[3], NULL);
2309 img->next = event_images->imgs;
2310 event_images->imgs = img;
2315 * \brief Main ass rendering function, glues everything together
2316 * \param event event to render
2317 * \param event_images struct containing resulting images, will also be initialized
2318 * Process event, appending resulting ASS_Image's to images_root.
2321 ass_render_event(ASS_Renderer *render_priv, ASS_Event *event,
2322 EventImages *event_images)
2325 int MarginL, MarginR, MarginV;
2327 double device_x = 0;
2328 double device_y = 0;
2329 TextInfo *text_info = &render_priv->text_info;
2331 if (event->Style >= render_priv->track->n_styles) {
2332 ass_msg(render_priv->library, MSGL_WARN, "No style found");
2336 ass_msg(render_priv->library, MSGL_WARN, "Empty event");
2340 free_render_context(render_priv);
2341 init_render_context(render_priv, event);
2343 if (parse_events(render_priv, event))
2346 if (text_info->length == 0) {
2347 // no valid symbols in the event; this can be smth like {comment}
2348 free_render_context(render_priv);
2352 // Find shape runs and shape text
2353 ass_shaper_set_base_direction(render_priv->shaper,
2354 resolve_base_direction(render_priv->state.font_encoding));
2355 ass_shaper_find_runs(render_priv->shaper, render_priv, text_info->glyphs,
2357 if (ass_shaper_shape(render_priv->shaper, text_info) < 0) {
2358 ass_msg(render_priv->library, MSGL_ERR, "Failed to shape text");
2359 free_render_context(render_priv);
2363 retrieve_glyphs(render_priv);
2365 preliminary_layout(render_priv);
2367 // depends on glyph x coordinates being monotonous, so it should be done before line wrap
2368 process_karaoke_effects(render_priv);
2370 valign = render_priv->state.alignment & 12;
2373 (event->MarginL) ? event->MarginL : render_priv->state.style->MarginL;
2375 (event->MarginR) ? event->MarginR : render_priv->state.style->MarginR;
2377 (event->MarginV) ? event->MarginV : render_priv->state.style->MarginV;
2379 // calculate max length of a line
2380 double max_text_width =
2381 x2scr(render_priv, render_priv->track->PlayResX - MarginR) -
2382 x2scr(render_priv, MarginL);
2385 if (render_priv->state.evt_type != EVENT_HSCROLL) {
2386 // rearrange text in several lines
2387 wrap_lines_smart(render_priv, max_text_width);
2389 // no breaking or wrapping, everything in a single line
2390 text_info->lines[0].offset = 0;
2391 text_info->lines[0].len = text_info->length;
2392 text_info->n_lines = 1;
2393 measure_text(render_priv);
2396 reorder_text(render_priv);
2398 align_lines(render_priv, max_text_width);
2400 // determing text bounding box
2401 compute_string_bbox(text_info, &bbox);
2403 // determine device coordinates for text
2405 // x coordinate for everything except positioned events
2406 if (render_priv->state.evt_type == EVENT_NORMAL ||
2407 render_priv->state.evt_type == EVENT_VSCROLL) {
2408 device_x = x2scr(render_priv, MarginL);
2409 } else if (render_priv->state.evt_type == EVENT_HSCROLL) {
2410 if (render_priv->state.scroll_direction == SCROLL_RL)
2413 render_priv->track->PlayResX -
2414 render_priv->state.scroll_shift);
2415 else if (render_priv->state.scroll_direction == SCROLL_LR)
2418 render_priv->state.scroll_shift) - (bbox.xMax -
2422 // y coordinate for everything except positioned events
2423 if (render_priv->state.evt_type == EVENT_NORMAL ||
2424 render_priv->state.evt_type == EVENT_HSCROLL) {
2425 if (valign == VALIGN_TOP) { // toptitle
2427 y2scr_top(render_priv,
2428 MarginV) + text_info->lines[0].asc;
2429 } else if (valign == VALIGN_CENTER) { // midtitle
2431 y2scr(render_priv, render_priv->track->PlayResY / 2.0);
2432 device_y = scr_y - (bbox.yMax + bbox.yMin) / 2.0;
2433 } else { // subtitle
2434 double line_pos = render_priv->state.explicit ?
2435 0 : render_priv->settings.line_position;
2436 double scr_top, scr_bottom, scr_y0;
2437 if (valign != VALIGN_SUB)
2438 ass_msg(render_priv->library, MSGL_V,
2439 "Invalid valign, assuming 0 (subtitle)");
2441 y2scr_sub(render_priv,
2442 render_priv->track->PlayResY - MarginV);
2443 scr_top = y2scr_top(render_priv, 0); //xxx not always 0?
2444 device_y = scr_bottom + (scr_top - scr_bottom) * line_pos / 100.0;
2445 device_y -= text_info->height;
2446 device_y += text_info->lines[0].asc;
2447 // clip to top to avoid confusion if line_position is very high,
2448 // turning the subtitle into a toptitle
2449 // also, don't change behavior if line_position is not used
2450 scr_y0 = scr_top + text_info->lines[0].asc;
2451 if (device_y < scr_y0 && line_pos > 0) {
2455 } else if (render_priv->state.evt_type == EVENT_VSCROLL) {
2456 if (render_priv->state.scroll_direction == SCROLL_TB)
2459 render_priv->state.clip_y0 +
2460 render_priv->state.scroll_shift) - (bbox.yMax -
2462 else if (render_priv->state.scroll_direction == SCROLL_BT)
2465 render_priv->state.clip_y1 -
2466 render_priv->state.scroll_shift);
2469 // positioned events are totally different
2470 if (render_priv->state.evt_type == EVENT_POSITIONED) {
2473 get_base_point(&bbox, render_priv->state.alignment, &base_x, &base_y);
2475 x2scr_pos(render_priv, render_priv->state.pos_x) - base_x;
2477 y2scr_pos(render_priv, render_priv->state.pos_y) - base_y;
2480 // fix clip coordinates (they depend on alignment)
2481 if (render_priv->state.evt_type == EVENT_NORMAL ||
2482 render_priv->state.evt_type == EVENT_HSCROLL ||
2483 render_priv->state.evt_type == EVENT_VSCROLL) {
2484 render_priv->state.clip_x0 =
2485 x2scr_scaled(render_priv, render_priv->state.clip_x0);
2486 render_priv->state.clip_x1 =
2487 x2scr_scaled(render_priv, render_priv->state.clip_x1);
2488 if (valign == VALIGN_TOP) {
2489 render_priv->state.clip_y0 =
2490 y2scr_top(render_priv, render_priv->state.clip_y0);
2491 render_priv->state.clip_y1 =
2492 y2scr_top(render_priv, render_priv->state.clip_y1);
2493 } else if (valign == VALIGN_CENTER) {
2494 render_priv->state.clip_y0 =
2495 y2scr(render_priv, render_priv->state.clip_y0);
2496 render_priv->state.clip_y1 =
2497 y2scr(render_priv, render_priv->state.clip_y1);
2498 } else if (valign == VALIGN_SUB) {
2499 render_priv->state.clip_y0 =
2500 y2scr_sub(render_priv, render_priv->state.clip_y0);
2501 render_priv->state.clip_y1 =
2502 y2scr_sub(render_priv, render_priv->state.clip_y1);
2504 } else if (render_priv->state.evt_type == EVENT_POSITIONED) {
2505 render_priv->state.clip_x0 =
2506 x2scr_pos_scaled(render_priv, render_priv->state.clip_x0);
2507 render_priv->state.clip_x1 =
2508 x2scr_pos_scaled(render_priv, render_priv->state.clip_x1);
2509 render_priv->state.clip_y0 =
2510 y2scr_pos(render_priv, render_priv->state.clip_y0);
2511 render_priv->state.clip_y1 =
2512 y2scr_pos(render_priv, render_priv->state.clip_y1);
2515 if (render_priv->state.explicit) {
2516 // we still need to clip against screen boundaries
2517 double zx = x2scr_pos_scaled(render_priv, 0);
2518 double zy = y2scr_pos(render_priv, 0);
2519 double sx = x2scr_pos_scaled(render_priv, render_priv->track->PlayResX);
2520 double sy = y2scr_pos(render_priv, render_priv->track->PlayResY);
2522 render_priv->state.clip_x0 = render_priv->state.clip_x0 < zx ? zx : render_priv->state.clip_x0;
2523 render_priv->state.clip_y0 = render_priv->state.clip_y0 < zy ? zy : render_priv->state.clip_y0;
2524 render_priv->state.clip_x1 = render_priv->state.clip_x1 > sx ? sx : render_priv->state.clip_x1;
2525 render_priv->state.clip_y1 = render_priv->state.clip_y1 > sy ? sy : render_priv->state.clip_y1;
2528 calculate_rotation_params(render_priv, &bbox, device_x, device_y);
2530 render_and_combine_glyphs(render_priv, device_x, device_y);
2532 memset(event_images, 0, sizeof(*event_images));
2533 event_images->top = device_y - text_info->lines[0].asc;
2534 event_images->height = text_info->height;
2535 event_images->left =
2536 (device_x + bbox.xMin * render_priv->font_scale_x) + 0.5;
2537 event_images->width =
2538 (bbox.xMax - bbox.xMin) * render_priv->font_scale_x + 0.5;
2539 event_images->detect_collisions = render_priv->state.detect_collisions;
2540 event_images->shift_direction = (valign == VALIGN_TOP) ? 1 : -1;
2541 event_images->event = event;
2542 event_images->imgs = render_text(render_priv);
2544 if (render_priv->state.border_style == 4)
2545 add_background(render_priv, event_images);
2547 ass_shaper_cleanup(render_priv->shaper, text_info);
2548 free_render_context(render_priv);
2554 * \brief Check cache limits and reset cache if they are exceeded
2556 static void check_cache_limits(ASS_Renderer *priv, CacheStore *cache)
2558 ass_cache_cut(cache->composite_cache, cache->composite_max_size);
2559 ass_cache_cut(cache->bitmap_cache, cache->bitmap_max_size);
2560 ass_cache_cut(cache->outline_cache, cache->glyph_max);
2564 * \brief Start a new frame
2567 ass_start_frame(ASS_Renderer *render_priv, ASS_Track *track,
2570 ASS_Settings *settings_priv = &render_priv->settings;
2572 if (!render_priv->settings.frame_width
2573 && !render_priv->settings.frame_height)
2574 return 1; // library not initialized
2576 if (!render_priv->fontselect)
2579 if (render_priv->library != track->library)
2582 if (track->n_events == 0)
2583 return 1; // nothing to do
2585 render_priv->track = track;
2586 render_priv->time = now;
2588 ass_lazy_track_init(render_priv->library, render_priv->track);
2590 ass_shaper_set_kerning(render_priv->shaper, track->Kerning);
2591 ass_shaper_set_language(render_priv->shaper, track->Language);
2592 ass_shaper_set_level(render_priv->shaper, render_priv->settings.shaper);
2595 double par = render_priv->settings.par;
2597 if (settings_priv->frame_width && settings_priv->frame_height &&
2598 settings_priv->storage_width && settings_priv->storage_height) {
2599 double dar = ((double) settings_priv->frame_width) /
2600 settings_priv->frame_height;
2601 double sar = ((double) settings_priv->storage_width) /
2602 settings_priv->storage_height;
2607 render_priv->font_scale_x = par;
2609 render_priv->prev_images_root = render_priv->images_root;
2610 render_priv->images_root = NULL;
2612 check_cache_limits(render_priv, &render_priv->cache);
2617 static int cmp_event_layer(const void *p1, const void *p2)
2619 ASS_Event *e1 = ((EventImages *) p1)->event;
2620 ASS_Event *e2 = ((EventImages *) p2)->event;
2621 if (e1->Layer < e2->Layer)
2623 if (e1->Layer > e2->Layer)
2625 if (e1->ReadOrder < e2->ReadOrder)
2627 if (e1->ReadOrder > e2->ReadOrder)
2632 static ASS_RenderPriv *get_render_priv(ASS_Renderer *render_priv,
2635 if (!event->render_priv) {
2636 event->render_priv = calloc(1, sizeof(ASS_RenderPriv));
2637 if (!event->render_priv)
2640 if (render_priv->render_id != event->render_priv->render_id) {
2641 memset(event->render_priv, 0, sizeof(ASS_RenderPriv));
2642 event->render_priv->render_id = render_priv->render_id;
2645 return event->render_priv;
2648 static int overlap(Segment *s1, Segment *s2)
2650 if (s1->a >= s2->b || s2->a >= s1->b ||
2651 s1->ha >= s2->hb || s2->ha >= s1->hb)
2656 static int cmp_segment(const void *p1, const void *p2)
2658 return ((Segment *) p1)->a - ((Segment *) p2)->a;
2662 shift_event(ASS_Renderer *render_priv, EventImages *ei, int shift)
2664 ASS_Image *cur = ei->imgs;
2666 cur->dst_y += shift;
2667 // clip top and bottom
2668 if (cur->dst_y < 0) {
2669 int clip = -cur->dst_y;
2671 cur->bitmap += clip * cur->stride;
2674 if (cur->dst_y + cur->h >= render_priv->height) {
2675 int clip = cur->dst_y + cur->h - render_priv->height;
2687 // dir: 1 - move down
2689 static int fit_segment(Segment *s, Segment *fixed, int *cnt, int dir)
2694 if (dir == 1) // move down
2695 for (i = 0; i < *cnt; ++i) {
2696 if (s->b + shift <= fixed[i].a || s->a + shift >= fixed[i].b ||
2697 s->hb <= fixed[i].ha || s->ha >= fixed[i].hb)
2699 shift = fixed[i].b - s->a;
2700 } else // dir == -1, move up
2701 for (i = *cnt - 1; i >= 0; --i) {
2702 if (s->b + shift <= fixed[i].a || s->a + shift >= fixed[i].b ||
2703 s->hb <= fixed[i].ha || s->ha >= fixed[i].hb)
2705 shift = fixed[i].a - s->b;
2708 fixed[*cnt].a = s->a + shift;
2709 fixed[*cnt].b = s->b + shift;
2710 fixed[*cnt].ha = s->ha;
2711 fixed[*cnt].hb = s->hb;
2713 qsort(fixed, *cnt, sizeof(Segment), cmp_segment);
2719 fix_collisions(ASS_Renderer *render_priv, EventImages *imgs, int cnt)
2721 Segment *used = ass_realloc_array(NULL, cnt, sizeof(*used));
2728 // fill used[] with fixed events
2729 for (i = 0; i < cnt; ++i) {
2730 ASS_RenderPriv *priv;
2731 if (!imgs[i].detect_collisions)
2733 priv = get_render_priv(render_priv, imgs[i].event);
2734 if (priv && priv->height > 0) { // it's a fixed event
2737 s.b = priv->top + priv->height;
2739 s.hb = priv->left + priv->width;
2740 if (priv->height != imgs[i].height) { // no, it's not
2741 ass_msg(render_priv->library, MSGL_WARN,
2742 "Event height has changed");
2748 for (j = 0; j < cnt_used; ++j)
2749 if (overlap(&s, used + j)) { // no, it's not
2755 if (priv->height > 0) { // still a fixed event
2756 used[cnt_used].a = priv->top;
2757 used[cnt_used].b = priv->top + priv->height;
2758 used[cnt_used].ha = priv->left;
2759 used[cnt_used].hb = priv->left + priv->width;
2761 shift_event(render_priv, imgs + i, priv->top - imgs[i].top);
2765 qsort(used, cnt_used, sizeof(Segment), cmp_segment);
2767 // try to fit other events in free spaces
2768 for (i = 0; i < cnt; ++i) {
2769 ASS_RenderPriv *priv;
2770 if (!imgs[i].detect_collisions)
2772 priv = get_render_priv(render_priv, imgs[i].event);
2773 if (priv && priv->height == 0) { // not a fixed event
2777 s.b = imgs[i].top + imgs[i].height;
2778 s.ha = imgs[i].left;
2779 s.hb = imgs[i].left + imgs[i].width;
2780 shift = fit_segment(&s, used, &cnt_used, imgs[i].shift_direction);
2782 shift_event(render_priv, imgs + i, shift);
2784 priv->top = imgs[i].top;
2785 priv->height = imgs[i].height;
2786 priv->left = imgs[i].left;
2787 priv->width = imgs[i].width;
2796 * \brief compare two images
2797 * \param i1 first image
2798 * \param i2 second image
2799 * \return 0 if identical, 1 if different positions, 2 if different content
2801 static int ass_image_compare(ASS_Image *i1, ASS_Image *i2)
2807 if (i1->stride != i2->stride)
2809 if (i1->color != i2->color)
2811 if (i1->bitmap != i2->bitmap)
2813 if (i1->dst_x != i2->dst_x)
2815 if (i1->dst_y != i2->dst_y)
2821 * \brief compare current and previous image list
2822 * \param priv library handle
2823 * \return 0 if identical, 1 if different positions, 2 if different content
2825 static int ass_detect_change(ASS_Renderer *priv)
2827 ASS_Image *img, *img2;
2830 img = priv->prev_images_root;
2831 img2 = priv->images_root;
2833 while (img && diff < 2) {
2834 ASS_Image *next, *next2;
2837 int d = ass_image_compare(img, img2);
2842 // previous list is shorter
2850 // is the previous list longer?
2858 * \brief render a frame
2859 * \param priv library handle
2860 * \param track track
2861 * \param now current video timestamp (ms)
2862 * \param detect_change a value describing how the new images differ from the previous ones will be written here:
2863 * 0 if identical, 1 if different positions, 2 if different content.
2864 * Can be NULL, in that case no detection is performed.
2866 ASS_Image *ass_render_frame(ASS_Renderer *priv, ASS_Track *track,
2867 long long now, int *detect_change)
2874 rc = ass_start_frame(priv, track, now);
2876 if (detect_change) {
2882 // render events separately
2884 for (i = 0; i < track->n_events; ++i) {
2885 ASS_Event *event = track->events + i;
2886 if ((event->Start <= now)
2887 && (now < (event->Start + event->Duration))) {
2888 if (cnt >= priv->eimg_size) {
2889 priv->eimg_size += 100;
2892 priv->eimg_size * sizeof(EventImages));
2894 rc = ass_render_event(priv, event, priv->eimg + cnt);
2901 qsort(priv->eimg, cnt, sizeof(EventImages), cmp_event_layer);
2903 // call fix_collisions for each group of events with the same layer
2905 for (i = 1; i < cnt; ++i)
2906 if (last->event->Layer != priv->eimg[i].event->Layer) {
2907 fix_collisions(priv, last, priv->eimg + i - last);
2908 last = priv->eimg + i;
2911 fix_collisions(priv, last, priv->eimg + cnt - last);
2914 tail = &priv->images_root;
2915 for (i = 0; i < cnt; ++i) {
2916 ASS_Image *cur = priv->eimg[i].imgs;
2923 ass_frame_ref(priv->images_root);
2926 *detect_change = ass_detect_change(priv);
2928 // free the previous image list
2929 ass_frame_unref(priv->prev_images_root);
2930 priv->prev_images_root = NULL;
2932 return priv->images_root;
2936 * \brief Add reference to a frame image list.
2937 * \param image_list image list returned by ass_render_frame()
2939 void ass_frame_ref(ASS_Image *img)
2943 ((ASS_ImagePriv *) img)->ref_count++;
2947 * \brief Release reference to a frame image list.
2948 * \param image_list image list returned by ass_render_frame()
2950 void ass_frame_unref(ASS_Image *img)
2952 if (!img || --((ASS_ImagePriv *) img)->ref_count)
2955 ASS_ImagePriv *priv = (ASS_ImagePriv *) img;
2958 ass_cache_dec_ref(priv->source);
2960 ass_aligned_free(priv->result.bitmap);