2 * Copyright (C) 2009 Grigori Goronzy <greg@geekmind.org>
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.
26 #include "ass_render.h"
27 #include "ass_parse.h"
30 #define NBSP 0xa0 // unicode non-breaking space character
32 #define skip_to(x) while ((*p != (x)) && (*p != '}') && (*p != 0)) { ++p;}
33 #define skip(x) if (*p == (x)) ++p; else { return p; }
34 #define skipopt(x) if (*p == (x)) { ++p; }
37 * \brief Check if starting part of (*p) matches sample.
38 * If true, shift p to the first symbol after the matching part.
40 static inline int mystrcmp(char **p
, const char *sample
)
42 int len
= strlen(sample
);
43 if (strncmp(*p
, sample
, len
) == 0) {
50 double ensure_font_size(ASS_Renderer
*priv
, double size
)
54 else if (size
> priv
->height
* 2)
55 size
= priv
->height
* 2;
60 static void change_font_size(ASS_Renderer
*render_priv
, double sz
)
62 render_priv
->state
.font_size
= sz
;
66 * \brief Change current font, using setting from render_priv->state.
68 void update_font(ASS_Renderer
*render_priv
)
72 desc
.treat_family_as_pattern
= render_priv
->state
.treat_family_as_pattern
;
74 if (render_priv
->state
.family
[0] == '@') {
76 desc
.family
= strdup(render_priv
->state
.family
+ 1);
79 desc
.family
= strdup(render_priv
->state
.family
);
82 val
= render_priv
->state
.bold
;
83 // 0 = normal, 1 = bold, >1 = exact weight
84 if (val
== 1 || val
== -1)
90 val
= render_priv
->state
.italic
;
91 if (val
== 1 || val
== -1)
97 render_priv
->state
.font
=
98 ass_font_new(render_priv
->cache
.font_cache
, render_priv
->library
,
99 render_priv
->ftlibrary
, render_priv
->fontconfig_priv
,
103 if (render_priv
->state
.font
)
104 change_font_size(render_priv
, render_priv
->state
.font_size
);
108 * \brief Calculate valid border size. Makes sure the border sizes make sense.
110 * \param priv renderer state object
111 * \param border_x requested x border size
112 * \param border_y requested y border size
114 void calc_border(ASS_Renderer
*priv
, double border_x
, double border_y
)
116 if (border_x
< 0 && border_y
< 0) {
117 if (priv
->state
.border_style
== 1 ||
118 priv
->state
.border_style
== 3)
119 border_x
= border_y
= priv
->state
.style
->Outline
;
121 border_x
= border_y
= 1.;
124 priv
->state
.border_x
= border_x
;
125 priv
->state
.border_y
= border_y
;
129 * \brief Change border width
131 * \param render_priv renderer state object
132 * \param info glyph state object
134 void change_border(ASS_Renderer
*render_priv
, double border_x
, double border_y
)
136 int bord
= 64 * border_x
* render_priv
->border_scale
;
138 if (bord
> 0 && border_x
== border_y
) {
139 if (!render_priv
->state
.stroker
) {
142 FT_Stroker_New(render_priv
->ftlibrary
,
143 &render_priv
->state
.stroker
);
145 ass_msg(render_priv
->library
, MSGL_V
,
146 "failed to get stroker");
147 render_priv
->state
.stroker
= 0;
149 render_priv
->state
.stroker_radius
= -1.0;
151 if (render_priv
->state
.stroker
&& render_priv
->state
.stroker_radius
!= bord
) {
152 FT_Stroker_Set(render_priv
->state
.stroker
, bord
,
153 FT_STROKER_LINECAP_ROUND
,
154 FT_STROKER_LINEJOIN_ROUND
, 0);
155 render_priv
->state
.stroker_radius
= bord
;
158 FT_Stroker_Done(render_priv
->state
.stroker
);
159 render_priv
->state
.stroker
= 0;
164 * \brief Calculate a weighted average of two colors
165 * calculates c1*(1-a) + c2*a, but separately for each component except alpha
167 static void change_color(uint32_t *var
, uint32_t new, double pwr
)
169 (*var
) = ((uint32_t) (_r(*var
) * (1 - pwr
) + _r(new) * pwr
) << 24) +
170 ((uint32_t) (_g(*var
) * (1 - pwr
) + _g(new) * pwr
) << 16) +
171 ((uint32_t) (_b(*var
) * (1 - pwr
) + _b(new) * pwr
) << 8) + _a(*var
);
174 // like change_color, but for alpha component only
175 inline void change_alpha(uint32_t *var
, uint32_t new, double pwr
)
178 (_r(*var
) << 24) + (_g(*var
) << 16) + (_b(*var
) << 8) +
179 (uint32_t) (_a(*var
) * (1 - pwr
) + _a(new) * pwr
);
183 * \brief Multiply two alpha values
184 * \param a first value
185 * \param b second value
186 * \return result of multiplication
187 * Parameters and result are limited by 0xFF.
189 inline uint32_t mult_alpha(uint32_t a
, uint32_t b
)
191 return 0xFF - (0xFF - a
) * (0xFF - b
) / 0xFF;
195 * \brief Calculate alpha value by piecewise linear function
196 * Used for \fad, \fade implementation.
199 interpolate_alpha(long long now
, long long t1
, long long t2
, long long t3
,
200 long long t4
, unsigned a1
, unsigned a2
, unsigned a3
)
207 } else if (now
>= t4
) {
209 } else if (now
< t2
&& t2
> t1
) {
210 cf
= ((double) (now
- t1
)) / (t2
- t1
);
211 a
= a1
* (1 - cf
) + a2
* cf
;
212 } else if (now
>= t3
&& t4
> t3
) {
213 cf
= ((double) (now
- t3
)) / (t4
- t3
);
214 a
= a2
* (1 - cf
) + a3
* cf
;
215 } else { // t2 <= now < t3
223 * Parse a vector clip into an outline, using the proper scaling
224 * parameters. Translate it to correct for screen borders, if needed.
226 static char *parse_vector_clip(ASS_Renderer
*render_priv
, char *p
)
230 ASS_Drawing
*drawing
= render_priv
->state
.clip_drawing
;
232 ass_drawing_free(drawing
);
233 render_priv
->state
.clip_drawing
=
234 ass_drawing_new(render_priv
->library
, render_priv
->ftlibrary
);
235 drawing
= render_priv
->state
.clip_drawing
;
237 res
= mystrtoi(&p
, &scale
);
241 drawing
->scale
= scale
;
242 drawing
->scale_x
= render_priv
->font_scale_x
* render_priv
->font_scale
;
243 drawing
->scale_y
= render_priv
->font_scale
;
244 while (*p
!= ')' && *p
!= '}' && p
!= 0)
245 ass_drawing_add_char(drawing
, *p
++);
252 * \brief Parse style override tag.
253 * \param p string to parse
254 * \param pwr multiplier for some tag effects (comes from \t tags)
256 char *parse_tag(ASS_Renderer
*render_priv
, char *p
, double pwr
)
260 if ((*p
== '}') || (*p
== 0))
263 // New tags introduced in vsfilter 2.39
264 if (mystrcmp(&p
, "xbord")) {
266 if (mystrtod(&p
, &val
))
267 val
= render_priv
->state
.border_x
* (1 - pwr
) + val
* pwr
;
270 calc_border(render_priv
, val
, render_priv
->state
.border_y
);
271 render_priv
->state
.bm_run_id
++;
272 } else if (mystrcmp(&p
, "ybord")) {
274 if (mystrtod(&p
, &val
))
275 val
= render_priv
->state
.border_y
* (1 - pwr
) + val
* pwr
;
278 calc_border(render_priv
, render_priv
->state
.border_x
, val
);
279 render_priv
->state
.bm_run_id
++;
280 } else if (mystrcmp(&p
, "xshad")) {
282 if (mystrtod(&p
, &val
))
283 val
= render_priv
->state
.shadow_x
* (1 - pwr
) + val
* pwr
;
286 render_priv
->state
.shadow_x
= val
;
287 render_priv
->state
.bm_run_id
++;
288 } else if (mystrcmp(&p
, "yshad")) {
290 if (mystrtod(&p
, &val
))
291 val
= render_priv
->state
.shadow_y
* (1 - pwr
) + val
* pwr
;
294 render_priv
->state
.shadow_y
= val
;
295 render_priv
->state
.bm_run_id
++;
296 } else if (mystrcmp(&p
, "fax")) {
298 if (mystrtod(&p
, &val
))
299 render_priv
->state
.fax
=
300 val
* pwr
+ render_priv
->state
.fax
* (1 - pwr
);
302 render_priv
->state
.fax
= 0.;
303 } else if (mystrcmp(&p
, "fay")) {
305 if (mystrtod(&p
, &val
))
306 render_priv
->state
.fay
=
307 val
* pwr
+ render_priv
->state
.fay
* (1 - pwr
);
309 render_priv
->state
.fay
= 0.;
310 } else if (mystrcmp(&p
, "iclip")) {
315 res
&= mystrtoi(&p
, &x0
);
317 res
&= mystrtoi(&p
, &y0
);
319 res
&= mystrtoi(&p
, &x1
);
321 res
&= mystrtoi(&p
, &y1
);
324 render_priv
->state
.clip_x0
=
325 render_priv
->state
.clip_x0
* (1 - pwr
) + x0
* pwr
;
326 render_priv
->state
.clip_x1
=
327 render_priv
->state
.clip_x1
* (1 - pwr
) + x1
* pwr
;
328 render_priv
->state
.clip_y0
=
329 render_priv
->state
.clip_y0
* (1 - pwr
) + y0
* pwr
;
330 render_priv
->state
.clip_y1
=
331 render_priv
->state
.clip_y1
* (1 - pwr
) + y1
* pwr
;
332 render_priv
->state
.clip_mode
= 1;
333 } else if (!render_priv
->state
.clip_drawing
) {
334 p
= parse_vector_clip(render_priv
, start
);
335 render_priv
->state
.clip_drawing_mode
= 1;
337 render_priv
->state
.clip_mode
= 0;
338 } else if (mystrcmp(&p
, "blur")) {
340 if (mystrtod(&p
, &val
)) {
341 val
= render_priv
->state
.blur
* (1 - pwr
) + val
* pwr
;
342 val
= (val
< 0) ? 0 : val
;
343 val
= (val
> BLUR_MAX_RADIUS
) ? BLUR_MAX_RADIUS
: val
;
344 render_priv
->state
.blur
= val
;
346 render_priv
->state
.blur
= 0.0;
347 render_priv
->state
.bm_run_id
++;
349 } else if (mystrcmp(&p
, "fsc")) {
353 if (mystrtod(&p
, &val
)) {
355 render_priv
->state
.scale_x
=
356 render_priv
->state
.scale_x
* (1 - pwr
) + val
* pwr
;
358 render_priv
->state
.scale_x
=
359 render_priv
->state
.style
->ScaleX
;
360 } else if (tp
== 'y') {
361 if (mystrtod(&p
, &val
)) {
363 render_priv
->state
.scale_y
=
364 render_priv
->state
.scale_y
* (1 - pwr
) + val
* pwr
;
366 render_priv
->state
.scale_y
=
367 render_priv
->state
.style
->ScaleY
;
369 } else if (mystrcmp(&p
, "fsp")) {
371 if (mystrtod(&p
, &val
))
372 render_priv
->state
.hspacing
=
373 render_priv
->state
.hspacing
* (1 - pwr
) + val
* pwr
;
375 render_priv
->state
.hspacing
= render_priv
->state
.style
->Spacing
;
376 } else if (mystrcmp(&p
, "fs+")) {
378 if (mystrtod(&p
, &val
)) {
379 val
= render_priv
->state
.font_size
+ pwr
* val
;
381 val
= render_priv
->state
.style
->FontSize
;
382 if (render_priv
->state
.font
)
383 change_font_size(render_priv
, val
);
384 } else if (mystrcmp(&p
, "fs-")) {
386 if (mystrtod(&p
, &val
))
387 val
= render_priv
->state
.font_size
- pwr
* val
;
389 val
= render_priv
->state
.style
->FontSize
;
390 if (render_priv
->state
.font
)
391 change_font_size(render_priv
, val
);
392 } else if (mystrcmp(&p
, "fs")) {
394 if (mystrtod(&p
, &val
))
395 val
= render_priv
->state
.font_size
* (1 - pwr
) + val
* pwr
;
397 val
= render_priv
->state
.style
->FontSize
;
398 if (render_priv
->state
.font
)
399 change_font_size(render_priv
, val
);
400 } else if (mystrcmp(&p
, "bord")) {
402 if (mystrtod(&p
, &val
)) {
403 val
= render_priv
->state
.border_x
* (1 - pwr
) + val
* pwr
;
405 val
= -1.; // reset to default
406 calc_border(render_priv
, val
, val
);
407 render_priv
->state
.bm_run_id
++;
408 } else if (mystrcmp(&p
, "move")) {
409 double x1
, x2
, y1
, y2
;
410 long long t1
, t2
, delta_t
, t
;
426 ass_msg(render_priv
->library
, MSGL_DBG2
,
427 "movement6: (%f, %f) -> (%f, %f), (%" PRId64
" .. %"
428 PRId64
")\n", x1
, y1
, x2
, y2
, (int64_t) t1
,
432 t2
= render_priv
->state
.event
->Duration
;
433 ass_msg(render_priv
->library
, MSGL_DBG2
,
434 "movement: (%f, %f) -> (%f, %f)", x1
, y1
, x2
, y2
);
438 t
= render_priv
->time
- render_priv
->state
.event
->Start
;
444 k
= ((double) (t
- t1
)) / delta_t
;
445 x
= k
* (x2
- x1
) + x1
;
446 y
= k
* (y2
- y1
) + y1
;
447 if (render_priv
->state
.evt_type
!= EVENT_POSITIONED
) {
448 render_priv
->state
.pos_x
= x
;
449 render_priv
->state
.pos_y
= y
;
450 render_priv
->state
.detect_collisions
= 0;
451 render_priv
->state
.evt_type
= EVENT_POSITIONED
;
453 } else if (mystrcmp(&p
, "frx")) {
455 if (mystrtod(&p
, &val
)) {
457 render_priv
->state
.frx
=
458 val
* pwr
+ render_priv
->state
.frx
* (1 - pwr
);
460 render_priv
->state
.frx
= 0.;
461 } else if (mystrcmp(&p
, "fry")) {
463 if (mystrtod(&p
, &val
)) {
465 render_priv
->state
.fry
=
466 val
* pwr
+ render_priv
->state
.fry
* (1 - pwr
);
468 render_priv
->state
.fry
= 0.;
469 } else if (mystrcmp(&p
, "frz") || mystrcmp(&p
, "fr")) {
471 if (mystrtod(&p
, &val
)) {
473 render_priv
->state
.frz
=
474 val
* pwr
+ render_priv
->state
.frz
* (1 - pwr
);
476 render_priv
->state
.frz
=
477 M_PI
* render_priv
->state
.style
->Angle
/ 180.;
478 } else if (mystrcmp(&p
, "fn")) {
483 family
= malloc(p
- start
+ 1);
484 strncpy(family
, start
, p
- start
);
485 family
[p
- start
] = '\0';
487 family
= strdup(render_priv
->state
.style
->FontName
);
488 free(render_priv
->state
.family
);
489 render_priv
->state
.family
= family
;
490 update_font(render_priv
);
491 } else if (mystrcmp(&p
, "alpha")) {
494 int hex
= render_priv
->track
->track_type
== TRACK_TYPE_ASS
;
495 if (strtocolor(render_priv
->library
, &p
, &val
, hex
)) {
496 unsigned char a
= val
>> 24;
497 for (i
= 0; i
< 4; ++i
)
498 change_alpha(&render_priv
->state
.c
[i
], a
, pwr
);
500 change_alpha(&render_priv
->state
.c
[0],
501 render_priv
->state
.style
->PrimaryColour
, pwr
);
502 change_alpha(&render_priv
->state
.c
[1],
503 render_priv
->state
.style
->SecondaryColour
, pwr
);
504 change_alpha(&render_priv
->state
.c
[2],
505 render_priv
->state
.style
->OutlineColour
, pwr
);
506 change_alpha(&render_priv
->state
.c
[3],
507 render_priv
->state
.style
->BackColour
, pwr
);
509 render_priv
->state
.bm_run_id
++;
511 } else if (mystrcmp(&p
, "an")) {
513 if (mystrtoi(&p
, &val
) && val
) {
514 int v
= (val
- 1) / 3; // 0, 1 or 2 for vertical alignment
515 ass_msg(render_priv
->library
, MSGL_DBG2
, "an %d", val
);
518 val
= ((val
- 1) % 3) + 1; // horizontal alignment
520 ass_msg(render_priv
->library
, MSGL_DBG2
, "align %d", val
);
521 if ((render_priv
->state
.parsed_tags
& PARSED_A
) == 0) {
522 render_priv
->state
.alignment
= val
;
523 render_priv
->state
.parsed_tags
|= PARSED_A
;
526 render_priv
->state
.alignment
=
527 render_priv
->state
.style
->Alignment
;
528 } else if (mystrcmp(&p
, "a")) {
530 if (mystrtoi(&p
, &val
) && val
) {
531 if ((render_priv
->state
.parsed_tags
& PARSED_A
) == 0) {
532 // take care of a vsfilter quirk: handle illegal \a8 like \a5
533 render_priv
->state
.alignment
= (val
== 8) ? 5 : val
;
534 render_priv
->state
.parsed_tags
|= PARSED_A
;
537 render_priv
->state
.alignment
=
538 render_priv
->state
.style
->Alignment
;
539 } else if (mystrcmp(&p
, "pos")) {
546 ass_msg(render_priv
->library
, MSGL_DBG2
, "pos(%f, %f)", v1
, v2
);
547 if (render_priv
->state
.evt_type
== EVENT_POSITIONED
) {
548 ass_msg(render_priv
->library
, MSGL_V
, "Subtitle has a new \\pos "
549 "after \\move or \\pos, ignoring");
551 render_priv
->state
.evt_type
= EVENT_POSITIONED
;
552 render_priv
->state
.detect_collisions
= 0;
553 render_priv
->state
.pos_x
= v1
;
554 render_priv
->state
.pos_y
= v2
;
556 } else if (mystrcmp(&p
, "fad")) {
558 long long t1
, t2
, t3
, t4
;
560 ++p
; // either \fad or \fade
566 // 2-argument version (\fad, according to specs)
567 // a1 and a2 are fade-in and fade-out durations
569 t4
= render_priv
->state
.event
->Duration
;
576 // 6-argument version (\fade)
577 // a1 and a2 (and a3) are opacity values
590 if ((render_priv
->state
.parsed_tags
& PARSED_FADE
) == 0) {
591 render_priv
->state
.fade
=
592 interpolate_alpha(render_priv
->time
-
593 render_priv
->state
.event
->Start
, t1
, t2
,
595 render_priv
->state
.parsed_tags
|= PARSED_FADE
;
597 } else if (mystrcmp(&p
, "org")) {
604 ass_msg(render_priv
->library
, MSGL_DBG2
, "org(%d, %d)", v1
, v2
);
605 if (!render_priv
->state
.have_origin
) {
606 render_priv
->state
.org_x
= v1
;
607 render_priv
->state
.org_y
= v2
;
608 render_priv
->state
.have_origin
= 1;
609 render_priv
->state
.detect_collisions
= 0;
611 } else if (mystrcmp(&p
, "t")) {
616 long long t1
, t2
, t
, delta_t
;
619 for (cnt
= 0; cnt
< 3; ++cnt
) {
622 mystrtod(&p
, &v
[cnt
]);
627 v2
= (v
[1] < v1
) ? render_priv
->state
.event
->Duration
: v
[1];
629 } else if (cnt
== 2) {
631 v2
= (v
[1] < v1
) ? render_priv
->state
.event
->Duration
: v
[1];
633 } else if (cnt
== 1) {
635 v2
= render_priv
->state
.event
->Duration
;
639 v2
= render_priv
->state
.event
->Duration
;
642 render_priv
->state
.detect_collisions
= 0;
648 t
= render_priv
->time
- render_priv
->state
.event
->Start
; // FIXME: move to render_context
654 assert(delta_t
!= 0.);
655 k
= pow(((double) (t
- t1
)) / delta_t
, v3
);
658 p
= parse_tag(render_priv
, p
, k
); // maybe k*pwr ? no, specs forbid nested \t's
659 skip_to(')'); // in case there is some unknown tag or a comment
661 } else if (mystrcmp(&p
, "clip")) {
666 res
&= mystrtoi(&p
, &x0
);
668 res
&= mystrtoi(&p
, &y0
);
670 res
&= mystrtoi(&p
, &x1
);
672 res
&= mystrtoi(&p
, &y1
);
675 render_priv
->state
.clip_x0
=
676 render_priv
->state
.clip_x0
* (1 - pwr
) + x0
* pwr
;
677 render_priv
->state
.clip_x1
=
678 render_priv
->state
.clip_x1
* (1 - pwr
) + x1
* pwr
;
679 render_priv
->state
.clip_y0
=
680 render_priv
->state
.clip_y0
* (1 - pwr
) + y0
* pwr
;
681 render_priv
->state
.clip_y1
=
682 render_priv
->state
.clip_y1
* (1 - pwr
) + y1
* pwr
;
683 // Might be a vector clip
684 } else if (!render_priv
->state
.clip_drawing
) {
685 p
= parse_vector_clip(render_priv
, start
);
686 render_priv
->state
.clip_drawing_mode
= 0;
688 render_priv
->state
.clip_x0
= 0;
689 render_priv
->state
.clip_y0
= 0;
690 render_priv
->state
.clip_x1
= render_priv
->track
->PlayResX
;
691 render_priv
->state
.clip_y1
= render_priv
->track
->PlayResY
;
693 } else if (mystrcmp(&p
, "c")) {
695 int hex
= render_priv
->track
->track_type
== TRACK_TYPE_ASS
;
696 if (!strtocolor(render_priv
->library
, &p
, &val
, hex
))
697 val
= render_priv
->state
.style
->PrimaryColour
;
698 ass_msg(render_priv
->library
, MSGL_DBG2
, "color: %X", val
);
699 change_color(&render_priv
->state
.c
[0], val
, pwr
);
700 render_priv
->state
.bm_run_id
++;
701 } else if ((*p
>= '1') && (*p
<= '4') && (++p
)
702 && (mystrcmp(&p
, "c") || mystrcmp(&p
, "a"))) {
707 int hex
= render_priv
->track
->track_type
== TRACK_TYPE_ASS
;
708 assert((n
>= '1') && (n
<= '4'));
709 if (!strtocolor(render_priv
->library
, &p
, &val
, hex
))
712 val
= render_priv
->state
.style
->PrimaryColour
;
715 val
= render_priv
->state
.style
->SecondaryColour
;
718 val
= render_priv
->state
.style
->OutlineColour
;
721 val
= render_priv
->state
.style
->BackColour
;
725 break; // impossible due to assert; avoid compilation warning
729 change_color(render_priv
->state
.c
+ cidx
, val
, pwr
);
730 render_priv
->state
.bm_run_id
++;
733 change_alpha(render_priv
->state
.c
+ cidx
, val
>> 24, pwr
);
734 render_priv
->state
.bm_run_id
++;
737 ass_msg(render_priv
->library
, MSGL_WARN
, "Bad command: %c%c",
741 ass_msg(render_priv
->library
, MSGL_DBG2
, "single c/a at %f: %c%c = %X",
742 pwr
, n
, cmd
, render_priv
->state
.c
[cidx
]);
743 } else if (mystrcmp(&p
, "r")) {
748 style
= malloc(p
- start
+ 1);
749 strncpy(style
, start
, p
- start
);
750 style
[p
- start
] = '\0';
751 reset_render_context(render_priv
,
752 render_priv
->track
->styles
+ lookup_style(render_priv
->track
, style
));
755 reset_render_context(render_priv
, NULL
);
756 } else if (mystrcmp(&p
, "be")) {
758 if (mystrtoi(&p
, &val
)) {
759 // Clamp to a safe upper limit, since high values need excessive CPU
760 val
= (val
< 0) ? 0 : val
;
761 val
= (val
> MAX_BE
) ? MAX_BE
: val
;
762 render_priv
->state
.be
= val
;
764 render_priv
->state
.be
= 0;
765 render_priv
->state
.bm_run_id
++;
766 } else if (mystrcmp(&p
, "b")) {
768 if (mystrtoi(&p
, &b
)) {
770 render_priv
->state
.bold
= b
;
772 render_priv
->state
.bold
= render_priv
->state
.style
->Bold
;
773 update_font(render_priv
);
774 } else if (mystrcmp(&p
, "i")) {
776 if (mystrtoi(&p
, &i
)) {
778 render_priv
->state
.italic
= i
;
780 render_priv
->state
.italic
= render_priv
->state
.style
->Italic
;
781 update_font(render_priv
);
782 } else if (mystrcmp(&p
, "kf") || mystrcmp(&p
, "K")) {
785 render_priv
->state
.effect_type
= EF_KARAOKE_KF
;
786 if (render_priv
->state
.effect_timing
)
787 render_priv
->state
.effect_skip_timing
+=
788 render_priv
->state
.effect_timing
;
789 render_priv
->state
.effect_timing
= val
* 10;
790 } else if (mystrcmp(&p
, "ko")) {
793 render_priv
->state
.effect_type
= EF_KARAOKE_KO
;
794 if (render_priv
->state
.effect_timing
)
795 render_priv
->state
.effect_skip_timing
+=
796 render_priv
->state
.effect_timing
;
797 render_priv
->state
.effect_timing
= val
* 10;
798 } else if (mystrcmp(&p
, "k")) {
801 render_priv
->state
.effect_type
= EF_KARAOKE
;
802 if (render_priv
->state
.effect_timing
)
803 render_priv
->state
.effect_skip_timing
+=
804 render_priv
->state
.effect_timing
;
805 render_priv
->state
.effect_timing
= val
* 10;
806 } else if (mystrcmp(&p
, "shad")) {
808 if (mystrtod(&p
, &val
)) {
809 if (render_priv
->state
.shadow_x
== render_priv
->state
.shadow_y
)
810 val
= render_priv
->state
.shadow_x
* (1 - pwr
) + val
* pwr
;
813 render_priv
->state
.shadow_x
= render_priv
->state
.shadow_y
= val
;
814 render_priv
->state
.bm_run_id
++;
815 } else if (mystrcmp(&p
, "s")) {
817 if (mystrtoi(&p
, &val
) && val
)
818 render_priv
->state
.flags
|= DECO_STRIKETHROUGH
;
820 render_priv
->state
.flags
&= ~DECO_STRIKETHROUGH
;
821 render_priv
->state
.bm_run_id
++;
822 } else if (mystrcmp(&p
, "u")) {
824 if (mystrtoi(&p
, &val
) && val
)
825 render_priv
->state
.flags
|= DECO_UNDERLINE
;
827 render_priv
->state
.flags
&= ~DECO_UNDERLINE
;
828 render_priv
->state
.bm_run_id
++;
829 } else if (mystrcmp(&p
, "pbo")) {
831 if (mystrtod(&p
, &val
))
832 render_priv
->state
.drawing
->pbo
= val
;
833 } else if (mystrcmp(&p
, "p")) {
835 if (!mystrtoi(&p
, &val
))
838 render_priv
->state
.drawing
->scale
= val
;
839 render_priv
->state
.drawing_mode
= !!val
;
840 } else if (mystrcmp(&p
, "q")) {
842 if (!mystrtoi(&p
, &val
))
843 val
= render_priv
->track
->WrapStyle
;
844 render_priv
->state
.wrap_style
= val
;
845 } else if (mystrcmp(&p
, "fe")) {
847 if (!mystrtoi(&p
, &val
))
848 val
= render_priv
->state
.style
->Encoding
;
849 render_priv
->state
.font_encoding
= val
;
855 void apply_transition_effects(ASS_Renderer
*render_priv
, ASS_Event
*event
)
859 char *p
= event
->Effect
;
865 while (cnt
< 4 && (p
= strchr(p
, ';'))) {
866 v
[cnt
++] = atoi(++p
);
869 if (strncmp(event
->Effect
, "Banner;", 7) == 0) {
872 ass_msg(render_priv
->library
, MSGL_V
,
873 "Error parsing effect: '%s'", event
->Effect
);
876 if (cnt
>= 2 && v
[1] == 0) // right-to-left
877 render_priv
->state
.scroll_direction
= SCROLL_RL
;
878 else // left-to-right
879 render_priv
->state
.scroll_direction
= SCROLL_LR
;
884 render_priv
->state
.scroll_shift
=
885 (render_priv
->time
- render_priv
->state
.event
->Start
) / delay
;
886 render_priv
->state
.evt_type
= EVENT_HSCROLL
;
890 if (strncmp(event
->Effect
, "Scroll up;", 10) == 0) {
891 render_priv
->state
.scroll_direction
= SCROLL_BT
;
892 } else if (strncmp(event
->Effect
, "Scroll down;", 12) == 0) {
893 render_priv
->state
.scroll_direction
= SCROLL_TB
;
895 ass_msg(render_priv
->library
, MSGL_DBG2
,
896 "Unknown transition effect: '%s'", event
->Effect
);
899 // parse scroll up/down parameters
904 ass_msg(render_priv
->library
, MSGL_V
,
905 "Error parsing effect: '%s'", event
->Effect
);
911 render_priv
->state
.scroll_shift
=
912 (render_priv
->time
- render_priv
->state
.event
->Start
) / delay
;
921 y1
= render_priv
->track
->PlayResY
; // y0=y1=0 means fullscreen scrolling
922 render_priv
->state
.clip_y0
= y0
;
923 render_priv
->state
.clip_y1
= y1
;
924 render_priv
->state
.evt_type
= EVENT_VSCROLL
;
925 render_priv
->state
.detect_collisions
= 0;
931 * \brief determine karaoke effects
932 * Karaoke effects cannot be calculated during parse stage (get_next_char()),
933 * so they are done in a separate step.
934 * Parse stage: when karaoke style override is found, its parameters are stored in the next glyph's
935 * (the first glyph of the karaoke word)'s effect_type and effect_timing.
937 * 1. sets effect_type for all glyphs in the word (_karaoke_ word)
938 * 2. sets effect_timing for all glyphs to x coordinate of the border line between the left and right karaoke parts
939 * (left part is filled with PrimaryColour, right one - with SecondaryColour).
941 void process_karaoke_effects(ASS_Renderer
*render_priv
)
943 GlyphInfo
*cur
, *cur2
;
944 GlyphInfo
*s1
, *e1
; // start and end of the current word
945 GlyphInfo
*s2
; // start of the next word
947 int timing
; // current timing
948 int tm_start
, tm_end
; // timings at start and end of the current word
954 tm_current
= render_priv
->time
- render_priv
->state
.event
->Start
;
957 for (i
= 0; i
<= render_priv
->text_info
.length
; ++i
) {
958 cur
= render_priv
->text_info
.glyphs
+ i
;
959 if ((i
== render_priv
->text_info
.length
)
960 || (cur
->effect_type
!= EF_NONE
)) {
965 tm_start
= timing
+ s1
->effect_skip_timing
;
966 tm_end
= tm_start
+ s1
->effect_timing
;
970 for (cur2
= s1
; cur2
<= e1
; ++cur2
) {
971 x_start
= FFMIN(x_start
, d6_to_int(cur2
->bbox
.xMin
+ cur2
->pos
.x
));
972 x_end
= FFMAX(x_end
, d6_to_int(cur2
->bbox
.xMax
+ cur2
->pos
.x
));
975 dt
= (tm_current
- tm_start
);
976 if ((s1
->effect_type
== EF_KARAOKE
)
977 || (s1
->effect_type
== EF_KARAOKE_KO
)) {
982 } else if (s1
->effect_type
== EF_KARAOKE_KF
) {
983 dt
/= (tm_end
- tm_start
);
984 x
= x_start
+ (x_end
- x_start
) * dt
;
986 ass_msg(render_priv
->library
, MSGL_ERR
,
987 "Unknown effect type");
991 for (cur2
= s1
; cur2
<= e1
; ++cur2
) {
992 cur2
->effect_type
= s1
->effect_type
;
993 cur2
->effect_timing
= x
- d6_to_int(cur2
->pos
.x
);
1002 * \brief Get next ucs4 char from string, parsing UTF-8 and escapes
1003 * \param str string pointer
1004 * \return ucs4 code of the next char
1005 * On return str points to the unparsed part of the string
1007 unsigned get_next_char(ASS_Renderer
*render_priv
, char **str
)
1017 if ((p
[1] == 'N') || ((p
[1] == 'n') &&
1018 (render_priv
->state
.wrap_style
== 2))) {
1022 } else if (p
[1] == 'n') {
1026 } else if (p
[1] == 'h') {
1030 } else if (p
[1] == '{') {
1034 } else if (p
[1] == '}') {
1040 chr
= ass_utf8_get_char((char **) &p
);