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 static void change_font_size(ASS_Renderer
*render_priv
, double sz
)
52 double size
= sz
* render_priv
->font_scale
;
56 else if (size
> render_priv
->height
* 2)
57 size
= render_priv
->height
* 2;
59 ass_font_set_size(render_priv
->state
.font
, size
);
61 render_priv
->state
.font_size
= sz
;
65 * \brief Change current font, using setting from render_priv->state.
67 void update_font(ASS_Renderer
*render_priv
)
71 desc
.treat_family_as_pattern
= render_priv
->state
.treat_family_as_pattern
;
73 if (render_priv
->state
.family
[0] == '@') {
75 desc
.family
= strdup(render_priv
->state
.family
+ 1);
78 desc
.family
= strdup(render_priv
->state
.family
);
81 val
= render_priv
->state
.bold
;
82 // 0 = normal, 1 = bold, >1 = exact weight
83 if (val
== 1 || val
== -1)
89 val
= render_priv
->state
.italic
;
90 if (val
== 1 || val
== -1)
96 render_priv
->state
.font
=
97 ass_font_new(render_priv
->cache
.font_cache
, render_priv
->library
,
98 render_priv
->ftlibrary
, render_priv
->fontconfig_priv
,
102 if (render_priv
->state
.font
)
103 change_font_size(render_priv
, render_priv
->state
.font_size
);
107 * \brief Change border width
108 * negative value resets border to style value
110 void change_border(ASS_Renderer
*render_priv
, double border_x
,
114 if (!render_priv
->state
.font
)
117 if (border_x
< 0 && border_y
< 0) {
118 if (render_priv
->state
.style
->BorderStyle
== 1 ||
119 render_priv
->state
.style
->BorderStyle
== 3)
120 border_x
= border_y
= render_priv
->state
.style
->Outline
;
122 border_x
= border_y
= 1.;
125 render_priv
->state
.border_x
= border_x
;
126 render_priv
->state
.border_y
= border_y
;
128 bord
= 64 * border_x
* render_priv
->border_scale
;
129 if (bord
> 0 && border_x
== border_y
) {
130 if (!render_priv
->state
.stroker
) {
133 FT_Stroker_New(render_priv
->ftlibrary
,
134 &render_priv
->state
.stroker
);
136 ass_msg(render_priv
->library
, MSGL_V
,
137 "failed to get stroker");
138 render_priv
->state
.stroker
= 0;
141 if (render_priv
->state
.stroker
)
142 FT_Stroker_Set(render_priv
->state
.stroker
, bord
,
143 FT_STROKER_LINECAP_ROUND
,
144 FT_STROKER_LINEJOIN_ROUND
, 0);
146 FT_Stroker_Done(render_priv
->state
.stroker
);
147 render_priv
->state
.stroker
= 0;
152 * \brief Calculate a weighted average of two colors
153 * calculates c1*(1-a) + c2*a, but separately for each component except alpha
155 static void change_color(uint32_t *var
, uint32_t new, double pwr
)
157 (*var
) = ((uint32_t) (_r(*var
) * (1 - pwr
) + _r(new) * pwr
) << 24) +
158 ((uint32_t) (_g(*var
) * (1 - pwr
) + _g(new) * pwr
) << 16) +
159 ((uint32_t) (_b(*var
) * (1 - pwr
) + _b(new) * pwr
) << 8) + _a(*var
);
162 // like change_color, but for alpha component only
163 inline void change_alpha(uint32_t *var
, uint32_t new, double pwr
)
166 (_r(*var
) << 24) + (_g(*var
) << 16) + (_b(*var
) << 8) +
167 (uint32_t) (_a(*var
) * (1 - pwr
) + _a(new) * pwr
);
171 * \brief Multiply two alpha values
172 * \param a first value
173 * \param b second value
174 * \return result of multiplication
175 * Parameters and result are limited by 0xFF.
177 inline uint32_t mult_alpha(uint32_t a
, uint32_t b
)
179 return 0xFF - (0xFF - a
) * (0xFF - b
) / 0xFF;
183 * \brief Calculate alpha value by piecewise linear function
184 * Used for \fad, \fade implementation.
187 interpolate_alpha(long long now
, long long t1
, long long t2
, long long t3
,
188 long long t4
, unsigned a1
, unsigned a2
, unsigned a3
)
194 } else if (now
>= t4
) {
196 } else if (now
< t2
) { // and > t1
197 cf
= ((double) (now
- t1
)) / (t2
- t1
);
198 a
= a1
* (1 - cf
) + a2
* cf
;
199 } else if (now
> t3
) {
200 cf
= ((double) (now
- t3
)) / (t4
- t3
);
201 a
= a2
* (1 - cf
) + a3
* cf
;
202 } else { // t2 <= now <= t3
210 * Parse a vector clip into an outline, using the proper scaling
211 * parameters. Translate it to correct for screen borders, if needed.
213 static char *parse_vector_clip(ASS_Renderer
*render_priv
, char *p
)
217 ASS_Drawing
*drawing
;
219 ass_drawing_free(render_priv
->state
.clip_drawing
);
220 render_priv
->state
.clip_drawing
= ass_drawing_new(
221 render_priv
->fontconfig_priv
,
222 render_priv
->state
.font
,
223 render_priv
->settings
.hinting
,
224 render_priv
->ftlibrary
);
225 drawing
= render_priv
->state
.clip_drawing
;
227 res
= mystrtoi(&p
, &scale
);
231 drawing
->scale
= scale
;
232 drawing
->scale_x
= render_priv
->font_scale_x
* render_priv
->font_scale
;
233 drawing
->scale_y
= render_priv
->font_scale
;
234 while (*p
!= ')' && *p
!= '}' && p
!= 0)
235 ass_drawing_add_char(drawing
, *p
++);
242 * \brief Parse style override tag.
243 * \param p string to parse
244 * \param pwr multiplier for some tag effects (comes from \t tags)
246 static char *parse_tag(ASS_Renderer
*render_priv
, char *p
, double pwr
)
250 if ((*p
== '}') || (*p
== 0))
253 // New tags introduced in vsfilter 2.39
254 if (mystrcmp(&p
, "xbord")) {
256 if (mystrtod(&p
, &val
))
257 val
= render_priv
->state
.border_x
* (1 - pwr
) + val
* pwr
;
260 change_border(render_priv
, val
, render_priv
->state
.border_y
);
261 } else if (mystrcmp(&p
, "ybord")) {
263 if (mystrtod(&p
, &val
))
264 val
= render_priv
->state
.border_y
* (1 - pwr
) + val
* pwr
;
267 change_border(render_priv
, render_priv
->state
.border_x
, val
);
268 } else if (mystrcmp(&p
, "xshad")) {
270 if (mystrtod(&p
, &val
))
271 val
= render_priv
->state
.shadow_x
* (1 - pwr
) + val
* pwr
;
274 render_priv
->state
.shadow_x
= val
;
275 } else if (mystrcmp(&p
, "yshad")) {
277 if (mystrtod(&p
, &val
))
278 val
= render_priv
->state
.shadow_y
* (1 - pwr
) + val
* pwr
;
281 render_priv
->state
.shadow_y
= val
;
282 } else if (mystrcmp(&p
, "fax")) {
284 if (mystrtod(&p
, &val
))
285 render_priv
->state
.fax
=
286 val
* pwr
+ render_priv
->state
.fax
* (1 - pwr
);
288 render_priv
->state
.fax
= 0.;
289 } else if (mystrcmp(&p
, "fay")) {
291 if (mystrtod(&p
, &val
))
292 render_priv
->state
.fay
=
293 val
* pwr
+ render_priv
->state
.fay
* (1 - pwr
);
295 render_priv
->state
.fay
= 0.;
296 } else if (mystrcmp(&p
, "iclip")) {
301 res
&= mystrtoi(&p
, &x0
);
303 res
&= mystrtoi(&p
, &y0
);
305 res
&= mystrtoi(&p
, &x1
);
307 res
&= mystrtoi(&p
, &y1
);
310 render_priv
->state
.clip_x0
=
311 render_priv
->state
.clip_x0
* (1 - pwr
) + x0
* pwr
;
312 render_priv
->state
.clip_x1
=
313 render_priv
->state
.clip_x1
* (1 - pwr
) + x1
* pwr
;
314 render_priv
->state
.clip_y0
=
315 render_priv
->state
.clip_y0
* (1 - pwr
) + y0
* pwr
;
316 render_priv
->state
.clip_y1
=
317 render_priv
->state
.clip_y1
* (1 - pwr
) + y1
* pwr
;
318 render_priv
->state
.clip_mode
= 1;
319 } else if (!render_priv
->state
.clip_drawing
) {
320 p
= parse_vector_clip(render_priv
, start
);
321 render_priv
->state
.clip_drawing_mode
= 1;
323 render_priv
->state
.clip_mode
= 0;
324 } else if (mystrcmp(&p
, "blur")) {
326 if (mystrtod(&p
, &val
)) {
327 val
= render_priv
->state
.blur
* (1 - pwr
) + val
* pwr
;
328 val
= (val
< 0) ? 0 : val
;
329 val
= (val
> BLUR_MAX_RADIUS
) ? BLUR_MAX_RADIUS
: val
;
330 render_priv
->state
.blur
= val
;
332 render_priv
->state
.blur
= 0.0;
334 } else if (mystrcmp(&p
, "fsc")) {
338 if (mystrtod(&p
, &val
)) {
340 render_priv
->state
.scale_x
=
341 render_priv
->state
.scale_x
* (1 - pwr
) + val
* pwr
;
343 render_priv
->state
.scale_x
=
344 render_priv
->state
.style
->ScaleX
;
345 } else if (tp
== 'y') {
346 if (mystrtod(&p
, &val
)) {
348 render_priv
->state
.scale_y
=
349 render_priv
->state
.scale_y
* (1 - pwr
) + val
* pwr
;
351 render_priv
->state
.scale_y
=
352 render_priv
->state
.style
->ScaleY
;
354 } else if (mystrcmp(&p
, "fsp")) {
356 if (mystrtod(&p
, &val
))
357 render_priv
->state
.hspacing
=
358 render_priv
->state
.hspacing
* (1 - pwr
) + val
* pwr
;
360 render_priv
->state
.hspacing
= render_priv
->state
.style
->Spacing
;
361 } else if (mystrcmp(&p
, "fs")) {
363 if (mystrtod(&p
, &val
))
364 val
= render_priv
->state
.font_size
* (1 - pwr
) + val
* pwr
;
366 val
= render_priv
->state
.style
->FontSize
;
367 if (render_priv
->state
.font
)
368 change_font_size(render_priv
, val
);
369 } else if (mystrcmp(&p
, "bord")) {
371 if (mystrtod(&p
, &val
)) {
372 if (render_priv
->state
.border_x
== render_priv
->state
.border_y
)
373 val
= render_priv
->state
.border_x
* (1 - pwr
) + val
* pwr
;
375 val
= -1.; // reset to default
376 change_border(render_priv
, val
, val
);
377 } else if (mystrcmp(&p
, "move")) {
378 double x1
, x2
, y1
, y2
;
379 long long t1
, t2
, delta_t
, t
;
395 ass_msg(render_priv
->library
, MSGL_DBG2
,
396 "movement6: (%f, %f) -> (%f, %f), (%" PRId64
" .. %"
397 PRId64
")\n", x1
, y1
, x2
, y2
, (int64_t) t1
,
401 t2
= render_priv
->state
.event
->Duration
;
402 ass_msg(render_priv
->library
, MSGL_DBG2
,
403 "movement: (%f, %f) -> (%f, %f)", x1
, y1
, x2
, y2
);
407 t
= render_priv
->time
- render_priv
->state
.event
->Start
;
413 k
= ((double) (t
- t1
)) / delta_t
;
414 x
= k
* (x2
- x1
) + x1
;
415 y
= k
* (y2
- y1
) + y1
;
416 if (render_priv
->state
.evt_type
!= EVENT_POSITIONED
) {
417 render_priv
->state
.pos_x
= x
;
418 render_priv
->state
.pos_y
= y
;
419 render_priv
->state
.detect_collisions
= 0;
420 render_priv
->state
.evt_type
= EVENT_POSITIONED
;
422 } else if (mystrcmp(&p
, "frx")) {
424 if (mystrtod(&p
, &val
)) {
426 render_priv
->state
.frx
=
427 val
* pwr
+ render_priv
->state
.frx
* (1 - pwr
);
429 render_priv
->state
.frx
= 0.;
430 } else if (mystrcmp(&p
, "fry")) {
432 if (mystrtod(&p
, &val
)) {
434 render_priv
->state
.fry
=
435 val
* pwr
+ render_priv
->state
.fry
* (1 - pwr
);
437 render_priv
->state
.fry
= 0.;
438 } else if (mystrcmp(&p
, "frz") || mystrcmp(&p
, "fr")) {
440 if (mystrtod(&p
, &val
)) {
442 render_priv
->state
.frz
=
443 val
* pwr
+ render_priv
->state
.frz
* (1 - pwr
);
445 render_priv
->state
.frz
=
446 M_PI
* render_priv
->state
.style
->Angle
/ 180.;
447 } else if (mystrcmp(&p
, "fn")) {
452 family
= malloc(p
- start
+ 1);
453 strncpy(family
, start
, p
- start
);
454 family
[p
- start
] = '\0';
456 family
= strdup(render_priv
->state
.style
->FontName
);
457 if (render_priv
->state
.family
)
458 free(render_priv
->state
.family
);
459 render_priv
->state
.family
= family
;
460 update_font(render_priv
);
461 } else if (mystrcmp(&p
, "alpha")) {
464 int hex
= render_priv
->track
->track_type
== TRACK_TYPE_ASS
;
465 if (strtocolor(render_priv
->library
, &p
, &val
, hex
)) {
466 unsigned char a
= val
>> 24;
467 for (i
= 0; i
< 4; ++i
)
468 change_alpha(&render_priv
->state
.c
[i
], a
, pwr
);
470 change_alpha(&render_priv
->state
.c
[0],
471 render_priv
->state
.style
->PrimaryColour
, pwr
);
472 change_alpha(&render_priv
->state
.c
[1],
473 render_priv
->state
.style
->SecondaryColour
, pwr
);
474 change_alpha(&render_priv
->state
.c
[2],
475 render_priv
->state
.style
->OutlineColour
, pwr
);
476 change_alpha(&render_priv
->state
.c
[3],
477 render_priv
->state
.style
->BackColour
, pwr
);
480 } else if (mystrcmp(&p
, "an")) {
482 if (mystrtoi(&p
, &val
) && val
) {
483 int v
= (val
- 1) / 3; // 0, 1 or 2 for vertical alignment
484 ass_msg(render_priv
->library
, MSGL_DBG2
, "an %d", val
);
487 val
= ((val
- 1) % 3) + 1; // horizontal alignment
489 ass_msg(render_priv
->library
, MSGL_DBG2
, "align %d", val
);
490 render_priv
->state
.alignment
= val
;
492 render_priv
->state
.alignment
=
493 render_priv
->state
.style
->Alignment
;
494 } else if (mystrcmp(&p
, "a")) {
496 if (mystrtoi(&p
, &val
) && val
)
497 // take care of a vsfilter quirk: handle illegal \a8 like \a5
498 render_priv
->state
.alignment
= (val
== 8) ? 5 : val
;
500 render_priv
->state
.alignment
=
501 render_priv
->state
.style
->Alignment
;
502 } else if (mystrcmp(&p
, "pos")) {
509 ass_msg(render_priv
->library
, MSGL_DBG2
, "pos(%f, %f)", v1
, v2
);
510 if (render_priv
->state
.evt_type
== EVENT_POSITIONED
) {
511 ass_msg(render_priv
->library
, MSGL_V
, "Subtitle has a new \\pos "
512 "after \\move or \\pos, ignoring");
514 render_priv
->state
.evt_type
= EVENT_POSITIONED
;
515 render_priv
->state
.detect_collisions
= 0;
516 render_priv
->state
.pos_x
= v1
;
517 render_priv
->state
.pos_y
= v2
;
519 } else if (mystrcmp(&p
, "fad")) {
521 long long t1
, t2
, t3
, t4
;
523 ++p
; // either \fad or \fade
529 // 2-argument version (\fad, according to specs)
530 // a1 and a2 are fade-in and fade-out durations
532 t4
= render_priv
->state
.event
->Duration
;
539 // 6-argument version (\fade)
540 // a1 and a2 (and a3) are opacity values
553 render_priv
->state
.fade
=
554 interpolate_alpha(render_priv
->time
-
555 render_priv
->state
.event
->Start
, t1
, t2
,
557 } else if (mystrcmp(&p
, "org")) {
564 ass_msg(render_priv
->library
, MSGL_DBG2
, "org(%d, %d)", v1
, v2
);
565 if (!render_priv
->state
.have_origin
) {
566 render_priv
->state
.org_x
= v1
;
567 render_priv
->state
.org_y
= v2
;
568 render_priv
->state
.have_origin
= 1;
569 render_priv
->state
.detect_collisions
= 0;
571 } else if (mystrcmp(&p
, "t")) {
576 long long t1
, t2
, t
, delta_t
;
579 for (cnt
= 0; cnt
< 3; ++cnt
) {
582 mystrtod(&p
, &v
[cnt
]);
587 v2
= (v
[1] < v1
) ? render_priv
->state
.event
->Duration
: v
[1];
589 } else if (cnt
== 2) {
591 v2
= (v
[1] < v1
) ? render_priv
->state
.event
->Duration
: v
[1];
593 } else if (cnt
== 1) {
595 v2
= render_priv
->state
.event
->Duration
;
599 v2
= render_priv
->state
.event
->Duration
;
602 render_priv
->state
.detect_collisions
= 0;
608 t
= render_priv
->time
- render_priv
->state
.event
->Start
; // FIXME: move to render_context
614 assert(delta_t
!= 0.);
615 k
= pow(((double) (t
- t1
)) / delta_t
, v3
);
618 p
= parse_tag(render_priv
, p
, k
); // maybe k*pwr ? no, specs forbid nested \t's
619 skip_to(')'); // in case there is some unknown tag or a comment
621 } else if (mystrcmp(&p
, "clip")) {
626 res
&= mystrtoi(&p
, &x0
);
628 res
&= mystrtoi(&p
, &y0
);
630 res
&= mystrtoi(&p
, &x1
);
632 res
&= mystrtoi(&p
, &y1
);
635 render_priv
->state
.clip_x0
=
636 render_priv
->state
.clip_x0
* (1 - pwr
) + x0
* pwr
;
637 render_priv
->state
.clip_x1
=
638 render_priv
->state
.clip_x1
* (1 - pwr
) + x1
* pwr
;
639 render_priv
->state
.clip_y0
=
640 render_priv
->state
.clip_y0
* (1 - pwr
) + y0
* pwr
;
641 render_priv
->state
.clip_y1
=
642 render_priv
->state
.clip_y1
* (1 - pwr
) + y1
* pwr
;
643 // Might be a vector clip
644 } else if (!render_priv
->state
.clip_drawing
) {
645 p
= parse_vector_clip(render_priv
, start
);
646 render_priv
->state
.clip_drawing_mode
= 0;
648 render_priv
->state
.clip_x0
= 0;
649 render_priv
->state
.clip_y0
= 0;
650 render_priv
->state
.clip_x1
= render_priv
->track
->PlayResX
;
651 render_priv
->state
.clip_y1
= render_priv
->track
->PlayResY
;
653 } else if (mystrcmp(&p
, "c")) {
655 int hex
= render_priv
->track
->track_type
== TRACK_TYPE_ASS
;
656 if (!strtocolor(render_priv
->library
, &p
, &val
, hex
))
657 val
= render_priv
->state
.style
->PrimaryColour
;
658 ass_msg(render_priv
->library
, MSGL_DBG2
, "color: %X", val
);
659 change_color(&render_priv
->state
.c
[0], val
, pwr
);
660 } else if ((*p
>= '1') && (*p
<= '4') && (++p
)
661 && (mystrcmp(&p
, "c") || mystrcmp(&p
, "a"))) {
666 int hex
= render_priv
->track
->track_type
== TRACK_TYPE_ASS
;
667 assert((n
>= '1') && (n
<= '4'));
668 if (!strtocolor(render_priv
->library
, &p
, &val
, hex
))
671 val
= render_priv
->state
.style
->PrimaryColour
;
674 val
= render_priv
->state
.style
->SecondaryColour
;
677 val
= render_priv
->state
.style
->OutlineColour
;
680 val
= render_priv
->state
.style
->BackColour
;
684 break; // impossible due to assert; avoid compilation warning
688 change_color(render_priv
->state
.c
+ cidx
, val
, pwr
);
691 change_alpha(render_priv
->state
.c
+ cidx
, val
>> 24, pwr
);
694 ass_msg(render_priv
->library
, MSGL_WARN
, "Bad command: %c%c",
698 ass_msg(render_priv
->library
, MSGL_DBG2
, "single c/a at %f: %c%c = %X",
699 pwr
, n
, cmd
, render_priv
->state
.c
[cidx
]);
700 } else if (mystrcmp(&p
, "r")) {
701 reset_render_context(render_priv
);
702 } else if (mystrcmp(&p
, "be")) {
704 if (mystrtoi(&p
, &val
)) {
705 // Clamp to a safe upper limit, since high values need excessive CPU
706 val
= (val
< 0) ? 0 : val
;
707 val
= (val
> MAX_BE
) ? MAX_BE
: val
;
708 render_priv
->state
.be
= val
;
710 render_priv
->state
.be
= 0;
711 } else if (mystrcmp(&p
, "b")) {
713 if (mystrtoi(&p
, &b
)) {
715 render_priv
->state
.bold
= b
;
717 render_priv
->state
.bold
= render_priv
->state
.style
->Bold
;
718 update_font(render_priv
);
719 } else if (mystrcmp(&p
, "i")) {
721 if (mystrtoi(&p
, &i
)) {
723 render_priv
->state
.italic
= i
;
725 render_priv
->state
.italic
= render_priv
->state
.style
->Italic
;
726 update_font(render_priv
);
727 } else if (mystrcmp(&p
, "kf") || mystrcmp(&p
, "K")) {
730 render_priv
->state
.effect_type
= EF_KARAOKE_KF
;
731 if (render_priv
->state
.effect_timing
)
732 render_priv
->state
.effect_skip_timing
+=
733 render_priv
->state
.effect_timing
;
734 render_priv
->state
.effect_timing
= val
* 10;
735 } else if (mystrcmp(&p
, "ko")) {
738 render_priv
->state
.effect_type
= EF_KARAOKE_KO
;
739 if (render_priv
->state
.effect_timing
)
740 render_priv
->state
.effect_skip_timing
+=
741 render_priv
->state
.effect_timing
;
742 render_priv
->state
.effect_timing
= val
* 10;
743 } else if (mystrcmp(&p
, "k")) {
746 render_priv
->state
.effect_type
= EF_KARAOKE
;
747 if (render_priv
->state
.effect_timing
)
748 render_priv
->state
.effect_skip_timing
+=
749 render_priv
->state
.effect_timing
;
750 render_priv
->state
.effect_timing
= val
* 10;
751 } else if (mystrcmp(&p
, "shad")) {
753 if (mystrtod(&p
, &val
)) {
754 if (render_priv
->state
.shadow_x
== render_priv
->state
.shadow_y
)
755 val
= render_priv
->state
.shadow_x
* (1 - pwr
) + val
* pwr
;
758 render_priv
->state
.shadow_x
= render_priv
->state
.shadow_y
= val
;
759 } else if (mystrcmp(&p
, "s")) {
761 if (mystrtoi(&p
, &val
) && val
)
762 render_priv
->state
.flags
|= DECO_STRIKETHROUGH
;
764 render_priv
->state
.flags
&= ~DECO_STRIKETHROUGH
;
765 } else if (mystrcmp(&p
, "u")) {
767 if (mystrtoi(&p
, &val
) && val
)
768 render_priv
->state
.flags
|= DECO_UNDERLINE
;
770 render_priv
->state
.flags
&= ~DECO_UNDERLINE
;
771 } else if (mystrcmp(&p
, "pbo")) {
773 if (mystrtod(&p
, &val
))
774 render_priv
->state
.drawing
->pbo
= val
;
775 } else if (mystrcmp(&p
, "p")) {
777 if (!mystrtoi(&p
, &val
))
780 render_priv
->state
.drawing
->scale
= val
;
781 render_priv
->state
.drawing_mode
= !!val
;
782 } else if (mystrcmp(&p
, "q")) {
784 if (!mystrtoi(&p
, &val
))
785 val
= render_priv
->track
->WrapStyle
;
786 render_priv
->state
.wrap_style
= val
;
792 void apply_transition_effects(ASS_Renderer
*render_priv
, ASS_Event
*event
)
796 char *p
= event
->Effect
;
802 while (cnt
< 4 && (p
= strchr(p
, ';'))) {
803 v
[cnt
++] = atoi(++p
);
806 if (strncmp(event
->Effect
, "Banner;", 7) == 0) {
809 ass_msg(render_priv
->library
, MSGL_V
,
810 "Error parsing effect: '%s'", event
->Effect
);
813 if (cnt
>= 2 && v
[1] == 0) // right-to-left
814 render_priv
->state
.scroll_direction
= SCROLL_RL
;
815 else // left-to-right
816 render_priv
->state
.scroll_direction
= SCROLL_LR
;
821 render_priv
->state
.scroll_shift
=
822 (render_priv
->time
- render_priv
->state
.event
->Start
) / delay
;
823 render_priv
->state
.evt_type
= EVENT_HSCROLL
;
827 if (strncmp(event
->Effect
, "Scroll up;", 10) == 0) {
828 render_priv
->state
.scroll_direction
= SCROLL_BT
;
829 } else if (strncmp(event
->Effect
, "Scroll down;", 12) == 0) {
830 render_priv
->state
.scroll_direction
= SCROLL_TB
;
832 ass_msg(render_priv
->library
, MSGL_V
,
833 "Unknown transition effect: '%s'", event
->Effect
);
836 // parse scroll up/down parameters
841 ass_msg(render_priv
->library
, MSGL_V
,
842 "Error parsing effect: '%s'", event
->Effect
);
848 render_priv
->state
.scroll_shift
=
849 (render_priv
->time
- render_priv
->state
.event
->Start
) / delay
;
858 y1
= render_priv
->track
->PlayResY
; // y0=y1=0 means fullscreen scrolling
859 render_priv
->state
.clip_y0
= y0
;
860 render_priv
->state
.clip_y1
= y1
;
861 render_priv
->state
.evt_type
= EVENT_VSCROLL
;
862 render_priv
->state
.detect_collisions
= 0;
868 * \brief Get next ucs4 char from string, parsing and executing style overrides
869 * \param str string pointer
870 * \return ucs4 code of the next char
871 * On return str points to the unparsed part of the string
873 unsigned get_next_char(ASS_Renderer
*render_priv
, char **str
)
877 if (*p
== '{') { // '\0' goes here
880 p
= parse_tag(render_priv
, p
, 1.);
881 if (*p
== '}') { // end of tag
888 } else if (*p
!= '\\')
889 ass_msg(render_priv
->library
, MSGL_V
,
890 "Unable to parse: '%.30s'", p
);
901 if ((p
[1] == 'N') || ((p
[1] == 'n') &&
902 (render_priv
->state
.wrap_style
== 2))) {
906 } else if (p
[1] == 'n') {
910 } else if (p
[1] == 'h') {
914 } else if (p
[1] == '{') {
918 } else if (p
[1] == '}') {
924 chr
= ass_utf8_get_char((char **) &p
);