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.
24 #include "ass_render.h"
25 #include "ass_parse.h"
26 #include "ass_shaper.h"
28 #define MAX_GLYPHS_INITIAL 1024
29 #define MAX_LINES_INITIAL 64
30 #define SUBPIXEL_MASK 63
31 #define SUBPIXEL_ACCURACY 7
33 ASS_Renderer
*ass_renderer_init(ASS_Library
*library
)
37 ASS_Renderer
*priv
= 0;
38 int vmajor
, vminor
, vpatch
;
40 error
= FT_Init_FreeType(&ft
);
42 ass_msg(library
, MSGL_FATAL
, "%s failed", "FT_Init_FreeType");
46 FT_Library_Version(ft
, &vmajor
, &vminor
, &vpatch
);
47 ass_msg(library
, MSGL_V
, "Raster: FreeType %d.%d.%d",
48 vmajor
, vminor
, vpatch
);
50 priv
= calloc(1, sizeof(ASS_Renderer
));
56 priv
->synth_priv
= ass_synth_init(BLUR_MAX_RADIUS
);
58 priv
->library
= library
;
60 // images_root and related stuff is zero-filled in calloc
62 priv
->cache
.font_cache
= ass_font_cache_create();
63 priv
->cache
.bitmap_cache
= ass_bitmap_cache_create();
64 priv
->cache
.composite_cache
= ass_composite_cache_create();
65 priv
->cache
.outline_cache
= ass_outline_cache_create();
66 priv
->cache
.glyph_max
= GLYPH_CACHE_MAX
;
67 priv
->cache
.bitmap_max_size
= BITMAP_CACHE_MAX_SIZE
;
69 priv
->text_info
.max_glyphs
= MAX_GLYPHS_INITIAL
;
70 priv
->text_info
.max_lines
= MAX_LINES_INITIAL
;
71 priv
->text_info
.glyphs
= calloc(MAX_GLYPHS_INITIAL
, sizeof(GlyphInfo
));
72 priv
->text_info
.lines
= calloc(MAX_LINES_INITIAL
, sizeof(LineInfo
));
74 priv
->settings
.font_size_coeff
= 1.;
76 priv
->shaper
= ass_shaper_new(0);
77 ass_shaper_info(library
);
78 #ifdef CONFIG_HARFBUZZ
79 priv
->settings
.shaper
= ASS_SHAPING_COMPLEX
;
81 priv
->settings
.shaper
= ASS_SHAPING_SIMPLE
;
86 ass_msg(library
, MSGL_V
, "Initialized");
88 ass_msg(library
, MSGL_ERR
, "Initialization failed");
93 static void free_list_clear(ASS_Renderer
*render_priv
)
95 if (render_priv
->free_head
) {
96 FreeList
*item
= render_priv
->free_head
;
103 render_priv
->free_head
= NULL
;
107 void ass_renderer_done(ASS_Renderer
*render_priv
)
109 ass_cache_done(render_priv
->cache
.font_cache
);
110 ass_cache_done(render_priv
->cache
.bitmap_cache
);
111 ass_cache_done(render_priv
->cache
.composite_cache
);
112 ass_cache_done(render_priv
->cache
.outline_cache
);
114 ass_free_images(render_priv
->images_root
);
115 ass_free_images(render_priv
->prev_images_root
);
117 if (render_priv
->state
.stroker
) {
118 FT_Stroker_Done(render_priv
->state
.stroker
);
119 render_priv
->state
.stroker
= 0;
121 if (render_priv
->ftlibrary
)
122 FT_Done_FreeType(render_priv
->ftlibrary
);
123 if (render_priv
->fontconfig_priv
)
124 fontconfig_done(render_priv
->fontconfig_priv
);
125 if (render_priv
->synth_priv
)
126 ass_synth_done(render_priv
->synth_priv
);
127 ass_shaper_free(render_priv
->shaper
);
128 free(render_priv
->eimg
);
129 free(render_priv
->text_info
.glyphs
);
130 free(render_priv
->text_info
.lines
);
132 free(render_priv
->settings
.default_font
);
133 free(render_priv
->settings
.default_family
);
135 free_list_clear(render_priv
);
140 * \brief Create a new ASS_Image
141 * Parameters are the same as ASS_Image fields.
143 static ASS_Image
*my_draw_bitmap(unsigned char *bitmap
, int bitmap_w
,
144 int bitmap_h
, int stride
, int dst_x
,
145 int dst_y
, uint32_t color
)
147 ASS_Image
*img
= malloc(sizeof(ASS_Image
));
152 img
->stride
= stride
;
153 img
->bitmap
= bitmap
;
163 * \brief Mapping between script and screen coordinates
165 static double x2scr(ASS_Renderer
*render_priv
, double x
)
167 return x
* render_priv
->orig_width_nocrop
/ render_priv
->font_scale_x
/
168 render_priv
->track
->PlayResX
+
169 FFMAX(render_priv
->settings
.left_margin
, 0);
171 static double x2scr_pos(ASS_Renderer
*render_priv
, double x
)
173 return x
* render_priv
->orig_width
/ render_priv
->font_scale_x
/ render_priv
->track
->PlayResX
+
174 render_priv
->settings
.left_margin
;
176 static double x2scr_scaled(ASS_Renderer
*render_priv
, double x
)
178 return x
* render_priv
->orig_width_nocrop
/
179 render_priv
->track
->PlayResX
+
180 FFMAX(render_priv
->settings
.left_margin
, 0);
182 static double x2scr_pos_scaled(ASS_Renderer
*render_priv
, double x
)
184 return x
* render_priv
->orig_width
/ render_priv
->track
->PlayResX
+
185 render_priv
->settings
.left_margin
;
188 * \brief Mapping between script and screen coordinates
190 static double y2scr(ASS_Renderer
*render_priv
, double y
)
192 return y
* render_priv
->orig_height_nocrop
/
193 render_priv
->track
->PlayResY
+
194 FFMAX(render_priv
->settings
.top_margin
, 0);
196 static double y2scr_pos(ASS_Renderer
*render_priv
, double y
)
198 return y
* render_priv
->orig_height
/ render_priv
->track
->PlayResY
+
199 render_priv
->settings
.top_margin
;
202 // the same for toptitles
203 static double y2scr_top(ASS_Renderer
*render_priv
, double y
)
205 if (render_priv
->settings
.use_margins
)
206 return y
* render_priv
->orig_height_nocrop
/
207 render_priv
->track
->PlayResY
;
209 return y
* render_priv
->orig_height_nocrop
/
210 render_priv
->track
->PlayResY
+
211 FFMAX(render_priv
->settings
.top_margin
, 0);
213 // the same for subtitles
214 static double y2scr_sub(ASS_Renderer
*render_priv
, double y
)
216 if (render_priv
->settings
.use_margins
)
217 return y
* render_priv
->orig_height_nocrop
/
218 render_priv
->track
->PlayResY
+
219 FFMAX(render_priv
->settings
.top_margin
, 0)
220 + FFMAX(render_priv
->settings
.bottom_margin
, 0);
222 return y
* render_priv
->orig_height_nocrop
/
223 render_priv
->track
->PlayResY
+
224 FFMAX(render_priv
->settings
.top_margin
, 0);
228 * \brief Convert bitmap glyphs into ASS_Image list with inverse clipping
230 * Inverse clipping with the following strategy:
231 * - find rectangle from (x0, y0) to (cx0, y1)
232 * - find rectangle from (cx0, y0) to (cx1, cy0)
233 * - find rectangle from (cx0, cy1) to (cx1, y1)
234 * - find rectangle from (cx1, y0) to (x1, y1)
235 * These rectangles can be invalid and in this case are discarded.
236 * Afterwards, they are clipped against the screen coordinates.
237 * In an additional pass, the rectangles need to be split up left/right for
238 * karaoke effects. This can result in a lot of bitmaps (6 to be exact).
240 static ASS_Image
**render_glyph_i(ASS_Renderer
*render_priv
,
241 Bitmap
*bm
, int dst_x
, int dst_y
,
242 uint32_t color
, uint32_t color2
, int brk
,
243 ASS_Image
**tail
, unsigned int type
)
245 int i
, j
, x0
, y0
, x1
, y1
, cx0
, cy0
, cx1
, cy1
, sx
, sy
, zx
, zy
;
252 // we still need to clip against screen boundaries
253 zx
= x2scr_pos_scaled(render_priv
, 0);
254 zy
= y2scr_pos(render_priv
, 0);
255 sx
= x2scr_pos_scaled(render_priv
, render_priv
->track
->PlayResX
);
256 sy
= y2scr_pos(render_priv
, render_priv
->track
->PlayResY
);
262 cx0
= render_priv
->state
.clip_x0
- dst_x
;
263 cy0
= render_priv
->state
.clip_y0
- dst_y
;
264 cx1
= render_priv
->state
.clip_x1
- dst_x
;
265 cy1
= render_priv
->state
.clip_y1
- dst_y
;
267 // calculate rectangles and discard invalid ones while we're at it.
271 r
[i
].x1
= (cx0
> x1
) ? x1
: cx0
;
273 if (r
[i
].x1
> r
[i
].x0
&& r
[i
].y1
> r
[i
].y0
) i
++;
274 r
[i
].x0
= (cx0
< 0) ? x0
: cx0
;
276 r
[i
].x1
= (cx1
> x1
) ? x1
: cx1
;
277 r
[i
].y1
= (cy0
> y1
) ? y1
: cy0
;
278 if (r
[i
].x1
> r
[i
].x0
&& r
[i
].y1
> r
[i
].y0
) i
++;
279 r
[i
].x0
= (cx0
< 0) ? x0
: cx0
;
280 r
[i
].y0
= (cy1
< 0) ? y0
: cy1
;
281 r
[i
].x1
= (cx1
> x1
) ? x1
: cx1
;
283 if (r
[i
].x1
> r
[i
].x0
&& r
[i
].y1
> r
[i
].y0
) i
++;
284 r
[i
].x0
= (cx1
< 0) ? x0
: cx1
;
288 if (r
[i
].x1
> r
[i
].x0
&& r
[i
].y1
> r
[i
].y0
) i
++;
290 // clip each rectangle to screen coordinates
291 for (j
= 0; j
< i
; j
++) {
292 r
[j
].x0
= (r
[j
].x0
+ dst_x
< zx
) ? zx
- dst_x
: r
[j
].x0
;
293 r
[j
].y0
= (r
[j
].y0
+ dst_y
< zy
) ? zy
- dst_y
: r
[j
].y0
;
294 r
[j
].x1
= (r
[j
].x1
+ dst_x
> sx
) ? sx
- dst_x
: r
[j
].x1
;
295 r
[j
].y1
= (r
[j
].y1
+ dst_y
> sy
) ? sy
- dst_y
: r
[j
].y1
;
298 // draw the rectangles
299 for (j
= 0; j
< i
; j
++) {
301 // kick out rectangles that are invalid now
302 if (r
[j
].x1
<= r
[j
].x0
|| r
[j
].y1
<= r
[j
].y0
)
304 // split up into left and right for karaoke, if needed
305 if (lbrk
> r
[j
].x0
) {
306 if (lbrk
> r
[j
].x1
) lbrk
= r
[j
].x1
;
307 img
= my_draw_bitmap(bm
->buffer
+ r
[j
].y0
* bm
->stride
+ r
[j
].x0
,
308 lbrk
- r
[j
].x0
, r
[j
].y1
- r
[j
].y0
,
309 bm
->stride
, dst_x
+ r
[j
].x0
, dst_y
+ r
[j
].y0
, color
);
315 if (lbrk
< r
[j
].x1
) {
316 if (lbrk
< r
[j
].x0
) lbrk
= r
[j
].x0
;
317 img
= my_draw_bitmap(bm
->buffer
+ r
[j
].y0
* bm
->stride
+ lbrk
,
318 r
[j
].x1
- lbrk
, r
[j
].y1
- r
[j
].y0
,
319 bm
->stride
, dst_x
+ lbrk
, dst_y
+ r
[j
].y0
, color2
);
331 * \brief convert bitmap glyph into ASS_Image struct(s)
332 * \param bit freetype bitmap glyph, FT_PIXEL_MODE_GRAY
333 * \param dst_x bitmap x coordinate in video frame
334 * \param dst_y bitmap y coordinate in video frame
335 * \param color first color, RGBA
336 * \param color2 second color, RGBA
337 * \param brk x coordinate relative to glyph origin, color is used to the left of brk, color2 - to the right
338 * \param tail pointer to the last image's next field, head of the generated list should be stored here
339 * \return pointer to the new list tail
340 * Performs clipping. Uses my_draw_bitmap for actual bitmap convertion.
343 render_glyph(ASS_Renderer
*render_priv
, Bitmap
*bm
, int dst_x
, int dst_y
,
344 uint32_t color
, uint32_t color2
, int brk
, ASS_Image
**tail
, unsigned int type
)
346 // Inverse clipping in use?
347 if (render_priv
->state
.clip_mode
)
348 return render_glyph_i(render_priv
, bm
, dst_x
, dst_y
, color
, color2
,
351 // brk is relative to dst_x
352 // color = color left of brk
353 // color2 = color right of brk
354 int b_x0
, b_y0
, b_x1
, b_y1
; // visible part of the bitmap
355 int clip_x0
, clip_y0
, clip_x1
, clip_y1
;
364 clip_x0
= FFMINMAX(render_priv
->state
.clip_x0
, 0, render_priv
->width
);
365 clip_y0
= FFMINMAX(render_priv
->state
.clip_y0
, 0, render_priv
->height
);
366 clip_x1
= FFMINMAX(render_priv
->state
.clip_x1
, 0, render_priv
->width
);
367 clip_y1
= FFMINMAX(render_priv
->state
.clip_y1
, 0, render_priv
->height
);
373 tmp
= dst_x
- clip_x0
;
375 ass_msg(render_priv
->library
, MSGL_DBG2
, "clip left");
378 tmp
= dst_y
- clip_y0
;
380 ass_msg(render_priv
->library
, MSGL_DBG2
, "clip top");
383 tmp
= clip_x1
- dst_x
- bm
->w
;
385 ass_msg(render_priv
->library
, MSGL_DBG2
, "clip right");
388 tmp
= clip_y1
- dst_y
- bm
->h
;
390 ass_msg(render_priv
->library
, MSGL_DBG2
, "clip bottom");
394 if ((b_y0
>= b_y1
) || (b_x0
>= b_x1
))
397 if (brk
> b_x0
) { // draw left part
400 img
= my_draw_bitmap(bm
->buffer
+ bm
->stride
* b_y0
+ b_x0
,
401 brk
- b_x0
, b_y1
- b_y0
, bm
->stride
,
402 dst_x
+ b_x0
, dst_y
+ b_y0
, color
);
403 if (!img
) return tail
;
408 if (brk
< b_x1
) { // draw right part
411 img
= my_draw_bitmap(bm
->buffer
+ bm
->stride
* b_y0
+ brk
,
412 b_x1
- brk
, b_y1
- b_y0
, bm
->stride
,
413 dst_x
+ brk
, dst_y
+ b_y0
, color2
);
414 if (!img
) return tail
;
423 * \brief Replace the bitmap buffer in ASS_Image with a copy
424 * \param img ASS_Image to operate on
425 * \return pointer to old bitmap buffer
427 static unsigned char *clone_bitmap_buffer(ASS_Image
*img
)
429 unsigned char *old_bitmap
= img
->bitmap
;
430 int size
= img
->stride
* (img
->h
- 1) + img
->w
;
431 img
->bitmap
= malloc(size
);
432 memcpy(img
->bitmap
, old_bitmap
, size
);
437 * \brief Calculate overlapping area of two consecutive bitmaps and in case they
438 * overlap, blend them together
439 * Mainly useful for translucent glyphs and especially borders, to avoid the
440 * luminance adding up where they overlap (which looks ugly)
443 render_overlap(ASS_Renderer
*render_priv
, ASS_Image
**last_tail
,
446 int left
, top
, bottom
, right
;
447 int old_left
, old_top
, w
, h
, cur_left
, cur_top
;
448 int x
, y
, opos
, cpos
;
451 CompositeHashValue
*hv
;
452 CompositeHashValue chv
;
453 int ax
= (*last_tail
)->dst_x
;
454 int ay
= (*last_tail
)->dst_y
;
455 int aw
= (*last_tail
)->w
;
456 int as
= (*last_tail
)->stride
;
457 int ah
= (*last_tail
)->h
;
458 int bx
= (*tail
)->dst_x
;
459 int by
= (*tail
)->dst_y
;
461 int bs
= (*tail
)->stride
;
466 if ((*last_tail
)->bitmap
== (*tail
)->bitmap
)
469 if ((*last_tail
)->color
!= (*tail
)->color
)
472 // Calculate overlap coordinates
473 left
= (ax
> bx
) ? ax
: bx
;
474 top
= (ay
> by
) ? ay
: by
;
475 right
= ((ax
+ aw
) < (bx
+ bw
)) ? (ax
+ aw
) : (bx
+ bw
);
476 bottom
= ((ay
+ ah
) < (by
+ bh
)) ? (ay
+ ah
) : (by
+ bh
);
477 if ((right
<= left
) || (bottom
<= top
))
479 old_left
= left
- ax
;
483 cur_left
= left
- bx
;
487 hk
.a
= (*last_tail
)->bitmap
;
488 hk
.b
= (*tail
)->bitmap
;
499 hv
= ass_cache_get(render_priv
->cache
.composite_cache
, &hk
);
501 (*last_tail
)->bitmap
= hv
->a
;
502 (*tail
)->bitmap
= hv
->b
;
505 // Allocate new bitmaps and copy over data
506 a
= clone_bitmap_buffer(*last_tail
);
507 b
= clone_bitmap_buffer(*tail
);
509 // Blend overlapping area
510 for (y
= 0; y
< h
; y
++)
511 for (x
= 0; x
< w
; x
++) {
512 opos
= (old_top
+ y
) * (as
) + (old_left
+ x
);
513 cpos
= (cur_top
+ y
) * (bs
) + (cur_left
+ x
);
514 m
= FFMIN(a
[opos
] + b
[cpos
], 0xff);
515 (*last_tail
)->bitmap
[opos
] = 0;
516 (*tail
)->bitmap
[cpos
] = m
;
519 // Insert bitmaps into the cache
520 chv
.a
= (*last_tail
)->bitmap
;
521 chv
.b
= (*tail
)->bitmap
;
522 ass_cache_put(render_priv
->cache
.composite_cache
, &hk
, &chv
);
525 static void free_list_add(ASS_Renderer
*render_priv
, void *object
)
527 if (!render_priv
->free_head
) {
528 render_priv
->free_head
= calloc(1, sizeof(FreeList
));
529 render_priv
->free_head
->object
= object
;
530 render_priv
->free_tail
= render_priv
->free_head
;
532 FreeList
*l
= calloc(1, sizeof(FreeList
));
534 render_priv
->free_tail
->next
= l
;
535 render_priv
->free_tail
= render_priv
->free_tail
->next
;
540 * Iterate through a list of bitmaps and blend with clip vector, if
541 * applicable. The blended bitmaps are added to a free list which is freed
542 * at the start of a new frame.
544 static void blend_vector_clip(ASS_Renderer
*render_priv
,
548 Bitmap
*clip_bm
= NULL
;
550 ASS_Drawing
*drawing
= render_priv
->state
.clip_drawing
;
552 BitmapHashValue
*val
;
557 // Try to get mask from cache
558 memset(&key
, 0, sizeof(key
));
559 key
.type
= BITMAP_CLIP
;
560 key
.u
.clip
.text
= drawing
->text
;
561 val
= ass_cache_get(render_priv
->cache
.bitmap_cache
, &key
);
568 // Not found in cache, parse and rasterize it
569 outline
= ass_drawing_parse(drawing
, 1);
571 ass_msg(render_priv
->library
, MSGL_WARN
,
572 "Clip vector parsing failed. Skipping.");
573 goto blend_vector_error
;
576 // We need to translate the clip according to screen borders
577 if (render_priv
->settings
.left_margin
!= 0 ||
578 render_priv
->settings
.top_margin
!= 0) {
580 .x
= int_to_d6(render_priv
->settings
.left_margin
),
581 .y
= -int_to_d6(render_priv
->settings
.top_margin
),
583 FT_Outline_Translate(outline
, trans
.x
, trans
.y
);
586 ass_msg(render_priv
->library
, MSGL_DBG2
,
587 "Parsed vector clip: scales (%f, %f) string [%s]\n",
588 drawing
->scale_x
, drawing
->scale_y
, drawing
->text
);
590 clip_bm
= outline_to_bitmap(render_priv
->library
,
591 render_priv
->ftlibrary
, outline
, 0);
594 memset(&v
, 0, sizeof(v
));
595 key
.u
.clip
.text
= strdup(drawing
->text
);
597 ass_cache_put(render_priv
->cache
.bitmap_cache
, &key
, &v
);
601 if (!clip_bm
) goto blend_vector_exit
;
603 // Iterate through bitmaps and blend/clip them
604 for (cur
= head
; cur
; cur
= cur
->next
) {
605 int left
, top
, right
, bottom
, apos
, bpos
, y
, x
, w
, h
;
606 int ax
, ay
, aw
, ah
, as
;
607 int bx
, by
, bw
, bh
, bs
;
608 int aleft
, atop
, bleft
, btop
;
609 unsigned char *abuffer
, *bbuffer
, *nbuffer
;
611 abuffer
= cur
->bitmap
;
612 bbuffer
= clip_bm
->buffer
;
622 bs
= clip_bm
->stride
;
624 // Calculate overlap coordinates
625 left
= (ax
> bx
) ? ax
: bx
;
626 top
= (ay
> by
) ? ay
: by
;
627 right
= ((ax
+ aw
) < (bx
+ bw
)) ? (ax
+ aw
) : (bx
+ bw
);
628 bottom
= ((ay
+ ah
) < (by
+ bh
)) ? (ay
+ ah
) : (by
+ bh
);
636 if (render_priv
->state
.clip_drawing_mode
) {
638 if (ax
+ aw
< bx
|| ay
+ ah
< by
|| ax
> bx
+ bw
||
643 // Allocate new buffer and add to free list
644 nbuffer
= malloc(as
* ah
);
645 if (!nbuffer
) goto blend_vector_exit
;
646 free_list_add(render_priv
, nbuffer
);
649 memcpy(nbuffer
, abuffer
, as
* (ah
- 1) + aw
);
650 for (y
= 0; y
< h
; y
++)
651 for (x
= 0; x
< w
; x
++) {
652 apos
= (atop
+ y
) * as
+ aleft
+ x
;
653 bpos
= (btop
+ y
) * bs
+ bleft
+ x
;
654 nbuffer
[apos
] = FFMAX(0, abuffer
[apos
] - bbuffer
[bpos
]);
658 if (ax
+ aw
< bx
|| ay
+ ah
< by
|| ax
> bx
+ bw
||
664 // Allocate new buffer and add to free list
665 nbuffer
= calloc(as
, ah
);
666 if (!nbuffer
) goto blend_vector_exit
;
667 free_list_add(render_priv
, nbuffer
);
670 for (y
= 0; y
< h
; y
++)
671 for (x
= 0; x
< w
; x
++) {
672 apos
= (atop
+ y
) * as
+ aleft
+ x
;
673 bpos
= (btop
+ y
) * bs
+ bleft
+ x
;
674 nbuffer
[apos
] = (abuffer
[apos
] * bbuffer
[bpos
] + 255) >> 8;
677 cur
->bitmap
= nbuffer
;
681 ass_drawing_free(render_priv
->state
.clip_drawing
);
682 render_priv
->state
.clip_drawing
= 0;
686 * \brief Convert TextInfo struct to ASS_Image list
687 * Splits glyphs in halves when needed (for \kf karaoke).
689 static ASS_Image
*render_text(ASS_Renderer
*render_priv
, int dst_x
, int dst_y
)
695 ASS_Image
**tail
= &head
;
696 ASS_Image
**last_tail
= 0;
697 ASS_Image
**here_tail
= 0;
698 TextInfo
*text_info
= &render_priv
->text_info
;
700 for (i
= 0; i
< text_info
->length
; ++i
) {
701 GlyphInfo
*info
= text_info
->glyphs
+ i
;
702 if ((info
->symbol
== 0) || (info
->symbol
== '\n') || !info
->bm_s
703 || (info
->shadow_x
== 0 && info
->shadow_y
== 0) || info
->skip
)
713 dst_x
+ (info
->pos
.x
>> 6) +
714 (int) (info
->shadow_x
* render_priv
->border_scale
);
716 dst_y
+ (info
->pos
.y
>> 6) +
717 (int) (info
->shadow_y
* render_priv
->border_scale
);
722 render_glyph(render_priv
, bm
, pen_x
, pen_y
, info
->c
[3], 0,
723 1000000, tail
, IMAGE_TYPE_SHADOW
);
725 if (last_tail
&& tail
!= here_tail
&& ((info
->c
[3] & 0xff) > 0))
726 render_overlap(render_priv
, last_tail
, here_tail
);
727 last_tail
= here_tail
;
734 for (i
= 0; i
< text_info
->length
; ++i
) {
735 GlyphInfo
*info
= text_info
->glyphs
+ i
;
736 if ((info
->symbol
== 0) || (info
->symbol
== '\n') || !info
->bm_o
746 pen_x
= dst_x
+ (info
->pos
.x
>> 6);
747 pen_y
= dst_y
+ (info
->pos
.y
>> 6);
750 if ((info
->effect_type
== EF_KARAOKE_KO
)
751 && (info
->effect_timing
<= (info
->bbox
.xMax
>> 6))) {
756 render_glyph(render_priv
, bm
, pen_x
, pen_y
, info
->c
[2],
757 0, 1000000, tail
, IMAGE_TYPE_OUTLINE
);
758 if (last_tail
&& tail
!= here_tail
&& ((info
->c
[2] & 0xff) > 0))
759 render_overlap(render_priv
, last_tail
, here_tail
);
761 last_tail
= here_tail
;
767 for (i
= 0; i
< text_info
->length
; ++i
) {
768 GlyphInfo
*info
= text_info
->glyphs
+ i
;
769 if ((info
->symbol
== 0) || (info
->symbol
== '\n') || !info
->bm
779 pen_x
= dst_x
+ (info
->pos
.x
>> 6);
780 pen_y
= dst_y
+ (info
->pos
.y
>> 6);
783 if ((info
->effect_type
== EF_KARAOKE
)
784 || (info
->effect_type
== EF_KARAOKE_KO
)) {
785 if (info
->effect_timing
> (info
->bbox
.xMax
>> 6))
787 render_glyph(render_priv
, bm
, pen_x
, pen_y
,
788 info
->c
[0], 0, 1000000, tail
, IMAGE_TYPE_CHARACTER
);
791 render_glyph(render_priv
, bm
, pen_x
, pen_y
,
792 info
->c
[1], 0, 1000000, tail
, IMAGE_TYPE_CHARACTER
);
793 } else if (info
->effect_type
== EF_KARAOKE_KF
) {
795 render_glyph(render_priv
, bm
, pen_x
, pen_y
, info
->c
[0],
796 info
->c
[1], info
->effect_timing
, tail
, IMAGE_TYPE_CHARACTER
);
799 render_glyph(render_priv
, bm
, pen_x
, pen_y
, info
->c
[0],
800 0, 1000000, tail
, IMAGE_TYPE_CHARACTER
);
806 blend_vector_clip(render_priv
, head
);
811 static void compute_string_bbox(TextInfo
*text
, DBBox
*bbox
)
815 if (text
->length
> 0) {
818 bbox
->yMin
= -1 * text
->lines
[0].asc
+ d6_to_double(text
->glyphs
[0].pos
.y
);
819 bbox
->yMax
= text
->height
- text
->lines
[0].asc
+
820 d6_to_double(text
->glyphs
[0].pos
.y
);
822 for (i
= 0; i
< text
->length
; ++i
) {
823 GlyphInfo
*info
= text
->glyphs
+ i
;
824 if (info
->skip
) continue;
826 double s
= d6_to_double(info
->pos
.x
);
827 double e
= s
+ d6_to_double(info
->advance
.x
);
828 bbox
->xMin
= FFMIN(bbox
->xMin
, s
);
829 bbox
->xMax
= FFMAX(bbox
->xMax
, e
);
834 bbox
->xMin
= bbox
->xMax
= bbox
->yMin
= bbox
->yMax
= 0.;
838 * \brief partially reset render_context to style values
839 * Works like {\r}: resets some style overrides
841 void reset_render_context(ASS_Renderer
*render_priv
, ASS_Style
*style
)
844 style
= render_priv
->state
.style
;
846 render_priv
->state
.c
[0] = style
->PrimaryColour
;
847 render_priv
->state
.c
[1] = style
->SecondaryColour
;
848 render_priv
->state
.c
[2] = style
->OutlineColour
;
849 render_priv
->state
.c
[3] = style
->BackColour
;
850 render_priv
->state
.flags
=
851 (style
->Underline
? DECO_UNDERLINE
: 0) |
852 (style
->StrikeOut
? DECO_STRIKETHROUGH
: 0);
853 render_priv
->state
.font_size
= style
->FontSize
;
855 free(render_priv
->state
.family
);
856 render_priv
->state
.family
= NULL
;
857 render_priv
->state
.family
= strdup(style
->FontName
);
858 render_priv
->state
.treat_family_as_pattern
=
859 style
->treat_fontname_as_pattern
;
860 render_priv
->state
.bold
= style
->Bold
;
861 render_priv
->state
.italic
= style
->Italic
;
862 update_font(render_priv
);
864 render_priv
->state
.border_style
= style
->BorderStyle
;
865 calc_border(render_priv
, style
->Outline
, style
->Outline
);
866 change_border(render_priv
, render_priv
->state
.border_x
, render_priv
->state
.border_y
);
867 render_priv
->state
.scale_x
= style
->ScaleX
;
868 render_priv
->state
.scale_y
= style
->ScaleY
;
869 render_priv
->state
.hspacing
= style
->Spacing
;
870 render_priv
->state
.be
= 0;
871 render_priv
->state
.blur
= style
->Blur
;
872 render_priv
->state
.shadow_x
= style
->Shadow
;
873 render_priv
->state
.shadow_y
= style
->Shadow
;
874 render_priv
->state
.frx
= render_priv
->state
.fry
= 0.;
875 render_priv
->state
.frz
= M_PI
* style
->Angle
/ 180.;
876 render_priv
->state
.fax
= render_priv
->state
.fay
= 0.;
877 render_priv
->state
.wrap_style
= render_priv
->track
->WrapStyle
;
878 render_priv
->state
.font_encoding
= style
->Encoding
;
882 * \brief Start new event. Reset render_priv->state.
885 init_render_context(ASS_Renderer
*render_priv
, ASS_Event
*event
)
887 render_priv
->state
.event
= event
;
888 render_priv
->state
.style
= render_priv
->track
->styles
+ event
->Style
;
889 render_priv
->state
.parsed_tags
= 0;
891 reset_render_context(render_priv
, render_priv
->state
.style
);
893 render_priv
->state
.evt_type
= EVENT_NORMAL
;
894 render_priv
->state
.alignment
= render_priv
->state
.style
->Alignment
;
895 render_priv
->state
.pos_x
= 0;
896 render_priv
->state
.pos_y
= 0;
897 render_priv
->state
.org_x
= 0;
898 render_priv
->state
.org_y
= 0;
899 render_priv
->state
.have_origin
= 0;
900 render_priv
->state
.clip_x0
= 0;
901 render_priv
->state
.clip_y0
= 0;
902 render_priv
->state
.clip_x1
= render_priv
->track
->PlayResX
;
903 render_priv
->state
.clip_y1
= render_priv
->track
->PlayResY
;
904 render_priv
->state
.clip_mode
= 0;
905 render_priv
->state
.detect_collisions
= 1;
906 render_priv
->state
.fade
= 0;
907 render_priv
->state
.drawing_mode
= 0;
908 render_priv
->state
.effect_type
= EF_NONE
;
909 render_priv
->state
.effect_timing
= 0;
910 render_priv
->state
.effect_skip_timing
= 0;
911 render_priv
->state
.bm_run_id
= 0;
912 ass_drawing_free(render_priv
->state
.drawing
);
913 render_priv
->state
.drawing
= ass_drawing_new(render_priv
->library
,
914 render_priv
->ftlibrary
);
916 apply_transition_effects(render_priv
, event
);
919 static void free_render_context(ASS_Renderer
*render_priv
)
921 free(render_priv
->state
.family
);
922 ass_drawing_free(render_priv
->state
.drawing
);
924 render_priv
->state
.family
= NULL
;
925 render_priv
->state
.drawing
= NULL
;
929 * Replace the outline of a glyph by a contour which makes up a simple
932 static void draw_opaque_box(ASS_Renderer
*render_priv
, GlyphInfo
*info
,
933 int asc
, int desc
, FT_Outline
*ol
,
934 FT_Vector advance
, int sx
, int sy
)
938 double scale_y
= info
->scale_y
;
939 double scale_x
= info
->scale_x
;
945 // Emulate the WTFish behavior of VSFilter, i.e. double-scale
946 // the sizes of the opaque box.
947 adv
+= double_to_d6(info
->hspacing
* render_priv
->font_scale
* scale_x
);
952 desc
+= asc
* (scale_y
- 1.0);
954 FT_Vector points
[4] = {
955 { .x
= -sx
, .y
= asc
+ sy
},
956 { .x
= adv
+ sx
, .y
= asc
+ sy
},
957 { .x
= adv
+ sx
, .y
= -desc
- sy
},
958 { .x
= -sx
, .y
= -desc
- sy
},
961 FT_Outline_New(render_priv
->ftlibrary
, 4, 1, ol
);
963 ol
->n_points
= ol
->n_contours
= 0;
964 for (i
= 0; i
< 4; i
++) {
965 ol
->points
[ol
->n_points
] = points
[i
];
966 ol
->tags
[ol
->n_points
++] = 1;
968 ol
->contours
[ol
->n_contours
++] = ol
->n_points
- 1;
972 * Stroke an outline glyph in x/y direction. Applies various fixups to get
973 * around limitations of the FreeType stroker.
975 static void stroke_outline(ASS_Renderer
*render_priv
, FT_Outline
*outline
,
978 if (sx
<= 0 && sy
<= 0)
981 fix_freetype_stroker(outline
, sx
, sy
);
983 // Borders are equal; use the regular stroker
984 if (sx
== sy
&& render_priv
->state
.stroker
) {
986 unsigned n_points
, n_contours
;
988 FT_StrokerBorder border
= FT_Outline_GetOutsideBorder(outline
);
989 error
= FT_Stroker_ParseOutline(render_priv
->state
.stroker
, outline
, 0);
991 ass_msg(render_priv
->library
, MSGL_WARN
,
992 "FT_Stroker_ParseOutline failed, error: %d", error
);
994 error
= FT_Stroker_GetBorderCounts(render_priv
->state
.stroker
, border
,
995 &n_points
, &n_contours
);
997 ass_msg(render_priv
->library
, MSGL_WARN
,
998 "FT_Stroker_GetBorderCounts failed, error: %d", error
);
1000 FT_Outline_Done(render_priv
->ftlibrary
, outline
);
1001 FT_Outline_New(render_priv
->ftlibrary
, n_points
, n_contours
, outline
);
1002 outline
->n_points
= outline
->n_contours
= 0;
1003 FT_Stroker_ExportBorder(render_priv
->state
.stroker
, border
, outline
);
1005 // "Stroke" with the outline emboldener in two passes.
1006 // The outlines look uglier, but the emboldening never adds any points
1011 FT_Outline_New(render_priv
->ftlibrary
, outline
->n_points
,
1012 outline
->n_contours
, &nol
);
1013 FT_Outline_Copy(outline
, &nol
);
1015 FT_Outline_Embolden(outline
, sx
* 2);
1016 FT_Outline_Translate(outline
, -sx
, -sx
);
1017 FT_Outline_Embolden(&nol
, sy
* 2);
1018 FT_Outline_Translate(&nol
, -sy
, -sy
);
1020 for (i
= 0; i
< outline
->n_points
; i
++)
1021 outline
->points
[i
].y
= nol
.points
[i
].y
;
1023 FT_Outline_Done(render_priv
->ftlibrary
, &nol
);
1028 * \brief Prepare glyph hash
1031 fill_glyph_hash(ASS_Renderer
*priv
, OutlineHashKey
*outline_key
,
1034 if (info
->drawing
) {
1035 DrawingHashKey
*key
= &outline_key
->u
.drawing
;
1036 outline_key
->type
= OUTLINE_DRAWING
;
1037 key
->scale_x
= double_to_d16(info
->scale_x
);
1038 key
->scale_y
= double_to_d16(info
->scale_y
);
1039 key
->outline
.x
= double_to_d16(info
->border_x
);
1040 key
->outline
.y
= double_to_d16(info
->border_y
);
1041 key
->border_style
= info
->border_style
;
1042 key
->hash
= info
->drawing
->hash
;
1043 key
->text
= info
->drawing
->text
;
1044 key
->pbo
= info
->drawing
->pbo
;
1045 key
->scale
= info
->drawing
->scale
;
1047 GlyphHashKey
*key
= &outline_key
->u
.glyph
;
1048 outline_key
->type
= OUTLINE_GLYPH
;
1049 key
->font
= info
->font
;
1050 key
->size
= info
->font_size
;
1051 key
->face_index
= info
->face_index
;
1052 key
->glyph_index
= info
->glyph_index
;
1053 key
->bold
= info
->bold
;
1054 key
->italic
= info
->italic
;
1055 key
->scale_x
= double_to_d16(info
->scale_x
);
1056 key
->scale_y
= double_to_d16(info
->scale_y
);
1057 key
->outline
.x
= double_to_d16(info
->border_x
);
1058 key
->outline
.y
= double_to_d16(info
->border_y
);
1059 key
->flags
= info
->flags
;
1060 key
->border_style
= info
->border_style
;
1065 * \brief Get normal and outline (border) glyphs
1066 * \param info out: struct filled with extracted data
1067 * Tries to get both glyphs from cache.
1068 * If they can't be found, gets a glyph from font face, generates outline with FT_Stroker,
1069 * and add them to cache.
1070 * The glyphs are returned in info->glyph and info->outline_glyph
1073 get_outline_glyph(ASS_Renderer
*priv
, GlyphInfo
*info
)
1075 OutlineHashValue
*val
;
1078 memset(&info
->hash_key
, 0, sizeof(key
));
1080 fill_glyph_hash(priv
, &key
, info
);
1081 val
= ass_cache_get(priv
->cache
.outline_cache
, &key
);
1085 memset(&v
, 0, sizeof(v
));
1087 if (info
->drawing
) {
1088 ASS_Drawing
*drawing
= info
->drawing
;
1089 ass_drawing_hash(drawing
);
1090 if(!ass_drawing_parse(drawing
, 0))
1092 outline_copy(priv
->ftlibrary
, &drawing
->outline
,
1094 v
.advance
.x
= drawing
->advance
.x
;
1095 v
.advance
.y
= drawing
->advance
.y
;
1096 v
.asc
= drawing
->asc
;
1097 v
.desc
= drawing
->desc
;
1098 key
.u
.drawing
.text
= strdup(drawing
->text
);
1100 // arbitrary, not too small to prevent grid fitting rounding effects
1101 // XXX: this is a rather crude hack
1102 const double ft_size
= 256.0;
1103 ass_face_set_size(info
->font
->faces
[info
->face_index
], ft_size
);
1104 ass_font_set_transform(info
->font
,
1105 info
->scale_x
* info
->font_size
/ ft_size
,
1106 info
->scale_y
* info
->font_size
/ ft_size
,
1109 ass_font_get_glyph(priv
->fontconfig_priv
, info
->font
,
1110 info
->symbol
, info
->face_index
, info
->glyph_index
,
1111 priv
->settings
.hinting
, info
->flags
);
1112 if (glyph
!= NULL
) {
1113 outline_copy(priv
->ftlibrary
,
1114 &((FT_OutlineGlyph
)glyph
)->outline
, &v
.outline
);
1115 if (priv
->settings
.shaper
== ASS_SHAPING_SIMPLE
) {
1116 v
.advance
.x
= d16_to_d6(glyph
->advance
.x
);
1117 v
.advance
.y
= d16_to_d6(glyph
->advance
.y
);
1119 FT_Done_Glyph(glyph
);
1120 ass_font_get_asc_desc(info
->font
, info
->symbol
,
1122 v
.asc
*= info
->scale_y
* info
->font_size
/ ft_size
;
1123 v
.desc
*= info
->scale_y
* info
->font_size
/ ft_size
;
1130 FT_Outline_Get_CBox(v
.outline
, &v
.bbox_scaled
);
1132 if (info
->border_style
== 3) {
1135 v
.border
= calloc(1, sizeof(FT_Outline
));
1137 if (priv
->settings
.shaper
== ASS_SHAPING_SIMPLE
|| info
->drawing
)
1138 advance
= v
.advance
;
1140 advance
= info
->advance
;
1142 draw_opaque_box(priv
, info
, v
.asc
, v
.desc
, v
.border
, advance
,
1143 double_to_d6(info
->border_x
* priv
->border_scale
),
1144 double_to_d6(info
->border_y
* priv
->border_scale
));
1146 } else if ((info
->border_x
> 0 || info
->border_y
> 0)
1147 && double_to_d6(info
->scale_x
) && double_to_d6(info
->scale_y
)) {
1149 change_border(priv
, info
->border_x
, info
->border_y
);
1150 outline_copy(priv
->ftlibrary
, v
.outline
, &v
.border
);
1151 stroke_outline(priv
, v
.border
,
1152 double_to_d6(info
->border_x
* priv
->border_scale
),
1153 double_to_d6(info
->border_y
* priv
->border_scale
));
1156 v
.lib
= priv
->ftlibrary
;
1157 val
= ass_cache_put(priv
->cache
.outline_cache
, &key
, &v
);
1160 info
->hash_key
.u
.outline
.outline
= val
;
1161 info
->outline
= val
->outline
;
1162 info
->border
= val
->border
;
1163 info
->bbox
= val
->bbox_scaled
;
1164 if (info
->drawing
|| priv
->settings
.shaper
== ASS_SHAPING_SIMPLE
) {
1165 info
->cluster_advance
.x
= info
->advance
.x
= val
->advance
.x
;
1166 info
->cluster_advance
.y
= info
->advance
.y
= val
->advance
.y
;
1168 info
->asc
= val
->asc
;
1169 info
->desc
= val
->desc
;
1171 ass_drawing_free(info
->drawing
);
1175 * \brief Apply transformation to outline points of a glyph
1176 * Applies rotations given by frx, fry and frz and projects the points back
1177 * onto the screen plane.
1180 transform_3d_points(FT_Vector shift
, FT_Outline
*outline
, double frx
, double fry
,
1181 double frz
, double fax
, double fay
, double scale
,
1184 double sx
= sin(frx
);
1185 double sy
= sin(fry
);
1186 double sz
= sin(frz
);
1187 double cx
= cos(frx
);
1188 double cy
= cos(fry
);
1189 double cz
= cos(frz
);
1190 FT_Vector
*p
= outline
->points
;
1191 double x
, y
, z
, xx
, yy
, zz
;
1194 dist
= 20000 * scale
;
1195 for (i
= 0; i
< outline
->n_points
; i
++) {
1196 x
= (double) p
[i
].x
+ shift
.x
+ (fax
* (yshift
- p
[i
].y
));
1197 y
= (double) p
[i
].y
+ shift
.y
+ (-fay
* p
[i
].x
);
1200 xx
= x
* cz
+ y
* sz
;
1201 yy
= -(x
* sz
- y
* cz
);
1205 y
= yy
* cx
+ zz
* sx
;
1206 z
= yy
* sx
- zz
* cx
;
1208 xx
= x
* cy
+ z
* sy
;
1210 zz
= x
* sy
- z
* cy
;
1212 zz
= FFMAX(zz
, 1000 - dist
);
1214 x
= (xx
* dist
) / (zz
+ dist
);
1215 y
= (yy
* dist
) / (zz
+ dist
);
1216 p
[i
].x
= x
- shift
.x
+ 0.5;
1217 p
[i
].y
= y
- shift
.y
+ 0.5;
1222 * \brief Apply 3d transformation to several objects
1223 * \param shift FreeType vector
1224 * \param glyph FreeType glyph
1225 * \param glyph2 FreeType glyph
1226 * \param frx x-axis rotation angle
1227 * \param fry y-axis rotation angle
1228 * \param frz z-axis rotation angle
1229 * Rotates both glyphs by frx, fry and frz. Shift vector is added before rotation and subtracted after it.
1232 transform_3d(FT_Vector shift
, FT_Outline
*outline
, FT_Outline
*border
,
1233 double frx
, double fry
, double frz
, double fax
, double fay
,
1234 double scale
, int yshift
)
1238 if (frx
!= 0. || fry
!= 0. || frz
!= 0. || fax
!= 0. || fay
!= 0.) {
1240 transform_3d_points(shift
, outline
, frx
, fry
, frz
,
1241 fax
, fay
, scale
, yshift
);
1244 transform_3d_points(shift
, border
, frx
, fry
, frz
,
1245 fax
, fay
, scale
, yshift
);
1250 * \brief Get bitmaps for a glyph
1251 * \param info glyph info
1252 * Tries to get glyph bitmaps from bitmap cache.
1253 * If they can't be found, they are generated by rotating and rendering the glyph.
1254 * After that, bitmaps are added to the cache.
1255 * They are returned in info->bm (glyph), info->bm_o (outline) and info->bm_s (shadow).
1258 get_bitmap_glyph(ASS_Renderer
*render_priv
, GlyphInfo
*info
)
1260 BitmapHashValue
*val
;
1261 OutlineBitmapHashKey
*key
= &info
->hash_key
.u
.outline
;
1263 if (!info
->outline
|| info
->symbol
== '\n' || info
->symbol
== 0 || info
->skip
)
1266 val
= ass_cache_get(render_priv
->cache
.bitmap_cache
, &info
->hash_key
);
1270 BitmapHashValue hash_val
;
1272 double fax_scaled
, fay_scaled
;
1273 FT_Outline
*outline
, *border
;
1274 double scale_x
= render_priv
->font_scale_x
;
1276 hash_val
.bm
= hash_val
.bm_o
= hash_val
.bm_s
= 0;
1278 outline_copy(render_priv
->ftlibrary
, info
->outline
, &outline
);
1279 outline_copy(render_priv
->ftlibrary
, info
->border
, &border
);
1281 // calculating rotation shift vector (from rotation origin to the glyph basepoint)
1282 shift
.x
= key
->shift_x
;
1283 shift
.y
= key
->shift_y
;
1284 fax_scaled
= info
->fax
/ info
->scale_y
* info
->scale_x
;
1285 fay_scaled
= info
->fay
/ info
->scale_x
* info
->scale_y
;
1288 transform_3d(shift
, outline
, border
,
1289 info
->frx
, info
->fry
, info
->frz
, fax_scaled
,
1290 fay_scaled
, render_priv
->font_scale
, info
->asc
);
1292 // PAR correction scaling
1293 FT_Matrix m
= { double_to_d16(scale_x
), 0,
1294 0, double_to_d16(1.0) };
1299 FT_Outline_Transform(outline
, &m
);
1300 FT_Outline_Translate(outline
, key
->advance
.x
, -key
->advance
.y
);
1304 FT_Outline_Transform(border
, &m
);
1305 FT_Outline_Translate(border
, key
->advance
.x
, -key
->advance
.y
);
1309 error
= outline_to_bitmap3(render_priv
->library
,
1310 render_priv
->synth_priv
,
1311 render_priv
->ftlibrary
,
1313 &hash_val
.bm
, &hash_val
.bm_o
,
1314 &hash_val
.bm_s
, info
->be
,
1315 info
->blur
* render_priv
->blur_scale
,
1318 info
->border_x
|| info
->border_y
);
1322 val
= ass_cache_put(render_priv
->cache
.bitmap_cache
, &info
->hash_key
,
1325 outline_free(render_priv
->ftlibrary
, outline
);
1326 outline_free(render_priv
->ftlibrary
, border
);
1330 info
->bm_o
= val
->bm_o
;
1331 info
->bm_s
= val
->bm_s
;
1333 // VSFilter compatibility: invisible fill and no border?
1334 // In this case no shadow is supposed to be rendered.
1335 if (!info
->border
&& (info
->c
[0] & 0xFF) == 0xFF)
1340 * This function goes through text_info and calculates text parameters.
1341 * The following text_info fields are filled:
1347 static void measure_text(ASS_Renderer
*render_priv
)
1349 TextInfo
*text_info
= &render_priv
->text_info
;
1351 double max_asc
= 0., max_desc
= 0.;
1352 GlyphInfo
*last
= NULL
;
1355 text_info
->height
= 0.;
1356 for (i
= 0; i
< text_info
->length
+ 1; ++i
) {
1357 if ((i
== text_info
->length
) || text_info
->glyphs
[i
].linebreak
) {
1358 if (empty_line
&& cur_line
> 0 && last
&& i
< text_info
->length
) {
1359 max_asc
= d6_to_double(last
->asc
) / 2.0;
1360 max_desc
= d6_to_double(last
->desc
) / 2.0;
1362 text_info
->lines
[cur_line
].asc
= max_asc
;
1363 text_info
->lines
[cur_line
].desc
= max_desc
;
1364 text_info
->height
+= max_asc
+ max_desc
;
1366 max_asc
= max_desc
= 0.;
1370 if (i
< text_info
->length
) {
1371 GlyphInfo
*cur
= text_info
->glyphs
+ i
;
1372 if (d6_to_double(cur
->asc
) > max_asc
)
1373 max_asc
= d6_to_double(cur
->asc
);
1374 if (d6_to_double(cur
->desc
) > max_desc
)
1375 max_desc
= d6_to_double(cur
->desc
);
1376 if (cur
->symbol
!= '\n' && cur
->symbol
!= 0)
1380 text_info
->height
+=
1381 (text_info
->n_lines
-
1382 1) * render_priv
->settings
.line_spacing
;
1386 * Mark extra whitespace for later removal.
1388 #define IS_WHITESPACE(x) ((x->symbol == ' ' || x->symbol == '\n') \
1390 static void trim_whitespace(ASS_Renderer
*render_priv
)
1394 TextInfo
*ti
= &render_priv
->text_info
;
1396 // Mark trailing spaces
1398 cur
= ti
->glyphs
+ i
;
1399 while (i
&& IS_WHITESPACE(cur
)) {
1401 cur
= ti
->glyphs
+ --i
;
1404 // Mark leading whitespace
1407 while (i
< ti
->length
&& IS_WHITESPACE(cur
)) {
1409 cur
= ti
->glyphs
+ ++i
;
1412 // Mark all extraneous whitespace inbetween
1413 for (i
= 0; i
< ti
->length
; ++i
) {
1414 cur
= ti
->glyphs
+ i
;
1415 if (cur
->linebreak
) {
1416 // Mark whitespace before
1418 cur
= ti
->glyphs
+ j
;
1419 while (j
&& IS_WHITESPACE(cur
)) {
1421 cur
= ti
->glyphs
+ --j
;
1423 // A break itself can contain a whitespace, too
1424 cur
= ti
->glyphs
+ i
;
1425 if (cur
->symbol
== ' ') {
1427 // Mark whitespace after
1429 cur
= ti
->glyphs
+ j
;
1430 while (j
< ti
->length
&& IS_WHITESPACE(cur
)) {
1432 cur
= ti
->glyphs
+ ++j
;
1439 #undef IS_WHITESPACE
1442 * \brief rearrange text between lines
1443 * \param max_text_width maximal text line width in pixels
1444 * The algo is similar to the one in libvo/sub.c:
1445 * 1. Place text, wrapping it when current line is full
1446 * 2. Try moving words from the end of a line to the beginning of the next one while it reduces
1447 * the difference in lengths between this two lines.
1448 * The result may not be optimal, but usually is good enough.
1450 * FIXME: implement style 0 and 3 correctly
1453 wrap_lines_smart(ASS_Renderer
*render_priv
, double max_text_width
)
1456 GlyphInfo
*cur
, *s1
, *e1
, *s2
, *s3
, *w
;
1464 TextInfo
*text_info
= &render_priv
->text_info
;
1467 text_info
->n_lines
= 1;
1469 s1
= text_info
->glyphs
; // current line start
1470 for (i
= 0; i
< text_info
->length
; ++i
) {
1472 double s_offset
, len
;
1473 cur
= text_info
->glyphs
+ i
;
1474 s_offset
= d6_to_double(s1
->bbox
.xMin
+ s1
->pos
.x
);
1475 len
= d6_to_double(cur
->bbox
.xMax
+ cur
->pos
.x
) - s_offset
;
1477 if (cur
->symbol
== '\n') {
1480 ass_msg(render_priv
->library
, MSGL_DBG2
,
1481 "forced line break at %d", break_at
);
1482 } else if (cur
->symbol
== ' ') {
1484 } else if (len
>= max_text_width
1485 && (render_priv
->state
.wrap_style
!= 2)) {
1487 break_at
= last_space
;
1489 ass_msg(render_priv
->library
, MSGL_DBG2
, "line break at %d",
1493 if (break_at
!= -1) {
1494 // need to use one more line
1495 // marking break_at+1 as start of a new line
1496 int lead
= break_at
+ 1; // the first symbol of the new line
1497 if (text_info
->n_lines
>= text_info
->max_lines
) {
1498 // Raise maximum number of lines
1499 text_info
->max_lines
*= 2;
1500 text_info
->lines
= realloc(text_info
->lines
,
1502 text_info
->max_lines
);
1504 if (lead
< text_info
->length
) {
1505 text_info
->glyphs
[lead
].linebreak
= break_type
;
1507 s1
= text_info
->glyphs
+ lead
;
1508 s_offset
= d6_to_double(s1
->bbox
.xMin
+ s1
->pos
.x
);
1509 text_info
->n_lines
++;
1513 #define DIFF(x,y) (((x) < (y)) ? (y - x) : (x - y))
1515 while (!exit
&& render_priv
->state
.wrap_style
!= 1) {
1517 w
= s3
= text_info
->glyphs
;
1519 for (i
= 0; i
<= text_info
->length
; ++i
) {
1520 cur
= text_info
->glyphs
+ i
;
1521 if ((i
== text_info
->length
) || cur
->linebreak
) {
1525 if (s1
&& (s2
->linebreak
== 1)) { // have at least 2 lines, and linebreak is 'soft'
1526 double l1
, l2
, l1_new
, l2_new
;
1531 } while ((w
> s1
) && (w
->symbol
== ' '));
1532 while ((w
> s1
) && (w
->symbol
!= ' ')) {
1536 while ((e1
> s1
) && (e1
->symbol
== ' ')) {
1539 if (w
->symbol
== ' ')
1542 l1
= d6_to_double(((s2
- 1)->bbox
.xMax
+ (s2
- 1)->pos
.x
) -
1543 (s1
->bbox
.xMin
+ s1
->pos
.x
));
1544 l2
= d6_to_double(((s3
- 1)->bbox
.xMax
+ (s3
- 1)->pos
.x
) -
1545 (s2
->bbox
.xMin
+ s2
->pos
.x
));
1546 l1_new
= d6_to_double(
1547 (e1
->bbox
.xMax
+ e1
->pos
.x
) -
1548 (s1
->bbox
.xMin
+ s1
->pos
.x
));
1549 l2_new
= d6_to_double(
1550 ((s3
- 1)->bbox
.xMax
+ (s3
- 1)->pos
.x
) -
1551 (w
->bbox
.xMin
+ w
->pos
.x
));
1553 if (DIFF(l1_new
, l2_new
) < DIFF(l1
, l2
)) {
1560 if (i
== text_info
->length
)
1565 assert(text_info
->n_lines
>= 1);
1568 measure_text(render_priv
);
1569 trim_whitespace(render_priv
);
1577 cur
= text_info
->glyphs
+ i
;
1578 while (i
< text_info
->length
&& cur
->skip
)
1579 cur
= text_info
->glyphs
+ ++i
;
1580 pen_shift_x
= d6_to_double(-cur
->pos
.x
);
1582 for (i
= 0; i
< text_info
->length
; ++i
) {
1583 cur
= text_info
->glyphs
+ i
;
1584 if (cur
->linebreak
) {
1585 while (i
< text_info
->length
&& cur
->skip
&& cur
->symbol
!= '\n')
1586 cur
= text_info
->glyphs
+ ++i
;
1588 text_info
->lines
[cur_line
- 1].desc
+
1589 text_info
->lines
[cur_line
].asc
;
1590 text_info
->lines
[cur_line
- 1].len
= i
-
1591 text_info
->lines
[cur_line
- 1].offset
;
1592 text_info
->lines
[cur_line
].offset
= i
;
1595 pen_shift_x
= d6_to_double(-cur
->pos
.x
);
1596 pen_shift_y
+= height
+ render_priv
->settings
.line_spacing
;
1597 ass_msg(render_priv
->library
, MSGL_DBG2
,
1598 "shifting from %d to %d by (%f, %f)", i
,
1599 text_info
->length
- 1, pen_shift_x
, pen_shift_y
);
1601 cur
->bm_run_id
+= run_offset
;
1602 cur
->pos
.x
+= double_to_d6(pen_shift_x
);
1603 cur
->pos
.y
+= double_to_d6(pen_shift_y
);
1605 text_info
->lines
[cur_line
- 1].len
=
1606 text_info
->length
- text_info
->lines
[cur_line
- 1].offset
;
1610 for (i
= 0; i
< text_info
->n_lines
; i
++) {
1611 printf("line %d offset %d length %d\n", i
, text_info
->lines
[i
].offset
,
1612 text_info
->lines
[i
].len
);
1618 * \brief Calculate base point for positioning and rotation
1619 * \param bbox text bbox
1620 * \param alignment alignment
1621 * \param bx, by out: base point coordinates
1623 static void get_base_point(DBBox
*bbox
, int alignment
, double *bx
, double *by
)
1625 const int halign
= alignment
& 3;
1626 const int valign
= alignment
& 12;
1633 *bx
= (bbox
->xMax
+ bbox
->xMin
) / 2.0;
1645 *by
= (bbox
->yMax
+ bbox
->yMin
) / 2.0;
1654 * Prepare bitmap hash key of a glyph
1657 fill_bitmap_hash(ASS_Renderer
*priv
, GlyphInfo
*info
,
1658 OutlineBitmapHashKey
*hash_key
)
1660 hash_key
->frx
= rot_key(info
->frx
);
1661 hash_key
->fry
= rot_key(info
->fry
);
1662 hash_key
->frz
= rot_key(info
->frz
);
1663 hash_key
->fax
= double_to_d16(info
->fax
);
1664 hash_key
->fay
= double_to_d16(info
->fay
);
1665 hash_key
->be
= info
->be
;
1666 hash_key
->blur
= info
->blur
;
1667 hash_key
->shadow_offset
.x
= double_to_d6(
1668 info
->shadow_x
* priv
->border_scale
-
1669 (int) (info
->shadow_x
* priv
->border_scale
));
1670 hash_key
->shadow_offset
.y
= double_to_d6(
1671 info
->shadow_y
* priv
->border_scale
-
1672 (int) (info
->shadow_y
* priv
->border_scale
));
1676 * \brief Main ass rendering function, glues everything together
1677 * \param event event to render
1678 * \param event_images struct containing resulting images, will also be initialized
1679 * Process event, appending resulting ASS_Image's to images_root.
1682 ass_render_event(ASS_Renderer
*render_priv
, ASS_Event
*event
,
1683 EventImages
*event_images
)
1692 int MarginL
, MarginR
, MarginV
;
1694 int alignment
, halign
, valign
;
1695 double device_x
= 0;
1696 double device_y
= 0;
1697 TextInfo
*text_info
= &render_priv
->text_info
;
1698 GlyphInfo
*glyphs
= render_priv
->text_info
.glyphs
;
1699 ASS_Drawing
*drawing
;
1701 if (event
->Style
>= render_priv
->track
->n_styles
) {
1702 ass_msg(render_priv
->library
, MSGL_WARN
, "No style found");
1706 ass_msg(render_priv
->library
, MSGL_WARN
, "Empty event");
1710 init_render_context(render_priv
, event
);
1712 drawing
= render_priv
->state
.drawing
;
1713 text_info
->length
= 0;
1721 // get next char, executing style override
1722 // this affects render_context
1725 if (!in_tag
&& *p
== '{') { // '\0' goes here
1730 int prev_drawing_mode
= render_priv
->state
.drawing_mode
;
1731 p
= parse_tag(render_priv
, p
, 1.);
1732 if (*p
== '}') { // end of tag
1735 } else if (*p
!= '\\') {
1736 ass_msg(render_priv
->library
, MSGL_V
,
1737 "Unable to parse: '%.30s'", p
);
1739 if (prev_drawing_mode
&& !render_priv
->state
.drawing_mode
) {
1740 // Drawing mode was just disabled. We must exit and draw it
1741 // immediately, instead of letting further tags affect it.
1746 code
= get_next_char(render_priv
, &p
);
1747 if (code
&& render_priv
->state
.drawing_mode
) {
1748 ass_drawing_add_char(drawing
, (char) code
);
1749 continue; // skip everything in drawing mode
1755 if (text_info
->length
>= text_info
->max_glyphs
) {
1756 // Raise maximum number of glyphs
1757 text_info
->max_glyphs
*= 2;
1758 text_info
->glyphs
= glyphs
=
1759 realloc(text_info
->glyphs
,
1760 sizeof(GlyphInfo
) * text_info
->max_glyphs
);
1763 // Clear current GlyphInfo
1764 memset(&glyphs
[text_info
->length
], 0, sizeof(GlyphInfo
));
1768 drawing
->scale_x
= render_priv
->state
.scale_x
*
1769 render_priv
->font_scale
;
1770 drawing
->scale_y
= render_priv
->state
.scale_y
*
1771 render_priv
->font_scale
;
1772 code
= 0xfffc; // object replacement character
1773 glyphs
[text_info
->length
].drawing
= drawing
;
1776 // face could have been changed in get_next_char
1777 if (!render_priv
->state
.font
) {
1778 free_render_context(render_priv
);
1785 // Fill glyph information
1786 glyphs
[text_info
->length
].symbol
= code
;
1787 glyphs
[text_info
->length
].font
= render_priv
->state
.font
;
1788 for (i
= 0; i
< 4; ++i
) {
1789 uint32_t clr
= render_priv
->state
.c
[i
];
1791 mult_alpha(_a(clr
), render_priv
->state
.fade
), 1.);
1792 glyphs
[text_info
->length
].c
[i
] = clr
;
1794 glyphs
[text_info
->length
].effect_type
= render_priv
->state
.effect_type
;
1795 glyphs
[text_info
->length
].effect_timing
=
1796 render_priv
->state
.effect_timing
;
1797 glyphs
[text_info
->length
].effect_skip_timing
=
1798 render_priv
->state
.effect_skip_timing
;
1799 glyphs
[text_info
->length
].font_size
=
1800 render_priv
->state
.font_size
* render_priv
->font_scale
;
1801 glyphs
[text_info
->length
].be
= render_priv
->state
.be
;
1802 glyphs
[text_info
->length
].blur
= render_priv
->state
.blur
;
1803 glyphs
[text_info
->length
].shadow_x
= render_priv
->state
.shadow_x
;
1804 glyphs
[text_info
->length
].shadow_y
= render_priv
->state
.shadow_y
;
1805 glyphs
[text_info
->length
].scale_x
= render_priv
->state
.scale_x
;
1806 glyphs
[text_info
->length
].scale_y
= render_priv
->state
.scale_y
;
1807 glyphs
[text_info
->length
].border_style
= render_priv
->state
.border_style
;
1808 glyphs
[text_info
->length
].border_x
= render_priv
->state
.border_x
;
1809 glyphs
[text_info
->length
].border_y
= render_priv
->state
.border_y
;
1810 glyphs
[text_info
->length
].hspacing
= render_priv
->state
.hspacing
;
1811 glyphs
[text_info
->length
].bold
= render_priv
->state
.bold
;
1812 glyphs
[text_info
->length
].italic
= render_priv
->state
.italic
;
1813 glyphs
[text_info
->length
].flags
= render_priv
->state
.flags
;
1814 glyphs
[text_info
->length
].frx
= render_priv
->state
.frx
;
1815 glyphs
[text_info
->length
].fry
= render_priv
->state
.fry
;
1816 glyphs
[text_info
->length
].frz
= render_priv
->state
.frz
;
1817 glyphs
[text_info
->length
].fax
= render_priv
->state
.fax
;
1818 glyphs
[text_info
->length
].fay
= render_priv
->state
.fay
;
1819 glyphs
[text_info
->length
].bm_run_id
= render_priv
->state
.bm_run_id
;
1821 if (glyphs
[text_info
->length
].drawing
) {
1822 drawing
= render_priv
->state
.drawing
=
1823 ass_drawing_new(render_priv
->library
, render_priv
->ftlibrary
);
1826 text_info
->length
++;
1828 render_priv
->state
.effect_type
= EF_NONE
;
1829 render_priv
->state
.effect_timing
= 0;
1830 render_priv
->state
.effect_skip_timing
= 0;
1834 if (text_info
->length
== 0) {
1835 // no valid symbols in the event; this can be smth like {comment}
1836 free_render_context(render_priv
);
1840 // Find shape runs and shape text
1841 ass_shaper_set_base_direction(render_priv
->shaper
,
1842 resolve_base_direction(render_priv
->state
.font_encoding
));
1843 ass_shaper_find_runs(render_priv
->shaper
, render_priv
, glyphs
,
1845 ass_shaper_shape(render_priv
->shaper
, text_info
);
1848 for (i
= 0; i
< text_info
->length
; i
++) {
1849 GlyphInfo
*info
= glyphs
+ i
;
1851 get_outline_glyph(render_priv
, info
);
1856 // Add additional space after italic to non-italic style changes
1857 if (i
&& glyphs
[i
- 1].italic
&& !info
->italic
) {
1859 GlyphInfo
*og
= &glyphs
[back
];
1860 while (back
&& og
->bbox
.xMax
- og
->bbox
.xMin
== 0
1862 og
= &glyphs
[--back
];
1863 if (og
->bbox
.xMax
> og
->cluster_advance
.x
)
1864 og
->cluster_advance
.x
= og
->bbox
.xMax
;
1867 // add horizontal letter spacing
1868 info
->cluster_advance
.x
+= double_to_d6(info
->hspacing
*
1869 render_priv
->font_scale
* info
->scale_x
);
1871 // add displacement for vertical shearing
1872 info
->cluster_advance
.y
+= (info
->fay
/ info
->scale_x
* info
->scale_y
) * info
->cluster_advance
.x
;
1876 // Preliminary layout (for line wrapping)
1880 for (i
= 0; i
< text_info
->length
; i
++) {
1881 GlyphInfo
*info
= glyphs
+ i
;
1882 FT_Vector cluster_pen
= pen
;
1884 info
->pos
.x
= cluster_pen
.x
;
1885 info
->pos
.y
= cluster_pen
.y
;
1887 cluster_pen
.x
+= info
->advance
.x
;
1888 cluster_pen
.y
+= info
->advance
.y
;
1891 info
->hash_key
.type
= BITMAP_OUTLINE
;
1892 fill_bitmap_hash(render_priv
, info
, &info
->hash_key
.u
.outline
);
1897 pen
.x
+= info
->cluster_advance
.x
;
1898 pen
.y
+= info
->cluster_advance
.y
;
1899 previous
= info
->symbol
;
1903 // depends on glyph x coordinates being monotonous, so it should be done before line wrap
1904 process_karaoke_effects(render_priv
);
1907 alignment
= render_priv
->state
.alignment
;
1908 halign
= alignment
& 3;
1909 valign
= alignment
& 12;
1912 (event
->MarginL
) ? event
->MarginL
: render_priv
->state
.style
->MarginL
;
1914 (event
->MarginR
) ? event
->MarginR
: render_priv
->state
.style
->MarginR
;
1916 (event
->MarginV
) ? event
->MarginV
: render_priv
->state
.style
->MarginV
;
1918 // calculate max length of a line
1919 double max_text_width
=
1920 x2scr(render_priv
, render_priv
->track
->PlayResX
- MarginR
) -
1921 x2scr(render_priv
, MarginL
);
1924 if (render_priv
->state
.evt_type
!= EVENT_HSCROLL
) {
1925 // rearrange text in several lines
1926 wrap_lines_smart(render_priv
, max_text_width
);
1928 // no breaking or wrapping, everything in a single line
1929 text_info
->lines
[0].offset
= 0;
1930 text_info
->lines
[0].len
= text_info
->length
;
1931 text_info
->n_lines
= 1;
1932 measure_text(render_priv
);
1935 // Reorder text into visual order
1936 FriBidiStrIndex
*cmap
= ass_shaper_reorder(render_priv
->shaper
, text_info
);
1938 // Reposition according to the map
1942 for (i
= 0; i
< text_info
->length
; i
++) {
1943 GlyphInfo
*info
= glyphs
+ cmap
[i
];
1944 if (glyphs
[i
].linebreak
) {
1945 pen
.y
-= (info
->fay
/ info
->scale_x
* info
->scale_y
) * pen
.x
;
1947 pen
.y
+= double_to_d6(text_info
->lines
[lineno
-1].desc
);
1948 pen
.y
+= double_to_d6(text_info
->lines
[lineno
].asc
);
1949 pen
.y
+= double_to_d6(render_priv
->settings
.line_spacing
);
1952 if (info
->skip
) continue;
1953 FT_Vector cluster_pen
= pen
;
1955 info
->pos
.x
= info
->offset
.x
+ cluster_pen
.x
;
1956 info
->pos
.y
= info
->offset
.y
+ cluster_pen
.y
;
1957 cluster_pen
.x
+= info
->advance
.x
;
1958 cluster_pen
.y
+= info
->advance
.y
;
1961 info
= glyphs
+ cmap
[i
];
1962 pen
.x
+= info
->cluster_advance
.x
;
1963 pen
.y
+= info
->cluster_advance
.y
;
1967 if (render_priv
->state
.evt_type
!= EVENT_HSCROLL
) {
1970 for (i
= 0; i
<= text_info
->length
; ++i
) { // (text_info->length + 1) is the end of the last line
1971 if ((i
== text_info
->length
) || glyphs
[i
].linebreak
) {
1972 // remove letter spacing (which is included in cluster_advance)
1974 width
-= render_priv
->state
.hspacing
* render_priv
->font_scale
*
1975 glyphs
[i
-1].scale_x
;
1977 if (halign
== HALIGN_LEFT
) { // left aligned, no action
1979 } else if (halign
== HALIGN_RIGHT
) { // right aligned
1980 shift
= max_text_width
- width
;
1981 } else if (halign
== HALIGN_CENTER
) { // centered
1982 shift
= (max_text_width
- width
) / 2.0;
1984 for (j
= last_break
+ 1; j
< i
; ++j
) {
1985 GlyphInfo
*info
= glyphs
+ j
;
1987 info
->pos
.x
+= double_to_d6(shift
);
1994 if (i
< text_info
->length
&& !glyphs
[i
].skip
&&
1995 glyphs
[i
].symbol
!= '\n' && glyphs
[i
].symbol
!= 0) {
1996 width
+= d6_to_double(glyphs
[i
].cluster_advance
.x
);
2001 // determing text bounding box
2002 compute_string_bbox(text_info
, &bbox
);
2004 // determine device coordinates for text
2006 // x coordinate for everything except positioned events
2007 if (render_priv
->state
.evt_type
== EVENT_NORMAL
||
2008 render_priv
->state
.evt_type
== EVENT_VSCROLL
) {
2009 device_x
= x2scr(render_priv
, MarginL
);
2010 } else if (render_priv
->state
.evt_type
== EVENT_HSCROLL
) {
2011 if (render_priv
->state
.scroll_direction
== SCROLL_RL
)
2014 render_priv
->track
->PlayResX
-
2015 render_priv
->state
.scroll_shift
);
2016 else if (render_priv
->state
.scroll_direction
== SCROLL_LR
)
2019 render_priv
->state
.scroll_shift
) - (bbox
.xMax
-
2023 // y coordinate for everything except positioned events
2024 if (render_priv
->state
.evt_type
== EVENT_NORMAL
||
2025 render_priv
->state
.evt_type
== EVENT_HSCROLL
) {
2026 if (valign
== VALIGN_TOP
) { // toptitle
2028 y2scr_top(render_priv
,
2029 MarginV
) + text_info
->lines
[0].asc
;
2030 } else if (valign
== VALIGN_CENTER
) { // midtitle
2032 y2scr(render_priv
, render_priv
->track
->PlayResY
/ 2.0);
2033 device_y
= scr_y
- (bbox
.yMax
+ bbox
.yMin
) / 2.0;
2034 } else { // subtitle
2035 double scr_top
, scr_bottom
, scr_y0
;
2036 if (valign
!= VALIGN_SUB
)
2037 ass_msg(render_priv
->library
, MSGL_V
,
2038 "Invalid valign, assuming 0 (subtitle)");
2040 y2scr_sub(render_priv
,
2041 render_priv
->track
->PlayResY
- MarginV
);
2042 scr_top
= y2scr_top(render_priv
, 0); //xxx not always 0?
2043 device_y
= scr_bottom
+ (scr_top
- scr_bottom
) *
2044 render_priv
->settings
.line_position
/ 100.0;
2045 device_y
-= text_info
->height
;
2046 device_y
+= text_info
->lines
[0].asc
;
2047 // clip to top to avoid confusion if line_position is very high,
2048 // turning the subtitle into a toptitle
2049 // also, don't change behavior if line_position is not used
2050 scr_y0
= scr_top
+ text_info
->lines
[0].asc
;
2051 if (device_y
< scr_y0
&& render_priv
->settings
.line_position
> 0) {
2055 } else if (render_priv
->state
.evt_type
== EVENT_VSCROLL
) {
2056 if (render_priv
->state
.scroll_direction
== SCROLL_TB
)
2059 render_priv
->state
.clip_y0
+
2060 render_priv
->state
.scroll_shift
) - (bbox
.yMax
-
2062 else if (render_priv
->state
.scroll_direction
== SCROLL_BT
)
2065 render_priv
->state
.clip_y1
-
2066 render_priv
->state
.scroll_shift
);
2069 // positioned events are totally different
2070 if (render_priv
->state
.evt_type
== EVENT_POSITIONED
) {
2073 ass_msg(render_priv
->library
, MSGL_DBG2
, "positioned event at %f, %f",
2074 render_priv
->state
.pos_x
, render_priv
->state
.pos_y
);
2075 get_base_point(&bbox
, alignment
, &base_x
, &base_y
);
2077 x2scr_pos(render_priv
, render_priv
->state
.pos_x
) - base_x
;
2079 y2scr_pos(render_priv
, render_priv
->state
.pos_y
) - base_y
;
2082 // fix clip coordinates (they depend on alignment)
2083 if (render_priv
->state
.evt_type
== EVENT_NORMAL
||
2084 render_priv
->state
.evt_type
== EVENT_HSCROLL
||
2085 render_priv
->state
.evt_type
== EVENT_VSCROLL
) {
2086 render_priv
->state
.clip_x0
=
2087 x2scr_scaled(render_priv
, render_priv
->state
.clip_x0
);
2088 render_priv
->state
.clip_x1
=
2089 x2scr_scaled(render_priv
, render_priv
->state
.clip_x1
);
2090 if (valign
== VALIGN_TOP
) {
2091 render_priv
->state
.clip_y0
=
2092 y2scr_top(render_priv
, render_priv
->state
.clip_y0
);
2093 render_priv
->state
.clip_y1
=
2094 y2scr_top(render_priv
, render_priv
->state
.clip_y1
);
2095 } else if (valign
== VALIGN_CENTER
) {
2096 render_priv
->state
.clip_y0
=
2097 y2scr(render_priv
, render_priv
->state
.clip_y0
);
2098 render_priv
->state
.clip_y1
=
2099 y2scr(render_priv
, render_priv
->state
.clip_y1
);
2100 } else if (valign
== VALIGN_SUB
) {
2101 render_priv
->state
.clip_y0
=
2102 y2scr_sub(render_priv
, render_priv
->state
.clip_y0
);
2103 render_priv
->state
.clip_y1
=
2104 y2scr_sub(render_priv
, render_priv
->state
.clip_y1
);
2106 } else if (render_priv
->state
.evt_type
== EVENT_POSITIONED
) {
2107 render_priv
->state
.clip_x0
=
2108 x2scr_pos_scaled(render_priv
, render_priv
->state
.clip_x0
);
2109 render_priv
->state
.clip_x1
=
2110 x2scr_pos_scaled(render_priv
, render_priv
->state
.clip_x1
);
2111 render_priv
->state
.clip_y0
=
2112 y2scr_pos(render_priv
, render_priv
->state
.clip_y0
);
2113 render_priv
->state
.clip_y1
=
2114 y2scr_pos(render_priv
, render_priv
->state
.clip_y1
);
2117 // calculate rotation parameters
2121 if (render_priv
->state
.have_origin
) {
2122 center
.x
= x2scr(render_priv
, render_priv
->state
.org_x
);
2123 center
.y
= y2scr(render_priv
, render_priv
->state
.org_y
);
2125 double bx
= 0., by
= 0.;
2126 get_base_point(&bbox
, alignment
, &bx
, &by
);
2127 center
.x
= device_x
+ bx
;
2128 center
.y
= device_y
+ by
;
2131 for (i
= 0; i
< text_info
->length
; ++i
) {
2132 GlyphInfo
*info
= glyphs
+ i
;
2134 OutlineBitmapHashKey
*key
= &info
->hash_key
.u
.outline
;
2136 if (key
->frx
|| key
->fry
|| key
->frz
|| key
->fax
|| key
->fay
) {
2137 key
->shift_x
= info
->pos
.x
+ double_to_d6(device_x
- center
.x
);
2138 key
->shift_y
= -(info
->pos
.y
+ double_to_d6(device_y
- center
.y
));
2148 // convert glyphs to bitmaps
2149 int left
= render_priv
->settings
.left_margin
;
2150 device_x
= (device_x
- left
) * render_priv
->font_scale_x
+ left
;
2151 for (i
= 0; i
< text_info
->length
; ++i
) {
2152 GlyphInfo
*info
= glyphs
+ i
;
2154 OutlineBitmapHashKey
*key
= &info
->hash_key
.u
.outline
;
2155 info
->pos
.x
*= render_priv
->font_scale_x
;
2157 double_to_d6(device_x
- (int) device_x
+
2158 d6_to_double(info
->pos
.x
& SUBPIXEL_MASK
)) & ~SUBPIXEL_ACCURACY
;
2160 double_to_d6(device_y
- (int) device_y
+
2161 d6_to_double(info
->pos
.y
& SUBPIXEL_MASK
)) & ~SUBPIXEL_ACCURACY
;
2162 get_bitmap_glyph(render_priv
, info
);
2167 memset(event_images
, 0, sizeof(*event_images
));
2168 event_images
->top
= device_y
- text_info
->lines
[0].asc
;
2169 event_images
->height
= text_info
->height
;
2170 event_images
->left
=
2171 (device_x
+ bbox
.xMin
* render_priv
->font_scale_x
) + 0.5;
2172 event_images
->width
=
2173 (bbox
.xMax
- bbox
.xMin
) * render_priv
->font_scale_x
+ 0.5;
2174 event_images
->detect_collisions
= render_priv
->state
.detect_collisions
;
2175 event_images
->shift_direction
= (valign
== VALIGN_TOP
) ? 1 : -1;
2176 event_images
->event
= event
;
2177 event_images
->imgs
= render_text(render_priv
, (int) device_x
, (int) device_y
);
2179 ass_shaper_cleanup(render_priv
->shaper
, text_info
);
2180 free_render_context(render_priv
);
2186 * \brief deallocate image list
2187 * \param img list pointer
2189 void ass_free_images(ASS_Image
*img
)
2192 ASS_Image
*next
= img
->next
;
2199 * \brief Check cache limits and reset cache if they are exceeded
2201 static void check_cache_limits(ASS_Renderer
*priv
, CacheStore
*cache
)
2203 if (ass_cache_empty(cache
->bitmap_cache
, cache
->bitmap_max_size
)) {
2204 ass_cache_empty(cache
->composite_cache
, 0);
2205 ass_free_images(priv
->prev_images_root
);
2206 priv
->prev_images_root
= 0;
2207 priv
->cache_cleared
= 1;
2209 if (ass_cache_empty(cache
->outline_cache
, cache
->glyph_max
)) {
2210 ass_cache_empty(cache
->bitmap_cache
, 0);
2211 ass_cache_empty(cache
->composite_cache
, 0);
2212 ass_free_images(priv
->prev_images_root
);
2213 priv
->prev_images_root
= 0;
2214 priv
->cache_cleared
= 1;
2219 * \brief Start a new frame
2222 ass_start_frame(ASS_Renderer
*render_priv
, ASS_Track
*track
,
2225 ASS_Settings
*settings_priv
= &render_priv
->settings
;
2227 if (!render_priv
->settings
.frame_width
2228 && !render_priv
->settings
.frame_height
)
2229 return 1; // library not initialized
2231 if (render_priv
->library
!= track
->library
)
2234 if (!render_priv
->fontconfig_priv
)
2237 free_list_clear(render_priv
);
2239 if (track
->n_events
== 0)
2240 return 1; // nothing to do
2242 render_priv
->track
= track
;
2243 render_priv
->time
= now
;
2245 ass_lazy_track_init(render_priv
->library
, render_priv
->track
);
2247 render_priv
->font_scale
= settings_priv
->font_size_coeff
*
2248 render_priv
->orig_height
/ render_priv
->track
->PlayResY
;
2249 if (render_priv
->storage_height
)
2250 render_priv
->blur_scale
= ((double) render_priv
->orig_height
) /
2251 render_priv
->storage_height
;
2253 render_priv
->blur_scale
= 1.;
2254 if (render_priv
->track
->ScaledBorderAndShadow
)
2255 render_priv
->border_scale
=
2256 ((double) render_priv
->orig_height
) /
2257 render_priv
->track
->PlayResY
;
2259 render_priv
->border_scale
= render_priv
->blur_scale
;
2260 render_priv
->border_scale
*= settings_priv
->font_size_coeff
;
2262 ass_shaper_set_kerning(render_priv
->shaper
, track
->Kerning
);
2263 ass_shaper_set_language(render_priv
->shaper
, track
->Language
);
2264 ass_shaper_set_level(render_priv
->shaper
, render_priv
->settings
.shaper
);
2267 double par
= render_priv
->settings
.par
;
2269 if (settings_priv
->frame_width
&& settings_priv
->frame_height
&&
2270 settings_priv
->storage_width
&& settings_priv
->storage_height
) {
2271 double dar
= ((double) settings_priv
->frame_width
) /
2272 settings_priv
->frame_height
;
2273 double sar
= ((double) settings_priv
->storage_width
) /
2274 settings_priv
->storage_height
;
2279 render_priv
->font_scale_x
= par
;
2281 render_priv
->prev_images_root
= render_priv
->images_root
;
2282 render_priv
->images_root
= 0;
2284 check_cache_limits(render_priv
, &render_priv
->cache
);
2289 static int cmp_event_layer(const void *p1
, const void *p2
)
2291 ASS_Event
*e1
= ((EventImages
*) p1
)->event
;
2292 ASS_Event
*e2
= ((EventImages
*) p2
)->event
;
2293 if (e1
->Layer
< e2
->Layer
)
2295 if (e1
->Layer
> e2
->Layer
)
2297 if (e1
->ReadOrder
< e2
->ReadOrder
)
2299 if (e1
->ReadOrder
> e2
->ReadOrder
)
2304 static ASS_RenderPriv
*get_render_priv(ASS_Renderer
*render_priv
,
2307 if (!event
->render_priv
)
2308 event
->render_priv
= calloc(1, sizeof(ASS_RenderPriv
));
2309 if (render_priv
->render_id
!= event
->render_priv
->render_id
) {
2310 memset(event
->render_priv
, 0, sizeof(ASS_RenderPriv
));
2311 event
->render_priv
->render_id
= render_priv
->render_id
;
2314 return event
->render_priv
;
2317 static int overlap(Segment
*s1
, Segment
*s2
)
2319 if (s1
->a
>= s2
->b
|| s2
->a
>= s1
->b
||
2320 s1
->ha
>= s2
->hb
|| s2
->ha
>= s1
->hb
)
2325 static int cmp_segment(const void *p1
, const void *p2
)
2327 return ((Segment
*) p1
)->a
- ((Segment
*) p2
)->a
;
2331 shift_event(ASS_Renderer
*render_priv
, EventImages
*ei
, int shift
)
2333 ASS_Image
*cur
= ei
->imgs
;
2335 cur
->dst_y
+= shift
;
2336 // clip top and bottom
2337 if (cur
->dst_y
< 0) {
2338 int clip
= -cur
->dst_y
;
2340 cur
->bitmap
+= clip
* cur
->stride
;
2343 if (cur
->dst_y
+ cur
->h
>= render_priv
->height
) {
2344 int clip
= cur
->dst_y
+ cur
->h
- render_priv
->height
;
2356 // dir: 1 - move down
2358 static int fit_segment(Segment
*s
, Segment
*fixed
, int *cnt
, int dir
)
2363 if (dir
== 1) // move down
2364 for (i
= 0; i
< *cnt
; ++i
) {
2365 if (s
->b
+ shift
<= fixed
[i
].a
|| s
->a
+ shift
>= fixed
[i
].b
||
2366 s
->hb
<= fixed
[i
].ha
|| s
->ha
>= fixed
[i
].hb
)
2368 shift
= fixed
[i
].b
- s
->a
;
2369 } else // dir == -1, move up
2370 for (i
= *cnt
- 1; i
>= 0; --i
) {
2371 if (s
->b
+ shift
<= fixed
[i
].a
|| s
->a
+ shift
>= fixed
[i
].b
||
2372 s
->hb
<= fixed
[i
].ha
|| s
->ha
>= fixed
[i
].hb
)
2374 shift
= fixed
[i
].a
- s
->b
;
2377 fixed
[*cnt
].a
= s
->a
+ shift
;
2378 fixed
[*cnt
].b
= s
->b
+ shift
;
2379 fixed
[*cnt
].ha
= s
->ha
;
2380 fixed
[*cnt
].hb
= s
->hb
;
2382 qsort(fixed
, *cnt
, sizeof(Segment
), cmp_segment
);
2388 fix_collisions(ASS_Renderer
*render_priv
, EventImages
*imgs
, int cnt
)
2390 Segment
*used
= malloc(cnt
* sizeof(*used
));
2394 // fill used[] with fixed events
2395 for (i
= 0; i
< cnt
; ++i
) {
2396 ASS_RenderPriv
*priv
;
2397 if (!imgs
[i
].detect_collisions
)
2399 priv
= get_render_priv(render_priv
, imgs
[i
].event
);
2400 if (priv
->height
> 0) { // it's a fixed event
2403 s
.b
= priv
->top
+ priv
->height
;
2405 s
.hb
= priv
->left
+ priv
->width
;
2406 if (priv
->height
!= imgs
[i
].height
) { // no, it's not
2407 ass_msg(render_priv
->library
, MSGL_WARN
,
2408 "Event height has changed");
2414 for (j
= 0; j
< cnt_used
; ++j
)
2415 if (overlap(&s
, used
+ j
)) { // no, it's not
2421 if (priv
->height
> 0) { // still a fixed event
2422 used
[cnt_used
].a
= priv
->top
;
2423 used
[cnt_used
].b
= priv
->top
+ priv
->height
;
2424 used
[cnt_used
].ha
= priv
->left
;
2425 used
[cnt_used
].hb
= priv
->left
+ priv
->width
;
2427 shift_event(render_priv
, imgs
+ i
, priv
->top
- imgs
[i
].top
);
2431 qsort(used
, cnt_used
, sizeof(Segment
), cmp_segment
);
2433 // try to fit other events in free spaces
2434 for (i
= 0; i
< cnt
; ++i
) {
2435 ASS_RenderPriv
*priv
;
2436 if (!imgs
[i
].detect_collisions
)
2438 priv
= get_render_priv(render_priv
, imgs
[i
].event
);
2439 if (priv
->height
== 0) { // not a fixed event
2443 s
.b
= imgs
[i
].top
+ imgs
[i
].height
;
2444 s
.ha
= imgs
[i
].left
;
2445 s
.hb
= imgs
[i
].left
+ imgs
[i
].width
;
2446 shift
= fit_segment(&s
, used
, &cnt_used
, imgs
[i
].shift_direction
);
2448 shift_event(render_priv
, imgs
+ i
, shift
);
2450 priv
->top
= imgs
[i
].top
;
2451 priv
->height
= imgs
[i
].height
;
2452 priv
->left
= imgs
[i
].left
;
2453 priv
->width
= imgs
[i
].width
;
2462 * \brief compare two images
2463 * \param i1 first image
2464 * \param i2 second image
2465 * \return 0 if identical, 1 if different positions, 2 if different content
2467 static int ass_image_compare(ASS_Image
*i1
, ASS_Image
*i2
)
2473 if (i1
->stride
!= i2
->stride
)
2475 if (i1
->color
!= i2
->color
)
2477 if (i1
->bitmap
!= i2
->bitmap
)
2479 if (i1
->dst_x
!= i2
->dst_x
)
2481 if (i1
->dst_y
!= i2
->dst_y
)
2487 * \brief compare current and previous image list
2488 * \param priv library handle
2489 * \return 0 if identical, 1 if different positions, 2 if different content
2491 static int ass_detect_change(ASS_Renderer
*priv
)
2493 ASS_Image
*img
, *img2
;
2496 if (priv
->cache_cleared
)
2499 img
= priv
->prev_images_root
;
2500 img2
= priv
->images_root
;
2502 while (img
&& diff
< 2) {
2503 ASS_Image
*next
, *next2
;
2506 int d
= ass_image_compare(img
, img2
);
2511 // previous list is shorter
2519 // is the previous list longer?
2527 * \brief render a frame
2528 * \param priv library handle
2529 * \param track track
2530 * \param now current video timestamp (ms)
2531 * \param detect_change a value describing how the new images differ from the previous ones will be written here:
2532 * 0 if identical, 1 if different positions, 2 if different content.
2533 * Can be NULL, in that case no detection is performed.
2535 ASS_Image
*ass_render_frame(ASS_Renderer
*priv
, ASS_Track
*track
,
2536 long long now
, int *detect_change
)
2543 rc
= ass_start_frame(priv
, track
, now
);
2545 if (detect_change
) {
2551 // render events separately
2553 for (i
= 0; i
< track
->n_events
; ++i
) {
2554 ASS_Event
*event
= track
->events
+ i
;
2555 if ((event
->Start
<= now
)
2556 && (now
< (event
->Start
+ event
->Duration
))) {
2557 if (cnt
>= priv
->eimg_size
) {
2558 priv
->eimg_size
+= 100;
2561 priv
->eimg_size
* sizeof(EventImages
));
2563 rc
= ass_render_event(priv
, event
, priv
->eimg
+ cnt
);
2570 qsort(priv
->eimg
, cnt
, sizeof(EventImages
), cmp_event_layer
);
2572 // call fix_collisions for each group of events with the same layer
2574 for (i
= 1; i
< cnt
; ++i
)
2575 if (last
->event
->Layer
!= priv
->eimg
[i
].event
->Layer
) {
2576 fix_collisions(priv
, last
, priv
->eimg
+ i
- last
);
2577 last
= priv
->eimg
+ i
;
2580 fix_collisions(priv
, last
, priv
->eimg
+ cnt
- last
);
2583 tail
= &priv
->images_root
;
2584 for (i
= 0; i
< cnt
; ++i
) {
2585 ASS_Image
*cur
= priv
->eimg
[i
].imgs
;
2594 *detect_change
= ass_detect_change(priv
);
2596 // free the previous image list
2597 ass_free_images(priv
->prev_images_root
);
2598 priv
->prev_images_root
= 0;
2599 priv
->cache_cleared
= 0;
2601 return priv
->images_root
;