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
.family
= strdup(render_priv
->state
.family
);
72 desc
.treat_family_as_pattern
=
73 render_priv
->state
.treat_family_as_pattern
;
75 val
= render_priv
->state
.bold
;
76 // 0 = normal, 1 = bold, >1 = exact weight
77 if (val
== 1 || val
== -1)
83 val
= render_priv
->state
.italic
;
84 if (val
== 1 || val
== -1)
90 render_priv
->state
.font
=
91 ass_font_new(render_priv
->cache
.font_cache
, render_priv
->library
,
92 render_priv
->ftlibrary
, render_priv
->fontconfig_priv
,
96 if (render_priv
->state
.font
)
97 change_font_size(render_priv
, render_priv
->state
.font_size
);
101 * \brief Change border width
102 * negative value resets border to style value
104 void change_border(ASS_Renderer
*render_priv
, double border_x
,
108 if (!render_priv
->state
.font
)
111 if (border_x
< 0 && border_y
< 0) {
112 if (render_priv
->state
.style
->BorderStyle
== 1 ||
113 render_priv
->state
.style
->BorderStyle
== 3)
114 border_x
= border_y
= render_priv
->state
.style
->Outline
;
116 border_x
= border_y
= 1.;
119 render_priv
->state
.border_x
= border_x
;
120 render_priv
->state
.border_y
= border_y
;
122 bord
= 64 * border_x
* render_priv
->border_scale
;
123 if (bord
> 0 && border_x
== border_y
) {
124 if (!render_priv
->state
.stroker
) {
127 FT_Stroker_New(render_priv
->ftlibrary
,
128 &render_priv
->state
.stroker
);
130 ass_msg(render_priv
->library
, MSGL_V
,
131 "failed to get stroker");
132 render_priv
->state
.stroker
= 0;
135 if (render_priv
->state
.stroker
)
136 FT_Stroker_Set(render_priv
->state
.stroker
, bord
,
137 FT_STROKER_LINECAP_ROUND
,
138 FT_STROKER_LINEJOIN_ROUND
, 0);
140 FT_Stroker_Done(render_priv
->state
.stroker
);
141 render_priv
->state
.stroker
= 0;
146 * \brief Calculate a weighted average of two colors
147 * calculates c1*(1-a) + c2*a, but separately for each component except alpha
149 static void change_color(uint32_t *var
, uint32_t new, double pwr
)
151 (*var
) = ((uint32_t) (_r(*var
) * (1 - pwr
) + _r(new) * pwr
) << 24) +
152 ((uint32_t) (_g(*var
) * (1 - pwr
) + _g(new) * pwr
) << 16) +
153 ((uint32_t) (_b(*var
) * (1 - pwr
) + _b(new) * pwr
) << 8) + _a(*var
);
156 // like change_color, but for alpha component only
157 inline void change_alpha(uint32_t *var
, uint32_t new, double pwr
)
160 (_r(*var
) << 24) + (_g(*var
) << 16) + (_b(*var
) << 8) +
161 (uint32_t) (_a(*var
) * (1 - pwr
) + _a(new) * pwr
);
165 * \brief Multiply two alpha values
166 * \param a first value
167 * \param b second value
168 * \return result of multiplication
169 * Parameters and result are limited by 0xFF.
171 inline uint32_t mult_alpha(uint32_t a
, uint32_t b
)
173 return 0xFF - (0xFF - a
) * (0xFF - b
) / 0xFF;
177 * \brief Calculate alpha value by piecewise linear function
178 * Used for \fad, \fade implementation.
181 interpolate_alpha(long long now
, long long t1
, long long t2
, long long t3
,
182 long long t4
, unsigned a1
, unsigned a2
, unsigned a3
)
188 } else if (now
>= t4
) {
190 } else if (now
< t2
) { // and > t1
191 cf
= ((double) (now
- t1
)) / (t2
- t1
);
192 a
= a1
* (1 - cf
) + a2
* cf
;
193 } else if (now
> t3
) {
194 cf
= ((double) (now
- t3
)) / (t4
- t3
);
195 a
= a2
* (1 - cf
) + a3
* cf
;
196 } else { // t2 <= now <= t3
204 * Parse a vector clip into an outline, using the proper scaling
205 * parameters. Translate it to correct for screen borders, if needed.
207 static char *parse_vector_clip(ASS_Renderer
*render_priv
, char *p
)
211 ASS_Drawing
*drawing
;
213 render_priv
->state
.clip_drawing
= ass_drawing_new(
214 render_priv
->fontconfig_priv
,
215 render_priv
->state
.font
,
216 render_priv
->settings
.hinting
,
217 render_priv
->ftlibrary
);
218 drawing
= render_priv
->state
.clip_drawing
;
220 res
= mystrtoi(&p
, &scale
);
224 drawing
->scale
= scale
;
225 drawing
->scale_x
= render_priv
->font_scale_x
* render_priv
->font_scale
;
226 drawing
->scale_y
= render_priv
->font_scale
;
227 while (*p
!= ')' && *p
!= '}' && p
!= 0)
228 ass_drawing_add_char(drawing
, *p
++);
230 if (ass_drawing_parse(drawing
, 1)) {
231 // We need to translate the clip according to screen borders
232 if (render_priv
->settings
.left_margin
!= 0 ||
233 render_priv
->settings
.top_margin
!= 0) {
235 .x
= int_to_d6(render_priv
->settings
.left_margin
),
236 .y
= -int_to_d6(render_priv
->settings
.top_margin
),
238 FT_Outline_Translate(&drawing
->glyph
->outline
, trans
.x
, trans
.y
);
240 ass_msg(render_priv
->library
, MSGL_DBG2
,
241 "Parsed vector clip: scale %d, scales (%f, %f) string [%s]\n",
242 scale
, drawing
->scale_x
, drawing
->scale_y
, drawing
->text
);
249 * \brief Parse style override tag.
250 * \param p string to parse
251 * \param pwr multiplier for some tag effects (comes from \t tags)
253 static char *parse_tag(ASS_Renderer
*render_priv
, char *p
, double pwr
)
257 if ((*p
== '}') || (*p
== 0))
260 // New tags introduced in vsfilter 2.39
261 if (mystrcmp(&p
, "xbord")) {
263 if (mystrtod(&p
, &val
))
264 val
= render_priv
->state
.border_x
* (1 - pwr
) + val
* pwr
;
267 change_border(render_priv
, val
, render_priv
->state
.border_y
);
268 } else if (mystrcmp(&p
, "ybord")) {
270 if (mystrtod(&p
, &val
))
271 val
= render_priv
->state
.border_y
* (1 - pwr
) + val
* pwr
;
274 change_border(render_priv
, render_priv
->state
.border_x
, val
);
275 } else if (mystrcmp(&p
, "xshad")) {
277 if (mystrtod(&p
, &val
))
278 val
= render_priv
->state
.shadow_x
* (1 - pwr
) + val
* pwr
;
281 render_priv
->state
.shadow_x
= val
;
282 } else if (mystrcmp(&p
, "yshad")) {
284 if (mystrtod(&p
, &val
))
285 val
= render_priv
->state
.shadow_y
* (1 - pwr
) + val
* pwr
;
288 render_priv
->state
.shadow_y
= val
;
289 } else if (mystrcmp(&p
, "fax")) {
291 if (mystrtod(&p
, &val
))
292 render_priv
->state
.fax
=
293 val
* pwr
+ render_priv
->state
.fax
* (1 - pwr
);
295 render_priv
->state
.fax
= 0.;
296 } else if (mystrcmp(&p
, "fay")) {
298 if (mystrtod(&p
, &val
))
299 render_priv
->state
.fay
=
300 val
* pwr
+ render_priv
->state
.fay
* (1 - pwr
);
302 render_priv
->state
.fay
= 0.;
303 } else if (mystrcmp(&p
, "iclip")) {
308 res
&= mystrtoi(&p
, &x0
);
310 res
&= mystrtoi(&p
, &y0
);
312 res
&= mystrtoi(&p
, &x1
);
314 res
&= mystrtoi(&p
, &y1
);
317 render_priv
->state
.clip_x0
=
318 render_priv
->state
.clip_x0
* (1 - pwr
) + x0
* pwr
;
319 render_priv
->state
.clip_x1
=
320 render_priv
->state
.clip_x1
* (1 - pwr
) + x1
* pwr
;
321 render_priv
->state
.clip_y0
=
322 render_priv
->state
.clip_y0
* (1 - pwr
) + y0
* pwr
;
323 render_priv
->state
.clip_y1
=
324 render_priv
->state
.clip_y1
* (1 - pwr
) + y1
* pwr
;
325 render_priv
->state
.clip_mode
= 1;
326 } else if (!render_priv
->state
.clip_drawing
) {
327 p
= parse_vector_clip(render_priv
, start
);
328 render_priv
->state
.clip_drawing_mode
= 1;
330 render_priv
->state
.clip_mode
= 0;
331 } else if (mystrcmp(&p
, "blur")) {
333 if (mystrtod(&p
, &val
)) {
334 val
= render_priv
->state
.blur
* (1 - pwr
) + val
* pwr
;
335 val
= (val
< 0) ? 0 : val
;
336 val
= (val
> BLUR_MAX_RADIUS
) ? BLUR_MAX_RADIUS
: val
;
337 render_priv
->state
.blur
= val
;
339 render_priv
->state
.blur
= 0.0;
341 } else if (mystrcmp(&p
, "fsc")) {
345 if (mystrtod(&p
, &val
)) {
347 render_priv
->state
.scale_x
=
348 render_priv
->state
.scale_x
* (1 - pwr
) + val
* pwr
;
350 render_priv
->state
.scale_x
=
351 render_priv
->state
.style
->ScaleX
;
352 } else if (tp
== 'y') {
353 if (mystrtod(&p
, &val
)) {
355 render_priv
->state
.scale_y
=
356 render_priv
->state
.scale_y
* (1 - pwr
) + val
* pwr
;
358 render_priv
->state
.scale_y
=
359 render_priv
->state
.style
->ScaleY
;
361 } else if (mystrcmp(&p
, "fsp")) {
363 if (mystrtod(&p
, &val
))
364 render_priv
->state
.hspacing
=
365 render_priv
->state
.hspacing
* (1 - pwr
) + val
* pwr
;
367 render_priv
->state
.hspacing
= render_priv
->state
.style
->Spacing
;
368 } else if (mystrcmp(&p
, "fs")) {
370 if (mystrtod(&p
, &val
))
371 val
= render_priv
->state
.font_size
* (1 - pwr
) + val
* pwr
;
373 val
= render_priv
->state
.style
->FontSize
;
374 if (render_priv
->state
.font
)
375 change_font_size(render_priv
, val
);
376 } else if (mystrcmp(&p
, "bord")) {
378 if (mystrtod(&p
, &val
)) {
379 if (render_priv
->state
.border_x
== render_priv
->state
.border_y
)
380 val
= render_priv
->state
.border_x
* (1 - pwr
) + val
* pwr
;
382 val
= -1.; // reset to default
383 change_border(render_priv
, val
, val
);
384 } else if (mystrcmp(&p
, "move")) {
385 double x1
, x2
, y1
, y2
;
386 long long t1
, t2
, delta_t
, t
;
402 ass_msg(render_priv
->library
, MSGL_DBG2
,
403 "movement6: (%f, %f) -> (%f, %f), (%" PRId64
" .. %"
404 PRId64
")\n", x1
, y1
, x2
, y2
, (int64_t) t1
,
408 t2
= render_priv
->state
.event
->Duration
;
409 ass_msg(render_priv
->library
, MSGL_DBG2
,
410 "movement: (%f, %f) -> (%f, %f)", x1
, y1
, x2
, y2
);
414 t
= render_priv
->time
- render_priv
->state
.event
->Start
;
420 k
= ((double) (t
- t1
)) / delta_t
;
421 x
= k
* (x2
- x1
) + x1
;
422 y
= k
* (y2
- y1
) + y1
;
423 if (render_priv
->state
.evt_type
!= EVENT_POSITIONED
) {
424 render_priv
->state
.pos_x
= x
;
425 render_priv
->state
.pos_y
= y
;
426 render_priv
->state
.detect_collisions
= 0;
427 render_priv
->state
.evt_type
= EVENT_POSITIONED
;
429 } else if (mystrcmp(&p
, "frx")) {
431 if (mystrtod(&p
, &val
)) {
433 render_priv
->state
.frx
=
434 val
* pwr
+ render_priv
->state
.frx
* (1 - pwr
);
436 render_priv
->state
.frx
= 0.;
437 } else if (mystrcmp(&p
, "fry")) {
439 if (mystrtod(&p
, &val
)) {
441 render_priv
->state
.fry
=
442 val
* pwr
+ render_priv
->state
.fry
* (1 - pwr
);
444 render_priv
->state
.fry
= 0.;
445 } else if (mystrcmp(&p
, "frz") || mystrcmp(&p
, "fr")) {
447 if (mystrtod(&p
, &val
)) {
449 render_priv
->state
.frz
=
450 val
* pwr
+ render_priv
->state
.frz
* (1 - pwr
);
452 render_priv
->state
.frz
=
453 M_PI
* render_priv
->state
.style
->Angle
/ 180.;
454 } else if (mystrcmp(&p
, "fn")) {
459 family
= malloc(p
- start
+ 1);
460 strncpy(family
, start
, p
- start
);
461 family
[p
- start
] = '\0';
463 family
= strdup(render_priv
->state
.style
->FontName
);
464 if (render_priv
->state
.family
)
465 free(render_priv
->state
.family
);
466 render_priv
->state
.family
= family
;
467 update_font(render_priv
);
468 } else if (mystrcmp(&p
, "alpha")) {
471 int hex
= render_priv
->track
->track_type
== TRACK_TYPE_ASS
;
472 if (strtocolor(render_priv
->library
, &p
, &val
, hex
)) {
473 unsigned char a
= val
>> 24;
474 for (i
= 0; i
< 4; ++i
)
475 change_alpha(&render_priv
->state
.c
[i
], a
, pwr
);
477 change_alpha(&render_priv
->state
.c
[0],
478 render_priv
->state
.style
->PrimaryColour
, pwr
);
479 change_alpha(&render_priv
->state
.c
[1],
480 render_priv
->state
.style
->SecondaryColour
, pwr
);
481 change_alpha(&render_priv
->state
.c
[2],
482 render_priv
->state
.style
->OutlineColour
, pwr
);
483 change_alpha(&render_priv
->state
.c
[3],
484 render_priv
->state
.style
->BackColour
, pwr
);
487 } else if (mystrcmp(&p
, "an")) {
489 if (mystrtoi(&p
, &val
) && val
) {
490 int v
= (val
- 1) / 3; // 0, 1 or 2 for vertical alignment
491 ass_msg(render_priv
->library
, MSGL_DBG2
, "an %d", val
);
494 val
= ((val
- 1) % 3) + 1; // horizontal alignment
496 ass_msg(render_priv
->library
, MSGL_DBG2
, "align %d", val
);
497 render_priv
->state
.alignment
= val
;
499 render_priv
->state
.alignment
=
500 render_priv
->state
.style
->Alignment
;
501 } else if (mystrcmp(&p
, "a")) {
503 if (mystrtoi(&p
, &val
) && val
)
504 // take care of a vsfilter quirk: handle illegal \a8 like \a5
505 render_priv
->state
.alignment
= (val
== 8) ? 5 : val
;
507 render_priv
->state
.alignment
=
508 render_priv
->state
.style
->Alignment
;
509 } else if (mystrcmp(&p
, "pos")) {
516 ass_msg(render_priv
->library
, MSGL_DBG2
, "pos(%f, %f)", v1
, v2
);
517 if (render_priv
->state
.evt_type
== EVENT_POSITIONED
) {
518 ass_msg(render_priv
->library
, MSGL_V
, "Subtitle has a new \\pos "
519 "after \\move or \\pos, ignoring");
521 render_priv
->state
.evt_type
= EVENT_POSITIONED
;
522 render_priv
->state
.detect_collisions
= 0;
523 render_priv
->state
.pos_x
= v1
;
524 render_priv
->state
.pos_y
= v2
;
526 } else if (mystrcmp(&p
, "fad")) {
528 long long t1
, t2
, t3
, t4
;
530 ++p
; // either \fad or \fade
536 // 2-argument version (\fad, according to specs)
537 // a1 and a2 are fade-in and fade-out durations
539 t4
= render_priv
->state
.event
->Duration
;
546 // 6-argument version (\fade)
547 // a1 and a2 (and a3) are opacity values
560 render_priv
->state
.fade
=
561 interpolate_alpha(render_priv
->time
-
562 render_priv
->state
.event
->Start
, t1
, t2
,
564 } else if (mystrcmp(&p
, "org")) {
571 ass_msg(render_priv
->library
, MSGL_DBG2
, "org(%d, %d)", v1
, v2
);
572 if (!render_priv
->state
.have_origin
) {
573 render_priv
->state
.org_x
= v1
;
574 render_priv
->state
.org_y
= v2
;
575 render_priv
->state
.have_origin
= 1;
576 render_priv
->state
.detect_collisions
= 0;
578 } else if (mystrcmp(&p
, "t")) {
583 long long t1
, t2
, t
, delta_t
;
586 for (cnt
= 0; cnt
< 3; ++cnt
) {
589 v
[cnt
] = strtod(p
, &p
);
594 v2
= (v
[1] < v1
) ? render_priv
->state
.event
->Duration
: v
[1];
596 } else if (cnt
== 2) {
598 v2
= (v
[1] < v1
) ? render_priv
->state
.event
->Duration
: v
[1];
600 } else if (cnt
== 1) {
602 v2
= render_priv
->state
.event
->Duration
;
606 v2
= render_priv
->state
.event
->Duration
;
609 render_priv
->state
.detect_collisions
= 0;
615 t
= render_priv
->time
- render_priv
->state
.event
->Start
; // FIXME: move to render_context
621 assert(delta_t
!= 0.);
622 k
= pow(((double) (t
- t1
)) / delta_t
, v3
);
625 p
= parse_tag(render_priv
, p
, k
); // maybe k*pwr ? no, specs forbid nested \t's
626 skip_to(')'); // in case there is some unknown tag or a comment
628 } else if (mystrcmp(&p
, "clip")) {
633 res
&= mystrtoi(&p
, &x0
);
635 res
&= mystrtoi(&p
, &y0
);
637 res
&= mystrtoi(&p
, &x1
);
639 res
&= mystrtoi(&p
, &y1
);
642 render_priv
->state
.clip_x0
=
643 render_priv
->state
.clip_x0
* (1 - pwr
) + x0
* pwr
;
644 render_priv
->state
.clip_x1
=
645 render_priv
->state
.clip_x1
* (1 - pwr
) + x1
* pwr
;
646 render_priv
->state
.clip_y0
=
647 render_priv
->state
.clip_y0
* (1 - pwr
) + y0
* pwr
;
648 render_priv
->state
.clip_y1
=
649 render_priv
->state
.clip_y1
* (1 - pwr
) + y1
* pwr
;
650 // Might be a vector clip
651 } else if (!render_priv
->state
.clip_drawing
) {
652 p
= parse_vector_clip(render_priv
, start
);
653 render_priv
->state
.clip_drawing_mode
= 0;
655 render_priv
->state
.clip_x0
= 0;
656 render_priv
->state
.clip_y0
= 0;
657 render_priv
->state
.clip_x1
= render_priv
->track
->PlayResX
;
658 render_priv
->state
.clip_y1
= render_priv
->track
->PlayResY
;
660 } else if (mystrcmp(&p
, "c")) {
662 int hex
= render_priv
->track
->track_type
== TRACK_TYPE_ASS
;
663 if (!strtocolor(render_priv
->library
, &p
, &val
, hex
))
664 val
= render_priv
->state
.style
->PrimaryColour
;
665 ass_msg(render_priv
->library
, MSGL_DBG2
, "color: %X", val
);
666 change_color(&render_priv
->state
.c
[0], val
, pwr
);
667 } else if ((*p
>= '1') && (*p
<= '4') && (++p
)
668 && (mystrcmp(&p
, "c") || mystrcmp(&p
, "a"))) {
673 int hex
= render_priv
->track
->track_type
== TRACK_TYPE_ASS
;
674 assert((n
>= '1') && (n
<= '4'));
675 if (!strtocolor(render_priv
->library
, &p
, &val
, hex
))
678 val
= render_priv
->state
.style
->PrimaryColour
;
681 val
= render_priv
->state
.style
->SecondaryColour
;
684 val
= render_priv
->state
.style
->OutlineColour
;
687 val
= render_priv
->state
.style
->BackColour
;
691 break; // impossible due to assert; avoid compilation warning
695 change_color(render_priv
->state
.c
+ cidx
, val
, pwr
);
698 change_alpha(render_priv
->state
.c
+ cidx
, val
>> 24, pwr
);
701 ass_msg(render_priv
->library
, MSGL_WARN
, "Bad command: %c%c",
705 ass_msg(render_priv
->library
, MSGL_DBG2
, "single c/a at %f: %c%c = %X",
706 pwr
, n
, cmd
, render_priv
->state
.c
[cidx
]);
707 } else if (mystrcmp(&p
, "r")) {
708 reset_render_context(render_priv
);
709 } else if (mystrcmp(&p
, "be")) {
711 if (mystrtoi(&p
, &val
)) {
712 // Clamp to a safe upper limit, since high values need excessive CPU
713 val
= (val
< 0) ? 0 : val
;
714 val
= (val
> MAX_BE
) ? MAX_BE
: val
;
715 render_priv
->state
.be
= val
;
717 render_priv
->state
.be
= 0;
718 } else if (mystrcmp(&p
, "b")) {
720 if (mystrtoi(&p
, &b
)) {
722 render_priv
->state
.bold
= b
;
724 render_priv
->state
.bold
= render_priv
->state
.style
->Bold
;
725 update_font(render_priv
);
726 } else if (mystrcmp(&p
, "i")) {
728 if (mystrtoi(&p
, &i
)) {
730 render_priv
->state
.italic
= i
;
732 render_priv
->state
.italic
= render_priv
->state
.style
->Italic
;
733 update_font(render_priv
);
734 } else if (mystrcmp(&p
, "kf") || mystrcmp(&p
, "K")) {
737 render_priv
->state
.effect_type
= EF_KARAOKE_KF
;
738 if (render_priv
->state
.effect_timing
)
739 render_priv
->state
.effect_skip_timing
+=
740 render_priv
->state
.effect_timing
;
741 render_priv
->state
.effect_timing
= val
* 10;
742 } else if (mystrcmp(&p
, "ko")) {
745 render_priv
->state
.effect_type
= EF_KARAOKE_KO
;
746 if (render_priv
->state
.effect_timing
)
747 render_priv
->state
.effect_skip_timing
+=
748 render_priv
->state
.effect_timing
;
749 render_priv
->state
.effect_timing
= val
* 10;
750 } else if (mystrcmp(&p
, "k")) {
753 render_priv
->state
.effect_type
= EF_KARAOKE
;
754 if (render_priv
->state
.effect_timing
)
755 render_priv
->state
.effect_skip_timing
+=
756 render_priv
->state
.effect_timing
;
757 render_priv
->state
.effect_timing
= val
* 10;
758 } else if (mystrcmp(&p
, "shad")) {
760 if (mystrtod(&p
, &val
)) {
761 if (render_priv
->state
.shadow_x
== render_priv
->state
.shadow_y
)
762 val
= render_priv
->state
.shadow_x
* (1 - pwr
) + val
* pwr
;
765 render_priv
->state
.shadow_x
= render_priv
->state
.shadow_y
= val
;
766 } else if (mystrcmp(&p
, "s")) {
768 if (mystrtoi(&p
, &val
) && val
)
769 render_priv
->state
.flags
|= DECO_STRIKETHROUGH
;
771 render_priv
->state
.flags
&= ~DECO_STRIKETHROUGH
;
772 } else if (mystrcmp(&p
, "u")) {
774 if (mystrtoi(&p
, &val
) && val
)
775 render_priv
->state
.flags
|= DECO_UNDERLINE
;
777 render_priv
->state
.flags
&= ~DECO_UNDERLINE
;
778 } else if (mystrcmp(&p
, "pbo")) {
780 if (mystrtod(&p
, &val
))
781 render_priv
->state
.drawing
->pbo
= val
;
782 } else if (mystrcmp(&p
, "p")) {
784 if (!mystrtoi(&p
, &val
))
787 render_priv
->state
.drawing
->scale
= val
;
788 render_priv
->state
.drawing_mode
= !!val
;
789 } else if (mystrcmp(&p
, "q")) {
791 if (!mystrtoi(&p
, &val
))
792 val
= render_priv
->track
->WrapStyle
;
793 render_priv
->state
.wrap_style
= val
;
799 void apply_transition_effects(ASS_Renderer
*render_priv
, ASS_Event
*event
)
803 char *p
= event
->Effect
;
809 while (cnt
< 4 && (p
= strchr(p
, ';'))) {
810 v
[cnt
++] = atoi(++p
);
813 if (strncmp(event
->Effect
, "Banner;", 7) == 0) {
816 ass_msg(render_priv
->library
, MSGL_V
,
817 "Error parsing effect: '%s'", event
->Effect
);
820 if (cnt
>= 2 && v
[1] == 0) // right-to-left
821 render_priv
->state
.scroll_direction
= SCROLL_RL
;
822 else // left-to-right
823 render_priv
->state
.scroll_direction
= SCROLL_LR
;
828 render_priv
->state
.scroll_shift
=
829 (render_priv
->time
- render_priv
->state
.event
->Start
) / delay
;
830 render_priv
->state
.evt_type
= EVENT_HSCROLL
;
834 if (strncmp(event
->Effect
, "Scroll up;", 10) == 0) {
835 render_priv
->state
.scroll_direction
= SCROLL_BT
;
836 } else if (strncmp(event
->Effect
, "Scroll down;", 12) == 0) {
837 render_priv
->state
.scroll_direction
= SCROLL_TB
;
839 ass_msg(render_priv
->library
, MSGL_V
,
840 "Unknown transition effect: '%s'", event
->Effect
);
843 // parse scroll up/down parameters
848 ass_msg(render_priv
->library
, MSGL_V
,
849 "Error parsing effect: '%s'", event
->Effect
);
855 render_priv
->state
.scroll_shift
=
856 (render_priv
->time
- render_priv
->state
.event
->Start
) / delay
;
865 y1
= render_priv
->track
->PlayResY
; // y0=y1=0 means fullscreen scrolling
866 render_priv
->state
.clip_y0
= y0
;
867 render_priv
->state
.clip_y1
= y1
;
868 render_priv
->state
.evt_type
= EVENT_VSCROLL
;
869 render_priv
->state
.detect_collisions
= 0;
875 * \brief Get next ucs4 char from string, parsing and executing style overrides
876 * \param str string pointer
877 * \return ucs4 code of the next char
878 * On return str points to the unparsed part of the string
880 unsigned get_next_char(ASS_Renderer
*render_priv
, char **str
)
884 if (*p
== '{') { // '\0' goes here
887 p
= parse_tag(render_priv
, p
, 1.);
888 if (*p
== '}') { // end of tag
895 } else if (*p
!= '\\')
896 ass_msg(render_priv
->library
, MSGL_V
,
897 "Unable to parse: '%s'", p
);
908 if ((p
[1] == 'N') || ((p
[1] == 'n') &&
909 (render_priv
->state
.wrap_style
== 2))) {
913 } else if (p
[1] == 'n') {
917 } else if (p
[1] == 'h') {
923 chr
= ass_utf8_get_char((char **) &p
);