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 Change border width
109 * negative value resets border to style value
111 void change_border(ASS_Renderer *render_priv, double border_x,
115 if (!render_priv->state.font)
118 if (border_x < 0 && border_y < 0) {
119 if (render_priv->state.style->BorderStyle == 1 ||
120 render_priv->state.style->BorderStyle == 3)
121 border_x = border_y = render_priv->state.style->Outline;
123 border_x = border_y = 1.;
126 render_priv->state.border_x = border_x;
127 render_priv->state.border_y = border_y;
129 bord = 64 * border_x * render_priv->border_scale;
130 if (bord > 0 && border_x == border_y) {
131 if (!render_priv->state.stroker) {
134 FT_Stroker_New(render_priv->ftlibrary,
135 &render_priv->state.stroker);
137 ass_msg(render_priv->library, MSGL_V,
138 "failed to get stroker");
139 render_priv->state.stroker = 0;
142 if (render_priv->state.stroker)
143 FT_Stroker_Set(render_priv->state.stroker, bord,
144 FT_STROKER_LINECAP_ROUND,
145 FT_STROKER_LINEJOIN_ROUND, 0);
147 FT_Stroker_Done(render_priv->state.stroker);
148 render_priv->state.stroker = 0;
153 * \brief Calculate a weighted average of two colors
154 * calculates c1*(1-a) + c2*a, but separately for each component except alpha
156 static void change_color(uint32_t *var, uint32_t new, double pwr)
158 (*var) = ((uint32_t) (_r(*var) * (1 - pwr) + _r(new) * pwr) << 24) +
159 ((uint32_t) (_g(*var) * (1 - pwr) + _g(new) * pwr) << 16) +
160 ((uint32_t) (_b(*var) * (1 - pwr) + _b(new) * pwr) << 8) + _a(*var);
163 // like change_color, but for alpha component only
164 inline void change_alpha(uint32_t *var, uint32_t new, double pwr)
167 (_r(*var) << 24) + (_g(*var) << 16) + (_b(*var) << 8) +
168 (uint32_t) (_a(*var) * (1 - pwr) + _a(new) * pwr);
172 * \brief Multiply two alpha values
173 * \param a first value
174 * \param b second value
175 * \return result of multiplication
176 * Parameters and result are limited by 0xFF.
178 inline uint32_t mult_alpha(uint32_t a, uint32_t b)
180 return 0xFF - (0xFF - a) * (0xFF - b) / 0xFF;
184 * \brief Calculate alpha value by piecewise linear function
185 * Used for \fad, \fade implementation.
188 interpolate_alpha(long long now, long long t1, long long t2, long long t3,
189 long long t4, unsigned a1, unsigned a2, unsigned a3)
196 } else if (now >= t4) {
198 } else if (now < t2 && t2 > t1) {
199 cf = ((double) (now - t1)) / (t2 - t1);
200 a = a1 * (1 - cf) + a2 * cf;
201 } else if (now >= t3 && t4 > t3) {
202 cf = ((double) (now - t3)) / (t4 - t3);
203 a = a2 * (1 - cf) + a3 * cf;
204 } else { // t2 <= now < t3
212 * Parse a vector clip into an outline, using the proper scaling
213 * parameters. Translate it to correct for screen borders, if needed.
215 static char *parse_vector_clip(ASS_Renderer *render_priv, char *p)
219 ASS_Drawing *drawing = render_priv->state.clip_drawing;
221 ass_drawing_free(drawing);
222 render_priv->state.clip_drawing =
223 ass_drawing_new(render_priv->library, render_priv->ftlibrary);
224 drawing = render_priv->state.clip_drawing;
226 res = mystrtoi(&p, &scale);
230 drawing->scale = scale;
231 drawing->scale_x = render_priv->font_scale_x * render_priv->font_scale;
232 drawing->scale_y = render_priv->font_scale;
233 while (*p != ')' && *p != '}' && p != 0)
234 ass_drawing_add_char(drawing, *p++);
241 * \brief Parse style override tag.
242 * \param p string to parse
243 * \param pwr multiplier for some tag effects (comes from \t tags)
245 static char *parse_tag(ASS_Renderer *render_priv, char *p, double pwr)
249 if ((*p == '}') || (*p == 0))
252 // New tags introduced in vsfilter 2.39
253 if (mystrcmp(&p, "xbord")) {
255 if (mystrtod(&p, &val))
256 val = render_priv->state.border_x * (1 - pwr) + val * pwr;
259 change_border(render_priv, val, render_priv->state.border_y);
260 render_priv->state.bm_run_id++;
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 render_priv->state.bm_run_id++;
276 } else if (mystrcmp(&p, "yshad")) {
278 if (mystrtod(&p, &val))
279 val = render_priv->state.shadow_y * (1 - pwr) + val * pwr;
282 render_priv->state.shadow_y = val;
283 render_priv->state.bm_run_id++;
284 } else if (mystrcmp(&p, "fax")) {
286 if (mystrtod(&p, &val))
287 render_priv->state.fax =
288 val * pwr + render_priv->state.fax * (1 - pwr);
290 render_priv->state.fax = 0.;
291 } else if (mystrcmp(&p, "fay")) {
293 if (mystrtod(&p, &val))
294 render_priv->state.fay =
295 val * pwr + render_priv->state.fay * (1 - pwr);
297 render_priv->state.fay = 0.;
298 } else if (mystrcmp(&p, "iclip")) {
303 res &= mystrtoi(&p, &x0);
305 res &= mystrtoi(&p, &y0);
307 res &= mystrtoi(&p, &x1);
309 res &= mystrtoi(&p, &y1);
312 render_priv->state.clip_x0 =
313 render_priv->state.clip_x0 * (1 - pwr) + x0 * pwr;
314 render_priv->state.clip_x1 =
315 render_priv->state.clip_x1 * (1 - pwr) + x1 * pwr;
316 render_priv->state.clip_y0 =
317 render_priv->state.clip_y0 * (1 - pwr) + y0 * pwr;
318 render_priv->state.clip_y1 =
319 render_priv->state.clip_y1 * (1 - pwr) + y1 * pwr;
320 render_priv->state.clip_mode = 1;
321 } else if (!render_priv->state.clip_drawing) {
322 p = parse_vector_clip(render_priv, start);
323 render_priv->state.clip_drawing_mode = 1;
325 render_priv->state.clip_mode = 0;
326 } else if (mystrcmp(&p, "blur")) {
328 if (mystrtod(&p, &val)) {
329 val = render_priv->state.blur * (1 - pwr) + val * pwr;
330 val = (val < 0) ? 0 : val;
331 val = (val > BLUR_MAX_RADIUS) ? BLUR_MAX_RADIUS : val;
332 render_priv->state.blur = val;
334 render_priv->state.blur = 0.0;
335 render_priv->state.bm_run_id++;
337 } else if (mystrcmp(&p, "fsc")) {
341 if (mystrtod(&p, &val)) {
343 render_priv->state.scale_x =
344 render_priv->state.scale_x * (1 - pwr) + val * pwr;
346 render_priv->state.scale_x =
347 render_priv->state.style->ScaleX;
348 } else if (tp == 'y') {
349 if (mystrtod(&p, &val)) {
351 render_priv->state.scale_y =
352 render_priv->state.scale_y * (1 - pwr) + val * pwr;
354 render_priv->state.scale_y =
355 render_priv->state.style->ScaleY;
357 } else if (mystrcmp(&p, "fsp")) {
359 if (mystrtod(&p, &val))
360 render_priv->state.hspacing =
361 render_priv->state.hspacing * (1 - pwr) + val * pwr;
363 render_priv->state.hspacing = render_priv->state.style->Spacing;
364 } else if (mystrcmp(&p, "fs+")) {
366 if (mystrtod(&p, &val)) {
367 val = render_priv->state.font_size + pwr * val;
369 val = render_priv->state.style->FontSize;
370 if (render_priv->state.font)
371 change_font_size(render_priv, val);
372 } else if (mystrcmp(&p, "fs-")) {
374 if (mystrtod(&p, &val))
375 val = render_priv->state.font_size - pwr * val;
377 val = render_priv->state.style->FontSize;
378 if (render_priv->state.font)
379 change_font_size(render_priv, val);
380 } else if (mystrcmp(&p, "fs")) {
382 if (mystrtod(&p, &val))
383 val = render_priv->state.font_size * (1 - pwr) + val * pwr;
385 val = render_priv->state.style->FontSize;
386 if (render_priv->state.font)
387 change_font_size(render_priv, val);
388 } else if (mystrcmp(&p, "bord")) {
390 if (mystrtod(&p, &val)) {
391 if (render_priv->state.border_x == render_priv->state.border_y)
392 val = render_priv->state.border_x * (1 - pwr) + val * pwr;
394 val = -1.; // reset to default
395 change_border(render_priv, val, val);
396 render_priv->state.bm_run_id++;
397 } else if (mystrcmp(&p, "move")) {
398 double x1, x2, y1, y2;
399 long long t1, t2, delta_t, t;
415 ass_msg(render_priv->library, MSGL_DBG2,
416 "movement6: (%f, %f) -> (%f, %f), (%" PRId64 " .. %"
417 PRId64 ")\n", x1, y1, x2, y2, (int64_t) t1,
421 t2 = render_priv->state.event->Duration;
422 ass_msg(render_priv->library, MSGL_DBG2,
423 "movement: (%f, %f) -> (%f, %f)", x1, y1, x2, y2);
427 t = render_priv->time - render_priv->state.event->Start;
433 k = ((double) (t - t1)) / delta_t;
434 x = k * (x2 - x1) + x1;
435 y = k * (y2 - y1) + y1;
436 if (render_priv->state.evt_type != EVENT_POSITIONED) {
437 render_priv->state.pos_x = x;
438 render_priv->state.pos_y = y;
439 render_priv->state.detect_collisions = 0;
440 render_priv->state.evt_type = EVENT_POSITIONED;
442 } else if (mystrcmp(&p, "frx")) {
444 if (mystrtod(&p, &val)) {
446 render_priv->state.frx =
447 val * pwr + render_priv->state.frx * (1 - pwr);
449 render_priv->state.frx = 0.;
450 } else if (mystrcmp(&p, "fry")) {
452 if (mystrtod(&p, &val)) {
454 render_priv->state.fry =
455 val * pwr + render_priv->state.fry * (1 - pwr);
457 render_priv->state.fry = 0.;
458 } else if (mystrcmp(&p, "frz") || mystrcmp(&p, "fr")) {
460 if (mystrtod(&p, &val)) {
462 render_priv->state.frz =
463 val * pwr + render_priv->state.frz * (1 - pwr);
465 render_priv->state.frz =
466 M_PI * render_priv->state.style->Angle / 180.;
467 } else if (mystrcmp(&p, "fn")) {
472 family = malloc(p - start + 1);
473 strncpy(family, start, p - start);
474 family[p - start] = '\0';
476 family = strdup(render_priv->state.style->FontName);
477 free(render_priv->state.family);
478 render_priv->state.family = family;
479 update_font(render_priv);
480 } else if (mystrcmp(&p, "alpha")) {
483 int hex = render_priv->track->track_type == TRACK_TYPE_ASS;
484 if (strtocolor(render_priv->library, &p, &val, hex)) {
485 unsigned char a = val >> 24;
486 for (i = 0; i < 4; ++i)
487 change_alpha(&render_priv->state.c[i], a, pwr);
489 change_alpha(&render_priv->state.c[0],
490 render_priv->state.style->PrimaryColour, pwr);
491 change_alpha(&render_priv->state.c[1],
492 render_priv->state.style->SecondaryColour, pwr);
493 change_alpha(&render_priv->state.c[2],
494 render_priv->state.style->OutlineColour, pwr);
495 change_alpha(&render_priv->state.c[3],
496 render_priv->state.style->BackColour, pwr);
498 render_priv->state.bm_run_id++;
500 } else if (mystrcmp(&p, "an")) {
502 if (mystrtoi(&p, &val) && val) {
503 int v = (val - 1) / 3; // 0, 1 or 2 for vertical alignment
504 ass_msg(render_priv->library, MSGL_DBG2, "an %d", val);
507 val = ((val - 1) % 3) + 1; // horizontal alignment
509 ass_msg(render_priv->library, MSGL_DBG2, "align %d", val);
510 if ((render_priv->state.parsed_tags & PARSED_A) == 0) {
511 render_priv->state.alignment = val;
512 render_priv->state.parsed_tags |= PARSED_A;
515 render_priv->state.alignment =
516 render_priv->state.style->Alignment;
517 } else if (mystrcmp(&p, "a")) {
519 if (mystrtoi(&p, &val) && val) {
520 if ((render_priv->state.parsed_tags & PARSED_A) == 0) {
521 // take care of a vsfilter quirk: handle illegal \a8 like \a5
522 render_priv->state.alignment = (val == 8) ? 5 : 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, "pos")) {
535 ass_msg(render_priv->library, MSGL_DBG2, "pos(%f, %f)", v1, v2);
536 if (render_priv->state.evt_type == EVENT_POSITIONED) {
537 ass_msg(render_priv->library, MSGL_V, "Subtitle has a new \\pos "
538 "after \\move or \\pos, ignoring");
540 render_priv->state.evt_type = EVENT_POSITIONED;
541 render_priv->state.detect_collisions = 0;
542 render_priv->state.pos_x = v1;
543 render_priv->state.pos_y = v2;
545 } else if (mystrcmp(&p, "fad")) {
547 long long t1, t2, t3, t4;
549 ++p; // either \fad or \fade
555 // 2-argument version (\fad, according to specs)
556 // a1 and a2 are fade-in and fade-out durations
558 t4 = render_priv->state.event->Duration;
565 // 6-argument version (\fade)
566 // a1 and a2 (and a3) are opacity values
579 if ((render_priv->state.parsed_tags & PARSED_FADE) == 0) {
580 render_priv->state.fade =
581 interpolate_alpha(render_priv->time -
582 render_priv->state.event->Start, t1, t2,
584 render_priv->state.parsed_tags |= PARSED_FADE;
586 } else if (mystrcmp(&p, "org")) {
593 ass_msg(render_priv->library, MSGL_DBG2, "org(%d, %d)", v1, v2);
594 if (!render_priv->state.have_origin) {
595 render_priv->state.org_x = v1;
596 render_priv->state.org_y = v2;
597 render_priv->state.have_origin = 1;
598 render_priv->state.detect_collisions = 0;
600 } else if (mystrcmp(&p, "t")) {
605 long long t1, t2, t, delta_t;
608 for (cnt = 0; cnt < 3; ++cnt) {
611 mystrtod(&p, &v[cnt]);
616 v2 = (v[1] < v1) ? render_priv->state.event->Duration : v[1];
618 } else if (cnt == 2) {
620 v2 = (v[1] < v1) ? render_priv->state.event->Duration : v[1];
622 } else if (cnt == 1) {
624 v2 = render_priv->state.event->Duration;
628 v2 = render_priv->state.event->Duration;
631 render_priv->state.detect_collisions = 0;
637 t = render_priv->time - render_priv->state.event->Start; // FIXME: move to render_context
643 assert(delta_t != 0.);
644 k = pow(((double) (t - t1)) / delta_t, v3);
647 p = parse_tag(render_priv, p, k); // maybe k*pwr ? no, specs forbid nested \t's
648 skip_to(')'); // in case there is some unknown tag or a comment
650 } else if (mystrcmp(&p, "clip")) {
655 res &= mystrtoi(&p, &x0);
657 res &= mystrtoi(&p, &y0);
659 res &= mystrtoi(&p, &x1);
661 res &= mystrtoi(&p, &y1);
664 render_priv->state.clip_x0 =
665 render_priv->state.clip_x0 * (1 - pwr) + x0 * pwr;
666 render_priv->state.clip_x1 =
667 render_priv->state.clip_x1 * (1 - pwr) + x1 * pwr;
668 render_priv->state.clip_y0 =
669 render_priv->state.clip_y0 * (1 - pwr) + y0 * pwr;
670 render_priv->state.clip_y1 =
671 render_priv->state.clip_y1 * (1 - pwr) + y1 * pwr;
672 // Might be a vector clip
673 } else if (!render_priv->state.clip_drawing) {
674 p = parse_vector_clip(render_priv, start);
675 render_priv->state.clip_drawing_mode = 0;
677 render_priv->state.clip_x0 = 0;
678 render_priv->state.clip_y0 = 0;
679 render_priv->state.clip_x1 = render_priv->track->PlayResX;
680 render_priv->state.clip_y1 = render_priv->track->PlayResY;
682 } else if (mystrcmp(&p, "c")) {
684 int hex = render_priv->track->track_type == TRACK_TYPE_ASS;
685 if (!strtocolor(render_priv->library, &p, &val, hex))
686 val = render_priv->state.style->PrimaryColour;
687 ass_msg(render_priv->library, MSGL_DBG2, "color: %X", val);
688 change_color(&render_priv->state.c[0], val, pwr);
689 render_priv->state.bm_run_id++;
690 } else if ((*p >= '1') && (*p <= '4') && (++p)
691 && (mystrcmp(&p, "c") || mystrcmp(&p, "a"))) {
696 int hex = render_priv->track->track_type == TRACK_TYPE_ASS;
697 assert((n >= '1') && (n <= '4'));
698 if (!strtocolor(render_priv->library, &p, &val, hex))
701 val = render_priv->state.style->PrimaryColour;
704 val = render_priv->state.style->SecondaryColour;
707 val = render_priv->state.style->OutlineColour;
710 val = render_priv->state.style->BackColour;
714 break; // impossible due to assert; avoid compilation warning
718 change_color(render_priv->state.c + cidx, val, pwr);
719 render_priv->state.bm_run_id++;
722 change_alpha(render_priv->state.c + cidx, val >> 24, pwr);
723 render_priv->state.bm_run_id++;
726 ass_msg(render_priv->library, MSGL_WARN, "Bad command: %c%c",
730 ass_msg(render_priv->library, MSGL_DBG2, "single c/a at %f: %c%c = %X",
731 pwr, n, cmd, render_priv->state.c[cidx]);
732 } else if (mystrcmp(&p, "r")) {
733 reset_render_context(render_priv);
734 } else if (mystrcmp(&p, "be")) {
736 if (mystrtoi(&p, &val)) {
737 // Clamp to a safe upper limit, since high values need excessive CPU
738 val = (val < 0) ? 0 : val;
739 val = (val > MAX_BE) ? MAX_BE : val;
740 render_priv->state.be = val;
742 render_priv->state.be = 0;
743 render_priv->state.bm_run_id++;
744 } else if (mystrcmp(&p, "b")) {
746 if (mystrtoi(&p, &b)) {
748 render_priv->state.bold = b;
750 render_priv->state.bold = render_priv->state.style->Bold;
751 update_font(render_priv);
752 } else if (mystrcmp(&p, "i")) {
754 if (mystrtoi(&p, &i)) {
756 render_priv->state.italic = i;
758 render_priv->state.italic = render_priv->state.style->Italic;
759 update_font(render_priv);
760 } else if (mystrcmp(&p, "kf") || mystrcmp(&p, "K")) {
763 render_priv->state.effect_type = EF_KARAOKE_KF;
764 if (render_priv->state.effect_timing)
765 render_priv->state.effect_skip_timing +=
766 render_priv->state.effect_timing;
767 render_priv->state.effect_timing = val * 10;
768 } else if (mystrcmp(&p, "ko")) {
771 render_priv->state.effect_type = EF_KARAOKE_KO;
772 if (render_priv->state.effect_timing)
773 render_priv->state.effect_skip_timing +=
774 render_priv->state.effect_timing;
775 render_priv->state.effect_timing = val * 10;
776 } else if (mystrcmp(&p, "k")) {
779 render_priv->state.effect_type = EF_KARAOKE;
780 if (render_priv->state.effect_timing)
781 render_priv->state.effect_skip_timing +=
782 render_priv->state.effect_timing;
783 render_priv->state.effect_timing = val * 10;
784 } else if (mystrcmp(&p, "shad")) {
786 if (mystrtod(&p, &val)) {
787 if (render_priv->state.shadow_x == render_priv->state.shadow_y)
788 val = render_priv->state.shadow_x * (1 - pwr) + val * pwr;
791 render_priv->state.shadow_x = render_priv->state.shadow_y = val;
792 render_priv->state.bm_run_id++;
793 } else if (mystrcmp(&p, "s")) {
795 if (mystrtoi(&p, &val) && val)
796 render_priv->state.flags |= DECO_STRIKETHROUGH;
798 render_priv->state.flags &= ~DECO_STRIKETHROUGH;
799 render_priv->state.bm_run_id++;
800 } else if (mystrcmp(&p, "u")) {
802 if (mystrtoi(&p, &val) && val)
803 render_priv->state.flags |= DECO_UNDERLINE;
805 render_priv->state.flags &= ~DECO_UNDERLINE;
806 render_priv->state.bm_run_id++;
807 } else if (mystrcmp(&p, "pbo")) {
809 if (mystrtod(&p, &val))
810 render_priv->state.drawing->pbo = val;
811 } else if (mystrcmp(&p, "p")) {
813 if (!mystrtoi(&p, &val))
816 render_priv->state.drawing->scale = val;
817 render_priv->state.drawing_mode = !!val;
818 } else if (mystrcmp(&p, "q")) {
820 if (!mystrtoi(&p, &val))
821 val = render_priv->track->WrapStyle;
822 render_priv->state.wrap_style = val;
823 } else if (mystrcmp(&p, "fe")) {
825 if (!mystrtoi(&p, &val))
826 val = render_priv->state.style->Encoding;
827 render_priv->state.font_encoding = val;
833 void apply_transition_effects(ASS_Renderer *render_priv, ASS_Event *event)
837 char *p = event->Effect;
843 while (cnt < 4 && (p = strchr(p, ';'))) {
844 v[cnt++] = atoi(++p);
847 if (strncmp(event->Effect, "Banner;", 7) == 0) {
850 ass_msg(render_priv->library, MSGL_V,
851 "Error parsing effect: '%s'", event->Effect);
854 if (cnt >= 2 && v[1] == 0) // right-to-left
855 render_priv->state.scroll_direction = SCROLL_RL;
856 else // left-to-right
857 render_priv->state.scroll_direction = SCROLL_LR;
862 render_priv->state.scroll_shift =
863 (render_priv->time - render_priv->state.event->Start) / delay;
864 render_priv->state.evt_type = EVENT_HSCROLL;
868 if (strncmp(event->Effect, "Scroll up;", 10) == 0) {
869 render_priv->state.scroll_direction = SCROLL_BT;
870 } else if (strncmp(event->Effect, "Scroll down;", 12) == 0) {
871 render_priv->state.scroll_direction = SCROLL_TB;
873 ass_msg(render_priv->library, MSGL_DBG2,
874 "Unknown transition effect: '%s'", event->Effect);
877 // parse scroll up/down parameters
882 ass_msg(render_priv->library, MSGL_V,
883 "Error parsing effect: '%s'", event->Effect);
889 render_priv->state.scroll_shift =
890 (render_priv->time - render_priv->state.event->Start) / delay;
899 y1 = render_priv->track->PlayResY; // y0=y1=0 means fullscreen scrolling
900 render_priv->state.clip_y0 = y0;
901 render_priv->state.clip_y1 = y1;
902 render_priv->state.evt_type = EVENT_VSCROLL;
903 render_priv->state.detect_collisions = 0;
909 * \brief determine karaoke effects
910 * Karaoke effects cannot be calculated during parse stage (get_next_char()),
911 * so they are done in a separate step.
912 * Parse stage: when karaoke style override is found, its parameters are stored in the next glyph's
913 * (the first glyph of the karaoke word)'s effect_type and effect_timing.
915 * 1. sets effect_type for all glyphs in the word (_karaoke_ word)
916 * 2. sets effect_timing for all glyphs to x coordinate of the border line between the left and right karaoke parts
917 * (left part is filled with PrimaryColour, right one - with SecondaryColour).
919 void process_karaoke_effects(ASS_Renderer *render_priv)
921 GlyphInfo *cur, *cur2;
922 GlyphInfo *s1, *e1; // start and end of the current word
923 GlyphInfo *s2; // start of the next word
925 int timing; // current timing
926 int tm_start, tm_end; // timings at start and end of the current word
932 tm_current = render_priv->time - render_priv->state.event->Start;
935 for (i = 0; i <= render_priv->text_info.length; ++i) {
936 cur = render_priv->text_info.glyphs + i;
937 if ((i == render_priv->text_info.length)
938 || (cur->effect_type != EF_NONE)) {
943 tm_start = timing + s1->effect_skip_timing;
944 tm_end = tm_start + s1->effect_timing;
948 for (cur2 = s1; cur2 <= e1; ++cur2) {
949 x_start = FFMIN(x_start, d6_to_int(cur2->bbox.xMin + cur2->pos.x));
950 x_end = FFMAX(x_end, d6_to_int(cur2->bbox.xMax + cur2->pos.x));
953 dt = (tm_current - tm_start);
954 if ((s1->effect_type == EF_KARAOKE)
955 || (s1->effect_type == EF_KARAOKE_KO)) {
960 } else if (s1->effect_type == EF_KARAOKE_KF) {
961 dt /= (tm_end - tm_start);
962 x = x_start + (x_end - x_start) * dt;
964 ass_msg(render_priv->library, MSGL_ERR,
965 "Unknown effect type");
969 for (cur2 = s1; cur2 <= e1; ++cur2) {
970 cur2->effect_type = s1->effect_type;
971 cur2->effect_timing = x - d6_to_int(cur2->pos.x);
980 * \brief Get next ucs4 char from string, parsing and executing style overrides
981 * \param str string pointer
982 * \return ucs4 code of the next char
983 * On return str points to the unparsed part of the string
985 unsigned get_next_char(ASS_Renderer *render_priv, char **str)
989 if (*p == '{') { // '\0' goes here
992 p = parse_tag(render_priv, p, 1.);
993 if (*p == '}') { // end of tag
1000 } else if (*p != '\\')
1001 ass_msg(render_priv->library, MSGL_V,
1002 "Unable to parse: '%.30s'", p);
1013 if ((p[1] == 'N') || ((p[1] == 'n') &&
1014 (render_priv->state.wrap_style == 2))) {
1018 } else if (p[1] == 'n') {
1022 } else if (p[1] == 'h') {
1026 } else if (p[1] == '{') {
1030 } else if (p[1] == '}') {
1036 chr = ass_utf8_get_char((char **) &p);