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_render.h"
28 #include "ass_parse.h"
29 #include "ass_shaper.h"
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
39 ASS_Renderer *ass_renderer_init(ASS_Library *library)
43 ASS_Renderer *priv = 0;
44 int vmajor, vminor, vpatch;
46 error = FT_Init_FreeType(&ft);
48 ass_msg(library, MSGL_FATAL, "%s failed", "FT_Init_FreeType");
52 FT_Library_Version(ft, &vmajor, &vminor, &vpatch);
53 ass_msg(library, MSGL_V, "Raster: FreeType %d.%d.%d",
54 vmajor, vminor, vpatch);
56 priv = calloc(1, sizeof(ASS_Renderer));
62 priv->library = library;
64 // images_root and related stuff is zero-filled in calloc
66 #if (defined(__i386__) || defined(__x86_64__)) && CONFIG_ASM
68 priv->engine = &ass_bitmap_engine_avx2;
70 priv->engine = &ass_bitmap_engine_sse2;
72 priv->engine = &ass_bitmap_engine_c;
74 priv->engine = &ass_bitmap_engine_c;
78 rasterizer_init(&priv->rasterizer, 16);
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;
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));
97 priv->settings.font_size_coeff = 1.;
98 priv->settings.selective_style_overrides = ASS_OVERRIDE_BIT_SELECTIVE_FONT_SCALE;
100 priv->shaper = ass_shaper_new(0);
101 ass_shaper_info(library);
102 #ifdef CONFIG_HARFBUZZ
103 priv->settings.shaper = ASS_SHAPING_COMPLEX;
105 priv->settings.shaper = ASS_SHAPING_SIMPLE;
110 ass_msg(library, MSGL_V, "Initialized");
112 ass_msg(library, MSGL_ERR, "Initialization failed");
117 static void free_list_clear(ASS_Renderer *render_priv)
119 if (render_priv->free_head) {
120 FreeList *item = render_priv->free_head;
123 ass_aligned_free(item->object);
127 render_priv->free_head = NULL;
131 void ass_renderer_done(ASS_Renderer *render_priv)
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);
138 ass_free_images(render_priv->images_root);
139 ass_free_images(render_priv->prev_images_root);
141 #if CONFIG_RASTERIZER
142 rasterizer_done(&render_priv->rasterizer);
145 if (render_priv->state.stroker) {
146 FT_Stroker_Done(render_priv->state.stroker);
147 render_priv->state.stroker = 0;
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);
158 free(render_priv->text_info.combined_bitmaps);
160 free(render_priv->settings.default_font);
161 free(render_priv->settings.default_family);
163 free(render_priv->user_override_style.FontName);
165 free_list_clear(render_priv);
170 * \brief Create a new ASS_Image
171 * Parameters are the same as ASS_Image fields.
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)
177 ASS_Image *img = malloc(sizeof(ASS_Image));
182 img->stride = stride;
183 img->bitmap = bitmap;
193 * \brief Mapping between script and screen coordinates
195 static double x2scr_pos(ASS_Renderer *render_priv, double x)
197 return x * render_priv->orig_width / render_priv->font_scale_x / render_priv->track->PlayResX +
198 render_priv->settings.left_margin;
200 static double x2scr(ASS_Renderer *render_priv, double x)
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);
208 static double x2scr_pos_scaled(ASS_Renderer *render_priv, double x)
210 return x * render_priv->orig_width / render_priv->track->PlayResX +
211 render_priv->settings.left_margin;
213 static double x2scr_scaled(ASS_Renderer *render_priv, double x)
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);
222 * \brief Mapping between script and screen coordinates
224 static double y2scr_pos(ASS_Renderer *render_priv, double y)
226 return y * render_priv->orig_height / render_priv->track->PlayResY +
227 render_priv->settings.top_margin;
229 static double y2scr(ASS_Renderer *render_priv, double y)
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);
238 // the same for toptitles
239 static double y2scr_top(ASS_Renderer *render_priv, double y)
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;
247 return y * render_priv->orig_height_nocrop /
248 render_priv->track->PlayResY +
249 FFMAX(render_priv->settings.top_margin, 0);
251 // the same for subtitles
252 static double y2scr_sub(ASS_Renderer *render_priv, double y)
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);
262 return y * render_priv->orig_height_nocrop /
263 render_priv->track->PlayResY +
264 FFMAX(render_priv->settings.top_margin, 0);
268 * \brief Convert bitmap glyphs into ASS_Image list with inverse clipping
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).
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)
285 int i, j, x0, y0, x1, y1, cx0, cy0, cx1, cy1, sx, sy, zx, zy;
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);
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;
307 // calculate rectangles and discard invalid ones while we're at it.
311 r[i].x1 = (cx0 > x1) ? x1 : cx0;
313 if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++;
314 r[i].x0 = (cx0 < 0) ? x0 : cx0;
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;
323 if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++;
324 r[i].x0 = (cx1 < 0) ? x0 : cx1;
328 if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++;
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;
338 // draw the rectangles
339 for (j = 0; j < i; j++) {
341 // kick out rectangles that are invalid now
342 if (r[j].x1 <= r[j].x0 || r[j].y1 <= r[j].y0)
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);
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);
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.
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)
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,
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;
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);
413 tmp = dst_x - clip_x0;
416 render_priv->state.has_clips = 1;
418 tmp = dst_y - clip_y0;
421 render_priv->state.has_clips = 1;
423 tmp = clip_x1 - dst_x - bm->w;
426 render_priv->state.has_clips = 1;
428 tmp = clip_y1 - dst_y - bm->h;
431 render_priv->state.has_clips = 1;
434 if ((b_y0 >= b_y1) || (b_x0 >= b_x1))
437 if (brk > b_x0) { // draw left part
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;
448 if (brk < b_x1) { // draw right part
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;
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)
467 FreeList *l = calloc(1, sizeof(FreeList));
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;
476 render_priv->free_tail->next = l;
477 render_priv->free_tail = render_priv->free_tail->next;
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.
487 static void blend_vector_clip(ASS_Renderer *render_priv,
490 Bitmap *clip_bm = NULL;
492 ASS_Drawing *drawing = render_priv->state.clip_drawing;
494 BitmapHashValue *val;
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);
510 // Not found in cache, parse and rasterize it
511 ASS_Outline *outline = ass_drawing_parse(drawing, 1);
513 ass_msg(render_priv->library, MSGL_WARN,
514 "Clip vector parsing failed. Skipping.");
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) {
522 .x = int_to_d6(render_priv->settings.left_margin),
523 .y = -int_to_d6(render_priv->settings.top_margin),
525 outline_translate(outline, trans.x, trans.y);
528 clip_bm = outline_to_bitmap(render_priv, outline, 0);
531 memset(&v, 0, sizeof(v));
532 key.u.clip.text = strdup(drawing->text);
534 ass_cache_put(render_priv->cache.bitmap_cache, &key, &v);
537 if (!clip_bm) return;
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;
547 render_priv->state.has_clips = 1;
549 abuffer = cur->bitmap;
550 bbuffer = clip_bm->buffer;
560 bs = clip_bm->stride;
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);
574 if (render_priv->state.clip_drawing_mode) {
576 if (ax + aw < bx || ay + ah < by || ax > bx + bw ||
577 ay > by + bh || !h || !w) {
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);
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,
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;
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);
611 render_priv->engine->mul_bitmaps(nbuffer, ns,
612 abuffer + atop * as + aleft, as,
613 bbuffer + btop * bs + bleft, bs,
621 cur->bitmap = nbuffer;
626 * \brief Convert TextInfo struct to ASS_Image list
627 * Splits glyphs in halves when needed (for \kf karaoke).
629 static ASS_Image *render_text(ASS_Renderer *render_priv)
632 ASS_Image **tail = &head;
633 TextInfo *text_info = &render_priv->text_info;
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)
641 render_glyph(render_priv, info->bm_s, info->x, info->y, info->c[3], 0,
642 1000000, tail, IMAGE_TYPE_SHADOW);
645 for (int i = 0; i < text_info->n_bitmaps; ++i) {
646 CombinedBitmapInfo *info = &text_info->combined_bitmaps[i];
650 if ((info->effect_type == EF_KARAOKE_KO)
651 && (info->effect_timing <= info->first_pos_x)) {
655 render_glyph(render_priv, info->bm_o, info->x, info->y, info->c[2],
656 0, 1000000, tail, IMAGE_TYPE_OUTLINE);
660 for (int i = 0; i < text_info->n_bitmaps; ++i) {
661 CombinedBitmapInfo *info = &text_info->combined_bitmaps[i];
665 if ((info->effect_type == EF_KARAOKE)
666 || (info->effect_type == EF_KARAOKE_KO)) {
667 if (info->effect_timing > info->first_pos_x)
669 render_glyph(render_priv, info->bm, info->x, info->y,
670 info->c[0], 0, 1000000, tail, IMAGE_TYPE_CHARACTER);
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) {
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);
681 render_glyph(render_priv, info->bm, info->x, info->y, info->c[0],
682 0, 1000000, tail, IMAGE_TYPE_CHARACTER);
686 blend_vector_clip(render_priv, head);
691 static void compute_string_bbox(TextInfo *text, DBBox *bbox)
695 if (text->length > 0) {
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);
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);
711 bbox->xMin = bbox->xMax = bbox->yMin = bbox->yMax = 0.;
714 static ASS_Style *handle_selective_style_overrides(ASS_Renderer *render_priv,
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;
728 user->Name = "OverrideStyle"; // name insignificant
730 // Either the event's style, or the style forced with a \r tag.
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.
739 render_priv->state.explicit = explicit;
741 render_priv->state.apply_font_scale =
742 !explicit || !(requested & ASS_OVERRIDE_BIT_SELECTIVE_FONT_SCALE);
744 // On positioned events, do not apply most overrides.
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;
755 // Copies fields even not covered by any of the other bits.
756 if (requested & ASS_OVERRIDE_FULL_STYLE)
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;
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;
771 if (requested & ASS_OVERRIDE_BIT_FONT_NAME) {
772 new->FontName = user->FontName;
773 new->treat_fontname_as_pattern = user->treat_fontname_as_pattern;
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;
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;
790 if (requested & ASS_OVERRIDE_BIT_BORDER) {
791 new->BorderStyle = user->BorderStyle;
792 new->Outline = user->Outline * scale;
793 new->Shadow = user->Shadow * scale;
796 if (requested & ASS_OVERRIDE_BIT_ALIGNMENT)
797 new->Alignment = user->Alignment;
799 if (requested & ASS_OVERRIDE_BIT_MARGINS) {
800 new->MarginL = user->MarginL;
801 new->MarginR = user->MarginR;
802 new->MarginV = user->MarginV;
806 new->FontName = rstyle->FontName;
808 render_priv->state.style = new;
809 render_priv->state.overrides = requested;
814 static void init_font_scale(ASS_Renderer *render_priv)
816 ASS_Settings *settings_priv = &render_priv->settings;
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;
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;
830 render_priv->border_scale = render_priv->blur_scale;
831 if (!settings_priv->storage_height)
832 render_priv->blur_scale = render_priv->border_scale;
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;
842 * \brief partially reset render_context to style values
843 * Works like {\r}: resets some style overrides
845 void reset_render_context(ASS_Renderer *render_priv, ASS_Style *style)
847 style = handle_selective_style_overrides(render_priv, style);
849 init_font_scale(render_priv);
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;
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);
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;
887 * \brief Start new event. Reset render_priv->state.
890 init_render_context(ASS_Renderer *render_priv, ASS_Event *event)
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;
897 reset_render_context(render_priv, NULL);
898 render_priv->state.wrap_style = render_priv->track->WrapStyle;
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;
919 apply_transition_effects(render_priv, event);
922 static void free_render_context(ASS_Renderer *render_priv)
924 free(render_priv->state.family);
925 ass_drawing_free(render_priv->state.clip_drawing);
927 render_priv->state.family = NULL;
928 render_priv->state.clip_drawing = NULL;
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;
937 * Replace the outline of a glyph by a contour which makes up a simple
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)
945 double scale_y = info->orig_scale_y;
946 double scale_x = info->orig_scale_x;
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);
959 desc += asc * (scale_y - 1.0);
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 },
968 ol->n_points = ol->n_contours = 0;
969 if (!outline_alloc(ol, 4, 1))
971 for (int i = 0; i < 4; ++i) {
972 ol->points[ol->n_points] = points[i];
973 ol->tags[ol->n_points++] = 1;
975 ol->contours[ol->n_contours++] = ol->n_points - 1;
979 * Stroke an outline glyph in x/y direction. Applies various fixups to get
980 * around limitations of the FreeType stroker.
982 static void stroke_outline(ASS_Renderer *render_priv, ASS_Outline *outline,
985 if (sx <= 0 && sy <= 0)
988 fix_freetype_stroker(outline, sx, sy);
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",
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)
1005 contours = contours_large;
1007 for (size_t i = 0; i < n_contours; ++i)
1008 contours[i] = FFMIN(outline->contours[i], n_points - 1);
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;
1018 // Borders are equal; use the regular stroker
1019 if (sx == sy && render_priv->state.stroker) {
1021 FT_StrokerBorder border = FT_Outline_GetOutsideBorder(&ftol);
1022 error = FT_Stroker_ParseOutline(render_priv->state.stroker, &ftol, 0);
1024 ass_msg(render_priv->library, MSGL_WARN,
1025 "FT_Stroker_ParseOutline failed, error: %d", error);
1027 unsigned new_points, new_contours;
1028 error = FT_Stroker_GetBorderCounts(render_priv->state.stroker, border,
1029 &new_points, &new_contours);
1031 ass_msg(render_priv->library, MSGL_WARN,
1032 "FT_Stroker_GetBorderCounts failed, error: %d", error);
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);
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);
1050 ftol.n_points = ftol.n_contours = 0;
1051 ftol.points = outline->points;
1052 ftol.tags = outline->tags;
1054 FT_Stroker_ExportBorder(render_priv->state.stroker, border, &ftol);
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];
1061 // "Stroke" with the outline emboldener (in two passes if needed).
1062 // The outlines look uglier, but the emboldening never adds any points
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);
1073 FT_Outline_New(render_priv->ftlibrary, ftol.n_points,
1074 ftol.n_contours, &nol);
1075 FT_Outline_Copy(&ftol, &nol);
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);
1082 for (i = 0; i < ftol.n_points; i++)
1083 ftol.points[i].y = nol.points[i].y;
1085 FT_Outline_Done(render_priv->ftlibrary, &nol);
1089 free(contours_large);
1093 * \brief Prepare glyph hash
1096 fill_glyph_hash(ASS_Renderer *priv, OutlineHashKey *outline_key,
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
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;
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;
1131 info->border_style == 3 ? double_to_d16(info->hspacing) : 0;
1136 * \brief Prepare combined-bitmap hash
1138 static void fill_composite_hash(CompositeHashKey *hk, CombinedBitmapInfo *info)
1140 hk->filter = info->filter;
1141 hk->bitmap_count = info->bitmap_count;
1142 hk->bitmaps = info->bitmaps;
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
1154 get_outline_glyph(ASS_Renderer *priv, GlyphInfo *info)
1156 OutlineHashValue *val;
1159 memset(&info->hash_key, 0, sizeof(info->hash_key));
1161 fill_glyph_hash(priv, &key, info);
1162 val = ass_cache_get(priv->cache.outline_cache, &key);
1166 memset(&v, 0, sizeof(v));
1168 if (info->drawing) {
1169 ASS_Drawing *drawing = info->drawing;
1170 ass_drawing_hash(drawing);
1171 if(!ass_drawing_parse(drawing, 0))
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);
1180 ass_face_set_size(info->font->faces[info->face_index],
1182 ass_font_set_transform(info->font, info->scale_x,
1183 info->scale_y, NULL);
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);
1194 FT_Done_Glyph(glyph);
1195 ass_font_get_asc_desc(info->font, info->symbol,
1197 v.asc *= info->scale_y;
1198 v.desc *= info->scale_y;
1205 outline_get_cbox(v.outline, &v.bbox_scaled);
1207 if (info->border_style == 3) {
1210 v.border = calloc(1, sizeof(ASS_Outline));
1212 if (priv->settings.shaper == ASS_SHAPING_SIMPLE || info->drawing)
1213 advance = v.advance;
1215 advance = info->advance;
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));
1221 } else if ((info->border_x > 0 || info->border_y > 0)
1222 && double_to_d6(info->scale_x) && double_to_d6(info->scale_y)) {
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));
1231 val = ass_cache_put(priv->cache.outline_cache, &key, &v);
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;
1242 info->asc = val->asc;
1243 info->desc = val->desc;
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.
1252 transform_3d_points(FT_Vector shift, ASS_Outline *outline, double frx, double fry,
1253 double frz, double fax, double fay, double scale,
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;
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);
1272 xx = x * cz + y * sz;
1273 yy = -(x * sz - y * cz);
1277 y = yy * cx + zz * sx;
1278 z = yy * sx - zz * cx;
1280 xx = x * cy + z * sy;
1282 zz = x * sy - z * cy;
1284 zz = FFMAX(zz, 1000 - dist);
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;
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.
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)
1310 if (frx != 0. || fry != 0. || frz != 0. || fax != 0. || fay != 0.) {
1312 transform_3d_points(shift, outline, frx, fry, frz,
1313 fax, fay, scale, yshift);
1316 transform_3d_points(shift, border, frx, fry, frz,
1317 fax, fay, scale, yshift);
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).
1330 get_bitmap_glyph(ASS_Renderer *render_priv, GlyphInfo *info)
1332 BitmapHashValue *val;
1333 OutlineBitmapHashKey *key = &info->hash_key.u.outline;
1335 if (!info->outline || info->symbol == '\n' || info->symbol == 0 || info->skip)
1338 val = ass_cache_get(render_priv->cache.bitmap_cache, &info->hash_key);
1342 BitmapHashValue hash_val;
1344 double fax_scaled, fay_scaled;
1345 double scale_x = render_priv->font_scale_x;
1347 hash_val.bm = hash_val.bm_o = NULL;
1349 ASS_Outline *outline = outline_copy(info->outline);
1350 ASS_Outline *border = outline_copy(info->border);
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;
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);
1364 // PAR correction scaling
1365 FT_Matrix m = { double_to_d16(scale_x), 0,
1366 0, double_to_d16(1.0) };
1371 outline_transform(outline, &m);
1372 outline_translate(outline, key->advance.x, -key->advance.y);
1376 outline_transform(border, &m);
1377 outline_translate(border, key->advance.x, -key->advance.y);
1381 error = outline_to_bitmap2(render_priv, outline, border,
1382 &hash_val.bm, &hash_val.bm_o);
1386 val = ass_cache_put(render_priv->cache.bitmap_cache, &info->hash_key,
1389 outline_free(outline);
1391 outline_free(border);
1399 * This function goes through text_info and calculates text parameters.
1400 * The following text_info fields are filled:
1406 static void measure_text(ASS_Renderer *render_priv)
1408 TextInfo *text_info = &render_priv->text_info;
1410 double max_asc = 0., max_desc = 0.;
1411 GlyphInfo *last = NULL;
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;
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;
1425 max_asc = max_desc = 0.;
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) {
1440 text_info->height +=
1441 (text_info->n_lines -
1442 1) * render_priv->settings.line_spacing;
1446 * Mark extra whitespace for later removal.
1448 #define IS_WHITESPACE(x) ((x->symbol == ' ' || x->symbol == '\n') \
1450 static void trim_whitespace(ASS_Renderer *render_priv)
1454 TextInfo *ti = &render_priv->text_info;
1456 // Mark trailing spaces
1458 cur = ti->glyphs + i;
1459 while (i && IS_WHITESPACE(cur)) {
1461 cur = ti->glyphs + --i;
1464 // Mark leading whitespace
1467 while (i < ti->length && IS_WHITESPACE(cur)) {
1469 cur = ti->glyphs + ++i;
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
1478 cur = ti->glyphs + j;
1479 while (j && IS_WHITESPACE(cur)) {
1481 cur = ti->glyphs + --j;
1483 // A break itself can contain a whitespace, too
1484 cur = ti->glyphs + i;
1485 if (cur->symbol == ' ' || cur->symbol == '\n') {
1487 // Mark whitespace after
1489 cur = ti->glyphs + j;
1490 while (j < ti->length && IS_WHITESPACE(cur)) {
1492 cur = ti->glyphs + ++j;
1499 #undef IS_WHITESPACE
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.
1510 * FIXME: implement style 0 and 3 correctly
1513 wrap_lines_smart(ASS_Renderer *render_priv, double max_text_width)
1516 GlyphInfo *cur, *s1, *e1, *s2, *s3;
1524 TextInfo *text_info = &render_priv->text_info;
1527 text_info->n_lines = 1;
1529 s1 = text_info->glyphs; // current line start
1530 for (i = 0; i < text_info->length; ++i) {
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;
1537 if (cur->symbol == '\n') {
1540 ass_msg(render_priv->library, MSGL_DBG2,
1541 "forced line break at %d", break_at);
1542 } else if (cur->symbol == ' ') {
1544 } else if (len >= max_text_width
1545 && (render_priv->state.wrap_style != 2)) {
1547 break_at = last_space;
1549 ass_msg(render_priv->library, MSGL_DBG2, "line break at %d",
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,
1562 text_info->max_lines);
1564 if (lead < text_info->length) {
1565 text_info->glyphs[lead].linebreak = break_type;
1567 s1 = text_info->glyphs + lead;
1568 text_info->n_lines++;
1572 #define DIFF(x,y) (((x) < (y)) ? (y - x) : (x - y))
1574 while (!exit && render_priv->state.wrap_style != 1) {
1576 s3 = text_info->glyphs;
1578 for (i = 0; i <= text_info->length; ++i) {
1579 cur = text_info->glyphs + i;
1580 if ((i == text_info->length) || cur->linebreak) {
1584 if (s1 && (s2->linebreak == 1)) { // have at least 2 lines, and linebreak is 'soft'
1585 double l1, l2, l1_new, l2_new;
1590 } while ((w > s1) && (w->symbol == ' '));
1591 while ((w > s1) && (w->symbol != ' ')) {
1595 while ((e1 > s1) && (e1->symbol == ' ')) {
1598 if (w->symbol == ' ')
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));
1612 if (DIFF(l1_new, l2_new) < DIFF(l1, l2)) {
1619 if (i == text_info->length)
1624 assert(text_info->n_lines >= 1);
1627 measure_text(render_priv);
1628 trim_whitespace(render_priv);
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);
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;
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;
1653 pen_shift_x = d6_to_double(-cur->pos.x);
1654 pen_shift_y += height + render_priv->settings.line_spacing;
1656 cur->pos.x += double_to_d6(pen_shift_x);
1657 cur->pos.y += double_to_d6(pen_shift_y);
1659 text_info->lines[cur_line - 1].len =
1660 text_info->length - text_info->lines[cur_line - 1].offset;
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);
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
1677 static void get_base_point(DBBox *bbox, int alignment, double *bx, double *by)
1679 const int halign = alignment & 3;
1680 const int valign = alignment & 12;
1687 *bx = (bbox->xMax + bbox->xMin) / 2.0;
1699 *by = (bbox->yMax + bbox->yMin) / 2.0;
1708 * Prepare bitmap hash key of a glyph
1711 fill_bitmap_hash(ASS_Renderer *priv, GlyphInfo *info,
1712 OutlineBitmapHashKey *hash_key)
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);
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
1731 fix_glyph_scaling(ASS_Renderer *priv, GlyphInfo *glyph)
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
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;
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;
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
1754 static int is_new_bm_run(GlyphInfo *info, GlyphInfo *last)
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;
1786 static void make_shadow_bitmap(CombinedBitmapInfo *info, ASS_Renderer *render_priv)
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);
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;
1807 } else if (info->bm)
1808 info->bm_s = copy_bitmap(render_priv->engine, info->bm);
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);
1820 // Parse event text.
1821 // Fill render_priv->text_info.
1822 static int parse_events(ASS_Renderer *render_priv, ASS_Event *event)
1824 TextInfo *text_info = &render_priv->text_info;
1825 ASS_Drawing *drawing = NULL;
1834 // get next char, executing style override
1835 // this affects render_context
1838 if ((*p == '{') && (q = strchr(p, '}'))) {
1840 p = parse_tag(render_priv, p, q, 1.);
1843 } else if (render_priv->state.drawing_scale) {
1847 while ((*q != '{') && (*q != 0))
1850 drawing = ass_drawing_new(render_priv->library,
1851 render_priv->ftlibrary);
1855 ass_drawing_set_text(drawing, p, q - p);
1856 code = 0xfffc; // object replacement character
1860 code = get_next_char(render_priv, &p);
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);
1875 if (text_info->length >= text_info->max_glyphs) {
1876 // Raise maximum number of glyphs
1877 text_info->max_glyphs *= 2;
1879 realloc(text_info->glyphs,
1880 sizeof(GlyphInfo) * text_info->max_glyphs);
1883 GlyphInfo *info = &text_info->glyphs[text_info->length];
1885 // Clear current GlyphInfo
1886 memset(info, 0, sizeof(GlyphInfo));
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;
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)
1908 mult_alpha(_a(clr), render_priv->state.fade), 1.);
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;
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;
1937 fix_glyph_scaling(render_priv, info);
1939 text_info->length++;
1941 render_priv->state.effect_type = EF_NONE;
1942 render_priv->state.effect_timing = 0;
1943 render_priv->state.effect_skip_timing = 0;
1946 ass_drawing_free(drawing);
1951 // Process render_priv->text_info and load glyph outlines.
1952 static void retrieve_glyphs(ASS_Renderer *render_priv)
1954 GlyphInfo *glyphs = render_priv->text_info.glyphs;
1957 for (i = 0; i < render_priv->text_info.length; i++) {
1958 GlyphInfo *info = glyphs + i;
1960 get_outline_glyph(render_priv, info);
1965 // Add additional space after italic to non-italic style changes
1966 if (i && glyphs[i - 1].italic && !info->italic) {
1968 GlyphInfo *og = &glyphs[back];
1969 while (back && og->bbox.xMax - og->bbox.xMin == 0
1971 og = &glyphs[--back];
1972 if (og->bbox.xMax > og->cluster_advance.x)
1973 og->cluster_advance.x = og->bbox.xMax;
1976 // add horizontal letter spacing
1977 info->cluster_advance.x += double_to_d6(info->hspacing *
1978 render_priv->font_scale * info->orig_scale_x);
1980 // add displacement for vertical shearing
1981 info->cluster_advance.y += (info->fay / info->scale_x * info->scale_y) * info->cluster_advance.x;
1985 // Preliminary layout (for line wrapping)
1986 static void preliminary_layout(ASS_Renderer *render_priv)
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;
1997 info->pos.x = cluster_pen.x;
1998 info->pos.y = cluster_pen.y;
2000 cluster_pen.x += info->advance.x;
2001 cluster_pen.y += info->advance.y;
2004 info->hash_key.type = BITMAP_OUTLINE;
2005 fill_bitmap_hash(render_priv, info, &info->hash_key.u.outline);
2009 info = render_priv->text_info.glyphs + i;
2010 pen.x += info->cluster_advance.x;
2011 pen.y += info->cluster_advance.y;
2015 // Reorder text into visual order
2016 static void reorder_text(ASS_Renderer *render_priv)
2018 TextInfo *text_info = &render_priv->text_info;
2022 FriBidiStrIndex *cmap = ass_shaper_reorder(render_priv->shaper, text_info);
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);
2030 // Reposition according to the map
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);
2046 else if (last_fay != info->fay) {
2047 pen.y -= (last_fay / info->scale_x * info->scale_y) * (pen.x - last_pen_x);
2050 last_fay = info->fay;
2051 if (info->skip) continue;
2052 FT_Vector cluster_pen = pen;
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;
2060 info = text_info->glyphs + cmap[i];
2061 pen.x += info->cluster_advance.x;
2062 pen.y += info->cluster_advance.y;
2066 static void align_lines(ASS_Renderer *render_priv, double max_text_width)
2068 TextInfo *text_info = &render_priv->text_info;
2069 GlyphInfo *glyphs = text_info->glyphs;
2072 int last_break = -1;
2073 int halign = render_priv->state.alignment & 3;
2075 if (render_priv->state.evt_type == EVENT_HSCROLL)
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) {
2081 if (halign == HALIGN_LEFT) { // left aligned, no action
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;
2088 for (j = last_break + 1; j < i; ++j) {
2089 GlyphInfo *info = glyphs + j;
2091 info->pos.x += double_to_d6(shift);
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);
2105 static void calculate_rotation_params(ASS_Renderer *render_priv, DBBox *bbox,
2106 double device_x, double device_y)
2108 TextInfo *text_info = &render_priv->text_info;
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);
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;
2122 for (i = 0; i < text_info->length; ++i) {
2123 GlyphInfo *info = text_info->glyphs + i;
2125 OutlineBitmapHashKey *key = &info->hash_key.u.outline;
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));
2140 static inline void rectangle_reset(Rectangle *rect)
2142 rect->x_min = rect->y_min = INT_MAX;
2143 rect->x_max = rect->y_max = INT_MIN;
2146 static inline void rectangle_combine(Rectangle *rect, const Bitmap *bm, int x, int y)
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);
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)
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;
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;
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);
2180 if(linebreak || is_new_bm_run(info, last_info)) {
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))
2187 text_info->max_bitmaps = new_size;
2188 combined_info = text_info->combined_bitmaps;
2190 current_info = &combined_info[nb_bitmaps];
2192 memcpy(¤t_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;
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;
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);
2214 current_info->x = current_info->y = INT_MAX;
2215 rectangle_reset(¤t_info->rect);
2216 rectangle_reset(¤t_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;
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)
2224 current_info->max_bitmap_count = MAX_SUB_BITMAPS_INITIAL;
2230 if (!info->image || !current_info)
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))
2237 current_info->max_bitmap_count = new_size;
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;
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(¤t_info->rect, info->image->bm, x, y);
2248 ++current_info->n_bm;
2250 if (info->image->bm_o) {
2251 rectangle_combine(¤t_info->rect_o, info->image->bm_o, x, y);
2252 ++current_info->n_bm_o;
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;
2266 fill_composite_hash(&hk, info);
2268 hv = ass_cache_get(render_priv->cache.composite_cache, &hk);
2271 info->bm_o = hv->bm_o;
2272 info->bm_s = hv->bm_s;
2273 free(info->bitmaps);
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)
2282 info->bm = copy_bitmap(render_priv->engine, info->bitmaps[j].image->bm);
2284 info->bm->left += info->bitmaps[j].x;
2285 info->bm->top += info->bitmaps[j].y;
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;
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;
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,
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)
2316 info->bm_o = copy_bitmap(render_priv->engine, info->bitmaps[j].image->bm_o);
2318 info->bm_o->left += info->bitmaps[j].x;
2319 info->bm_o->top += info->bitmaps[j].y;
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;
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;
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,
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);
2354 CompositeHashValue chv;
2356 chv.bm_o = info->bm_o;
2357 chv.bm_s = info->bm_s;
2359 ass_cache_put(render_priv->cache.composite_cache, &hk, &chv);
2362 text_info->n_bitmaps = nb_bitmaps;
2365 static void add_background(ASS_Renderer *render_priv, EventImages *event_images)
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);
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,
2377 render_priv->state.c[3]);
2379 img->next = event_images->imgs;
2380 event_images->imgs = img;
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.
2392 ass_render_event(ASS_Renderer *render_priv, ASS_Event *event,
2393 EventImages *event_images)
2396 int MarginL, MarginR, MarginV;
2398 double device_x = 0;
2399 double device_y = 0;
2400 TextInfo *text_info = &render_priv->text_info;
2402 if (event->Style >= render_priv->track->n_styles) {
2403 ass_msg(render_priv->library, MSGL_WARN, "No style found");
2407 ass_msg(render_priv->library, MSGL_WARN, "Empty event");
2411 free_render_context(render_priv);
2412 init_render_context(render_priv, event);
2414 if (parse_events(render_priv, event))
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);
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,
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);
2434 retrieve_glyphs(render_priv);
2436 preliminary_layout(render_priv);
2438 // depends on glyph x coordinates being monotonous, so it should be done before line wrap
2439 process_karaoke_effects(render_priv);
2441 valign = render_priv->state.alignment & 12;
2444 (event->MarginL) ? event->MarginL : render_priv->state.style->MarginL;
2446 (event->MarginR) ? event->MarginR : render_priv->state.style->MarginR;
2448 (event->MarginV) ? event->MarginV : render_priv->state.style->MarginV;
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);
2456 if (render_priv->state.evt_type != EVENT_HSCROLL) {
2457 // rearrange text in several lines
2458 wrap_lines_smart(render_priv, max_text_width);
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);
2467 reorder_text(render_priv);
2469 align_lines(render_priv, max_text_width);
2471 // determing text bounding box
2472 compute_string_bbox(text_info, &bbox);
2474 // determine device coordinates for text
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)
2484 render_priv->track->PlayResX -
2485 render_priv->state.scroll_shift);
2486 else if (render_priv->state.scroll_direction == SCROLL_LR)
2489 render_priv->state.scroll_shift) - (bbox.xMax -
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
2498 y2scr_top(render_priv,
2499 MarginV) + text_info->lines[0].asc;
2500 } else if (valign == VALIGN_CENTER) { // midtitle
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)");
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) {
2526 } else if (render_priv->state.evt_type == EVENT_VSCROLL) {
2527 if (render_priv->state.scroll_direction == SCROLL_TB)
2530 render_priv->state.clip_y0 +
2531 render_priv->state.scroll_shift) - (bbox.yMax -
2533 else if (render_priv->state.scroll_direction == SCROLL_BT)
2536 render_priv->state.clip_y1 -
2537 render_priv->state.scroll_shift);
2540 // positioned events are totally different
2541 if (render_priv->state.evt_type == EVENT_POSITIONED) {
2544 get_base_point(&bbox, render_priv->state.alignment, &base_x, &base_y);
2546 x2scr_pos(render_priv, render_priv->state.pos_x) - base_x;
2548 y2scr_pos(render_priv, render_priv->state.pos_y) - base_y;
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);
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);
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);
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;
2599 calculate_rotation_params(render_priv, &bbox, device_x, device_y);
2601 render_and_combine_glyphs(render_priv, device_x, device_y);
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);
2615 if (render_priv->state.border_style == 4)
2616 add_background(render_priv, event_images);
2618 ass_shaper_cleanup(render_priv->shaper, text_info);
2619 free_render_context(render_priv);
2625 * \brief deallocate image list
2626 * \param img list pointer
2628 void ass_free_images(ASS_Image *img)
2631 ASS_Image *next = img->next;
2638 * \brief Check cache limits and reset cache if they are exceeded
2640 static void check_cache_limits(ASS_Renderer *priv, CacheStore *cache)
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;
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;
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;
2663 * \brief Start a new frame
2666 ass_start_frame(ASS_Renderer *render_priv, ASS_Track *track,
2669 ASS_Settings *settings_priv = &render_priv->settings;
2671 if (!render_priv->settings.frame_width
2672 && !render_priv->settings.frame_height)
2673 return 1; // library not initialized
2675 if (!render_priv->fontselect)
2678 if (render_priv->library != track->library)
2681 free_list_clear(render_priv);
2683 if (track->n_events == 0)
2684 return 1; // nothing to do
2686 render_priv->track = track;
2687 render_priv->time = now;
2689 ass_lazy_track_init(render_priv->library, render_priv->track);
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);
2696 double par = render_priv->settings.par;
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;
2708 render_priv->font_scale_x = par;
2710 render_priv->prev_images_root = render_priv->images_root;
2711 render_priv->images_root = 0;
2713 check_cache_limits(render_priv, &render_priv->cache);
2718 static int cmp_event_layer(const void *p1, const void *p2)
2720 ASS_Event *e1 = ((EventImages *) p1)->event;
2721 ASS_Event *e2 = ((EventImages *) p2)->event;
2722 if (e1->Layer < e2->Layer)
2724 if (e1->Layer > e2->Layer)
2726 if (e1->ReadOrder < e2->ReadOrder)
2728 if (e1->ReadOrder > e2->ReadOrder)
2733 static ASS_RenderPriv *get_render_priv(ASS_Renderer *render_priv,
2736 if (!event->render_priv) {
2737 event->render_priv = calloc(1, sizeof(ASS_RenderPriv));
2738 if (!event->render_priv)
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;
2746 return event->render_priv;
2749 static int overlap(Segment *s1, Segment *s2)
2751 if (s1->a >= s2->b || s2->a >= s1->b ||
2752 s1->ha >= s2->hb || s2->ha >= s1->hb)
2757 static int cmp_segment(const void *p1, const void *p2)
2759 return ((Segment *) p1)->a - ((Segment *) p2)->a;
2763 shift_event(ASS_Renderer *render_priv, EventImages *ei, int shift)
2765 ASS_Image *cur = ei->imgs;
2767 cur->dst_y += shift;
2768 // clip top and bottom
2769 if (cur->dst_y < 0) {
2770 int clip = -cur->dst_y;
2772 cur->bitmap += clip * cur->stride;
2775 if (cur->dst_y + cur->h >= render_priv->height) {
2776 int clip = cur->dst_y + cur->h - render_priv->height;
2788 // dir: 1 - move down
2790 static int fit_segment(Segment *s, Segment *fixed, int *cnt, int dir)
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)
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)
2806 shift = fixed[i].a - s->b;
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;
2814 qsort(fixed, *cnt, sizeof(Segment), cmp_segment);
2820 fix_collisions(ASS_Renderer *render_priv, EventImages *imgs, int cnt)
2822 Segment *used = ass_realloc_array(NULL, cnt, sizeof(*used));
2829 // fill used[] with fixed events
2830 for (i = 0; i < cnt; ++i) {
2831 ASS_RenderPriv *priv;
2832 if (!imgs[i].detect_collisions)
2834 priv = get_render_priv(render_priv, imgs[i].event);
2835 if (priv && priv->height > 0) { // it's a fixed event
2838 s.b = priv->top + priv->height;
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");
2849 for (j = 0; j < cnt_used; ++j)
2850 if (overlap(&s, used + j)) { // no, it's not
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;
2862 shift_event(render_priv, imgs + i, priv->top - imgs[i].top);
2866 qsort(used, cnt_used, sizeof(Segment), cmp_segment);
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)
2873 priv = get_render_priv(render_priv, imgs[i].event);
2874 if (priv && priv->height == 0) { // not a fixed event
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);
2883 shift_event(render_priv, imgs + i, shift);
2885 priv->top = imgs[i].top;
2886 priv->height = imgs[i].height;
2887 priv->left = imgs[i].left;
2888 priv->width = imgs[i].width;
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
2902 static int ass_image_compare(ASS_Image *i1, ASS_Image *i2)
2908 if (i1->stride != i2->stride)
2910 if (i1->color != i2->color)
2912 if (i1->bitmap != i2->bitmap)
2914 if (i1->dst_x != i2->dst_x)
2916 if (i1->dst_y != i2->dst_y)
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
2926 static int ass_detect_change(ASS_Renderer *priv)
2928 ASS_Image *img, *img2;
2931 if (priv->cache_cleared || priv->state.has_clips)
2934 img = priv->prev_images_root;
2935 img2 = priv->images_root;
2937 while (img && diff < 2) {
2938 ASS_Image *next, *next2;
2941 int d = ass_image_compare(img, img2);
2946 // previous list is shorter
2954 // is the previous list longer?
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.
2970 ASS_Image *ass_render_frame(ASS_Renderer *priv, ASS_Track *track,
2971 long long now, int *detect_change)
2978 rc = ass_start_frame(priv, track, now);
2980 if (detect_change) {
2986 // render events separately
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;
2996 priv->eimg_size * sizeof(EventImages));
2998 rc = ass_render_event(priv, event, priv->eimg + cnt);
3005 qsort(priv->eimg, cnt, sizeof(EventImages), cmp_event_layer);
3007 // call fix_collisions for each group of events with the same layer
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;
3015 fix_collisions(priv, last, priv->eimg + cnt - last);
3018 tail = &priv->images_root;
3019 for (i = 0; i < cnt; ++i) {
3020 ASS_Image *cur = priv->eimg[i].imgs;
3029 *detect_change = ass_detect_change(priv);
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;
3036 return priv->images_root;