1 /*
2 * Copyright (C) 2009 Grigori Goronzy <greg@geekmind.org>
3 *
4 * This file is part of libass.
5 *
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.
9 *
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.
17 */
18
19 #include "config.h"
20 #include "ass_compat.h"
21
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <math.h>
26
27 #include "ass_library.h"
28 #include "ass_render.h"
29 #include "ass_parse.h"
30
31 #define MAX_VALID_NARGS 7
32 #define MAX_BE 127
33 #define NBSP 0xa0 // unicode non-breaking space character
34
35 struct arg {
36 char *start, *end;
37 };
38
argtoi(struct arg arg)39 static inline int argtoi(struct arg arg)
40 {
41 int value;
42 mystrtoi(&arg.start, &value);
43 return value;
44 }
45
argtoi32(struct arg arg)46 static inline int32_t argtoi32(struct arg arg)
47 {
48 int32_t value;
49 mystrtoi32(&arg.start, 10, &value);
50 return value;
51 }
52
argtod(struct arg arg)53 static inline double argtod(struct arg arg)
54 {
55 double value;
56 mystrtod(&arg.start, &value);
57 return value;
58 }
59
push_arg(struct arg * args,int * nargs,char * start,char * end)60 static inline void push_arg(struct arg *args, int *nargs, char *start, char *end)
61 {
62 if (*nargs <= MAX_VALID_NARGS) {
63 rskip_spaces(&end, start);
64 if (end > start) {
65 args[*nargs] = (struct arg) {start, end};
66 ++*nargs;
67 }
68 }
69 }
70
71 /**
72 * \brief Check if starting part of (*p) matches sample.
73 * If true, shift p to the first symbol after the matching part.
74 */
mystrcmp(char ** p,const char * sample)75 static inline int mystrcmp(char **p, const char *sample)
76 {
77 char *p2;
78 for (p2 = *p; *sample != 0 && *p2 == *sample; p2++, sample++)
79 ;
80 if (*sample == 0) {
81 *p = p2;
82 return 1;
83 }
84 return 0;
85 }
86
ensure_font_size(ASS_Renderer * priv,double size)87 double ensure_font_size(ASS_Renderer *priv, double size)
88 {
89 if (size < 1)
90 size = 1;
91 else if (size > priv->height * 2)
92 size = priv->height * 2;
93
94 return size;
95 }
96
97 /**
98 * \brief Change current font, using setting from render_priv->state.
99 */
update_font(ASS_Renderer * render_priv)100 void update_font(ASS_Renderer *render_priv)
101 {
102 unsigned val;
103 ASS_FontDesc desc;
104
105 desc.family = render_priv->state.family;
106 if (!desc.family.str)
107 return;
108 if (desc.family.len && desc.family.str[0] == '@') {
109 desc.vertical = 1;
110 desc.family.str++;
111 desc.family.len--;
112 } else {
113 desc.vertical = 0;
114 }
115
116 val = render_priv->state.bold;
117 // 0 = normal, 1 = bold, >1 = exact weight
118 if (val == 1 || val == -1)
119 val = 700; // bold
120 else if (val <= 0)
121 val = 400; // normal
122 desc.bold = val;
123
124 val = render_priv->state.italic;
125 if (val == 1)
126 val = 100; // italic
127 else if (val <= 0)
128 val = 0; // normal
129 desc.italic = val;
130
131 ass_cache_dec_ref(render_priv->state.font);
132 render_priv->state.font = ass_font_new(render_priv, &desc);
133 }
134
135 /**
136 * \brief Convert double to int32_t without UB
137 * on out-of-range values; match x86 behavior
138 */
dtoi32(double val)139 static inline int32_t dtoi32(double val)
140 {
141 if (isnan(val) || val <= INT32_MIN || val >= INT32_MAX + 1LL)
142 return INT32_MIN;
143 return val;
144 }
145
calc_anim(double new,double old,double pwr)146 static double calc_anim(double new, double old, double pwr)
147 {
148 return (1 - pwr) * old + new * pwr;
149 }
150
calc_anim_int32(uint32_t new,uint32_t old,double pwr)151 static int32_t calc_anim_int32(uint32_t new, uint32_t old, double pwr)
152 {
153 return dtoi32(calc_anim(new, old, pwr));
154 }
155
156 /**
157 * \brief Calculate a weighted average of two colors
158 * calculates c1*(1-a) + c2*a, but separately for each component except alpha
159 */
change_color(uint32_t * var,uint32_t new,double pwr)160 static void change_color(uint32_t *var, uint32_t new, double pwr)
161 {
162 uint32_t co = ass_bswap32(*var);
163 uint32_t cn = ass_bswap32(new);
164
165 uint32_t cc = (calc_anim_int32(cn & 0xff0000, co & 0xff0000, pwr) & 0xff0000) |
166 (calc_anim_int32(cn & 0x00ff00, co & 0x00ff00, pwr) & 0x00ff00) |
167 (calc_anim_int32(cn & 0x0000ff, co & 0x0000ff, pwr) & 0x0000ff);
168
169 (*var) = (ass_bswap32(cc & 0xffffff)) | _a(*var);
170 }
171
172 // like change_color, but for alpha component only
change_alpha(uint32_t * var,int32_t new,double pwr)173 inline void change_alpha(uint32_t *var, int32_t new, double pwr)
174 {
175 *var = (*var & 0xFFFFFF00) | (uint8_t)calc_anim_int32(_a(new), _a(*var), pwr);
176 }
177
178 /**
179 * \brief Multiply two alpha values
180 * \param a first value
181 * \param b second value
182 * \return result of multiplication
183 * At least one of the parameters must be less than or equal to 0xFF.
184 * The result is less than or equal to max(a, b, 0xFF).
185 */
mult_alpha(uint32_t a,uint32_t b)186 inline uint32_t mult_alpha(uint32_t a, uint32_t b)
187 {
188 return a - ((uint64_t) a * b + 0x7F) / 0xFF + b;
189 }
190
191 /**
192 * \brief Calculate alpha value by piecewise linear function
193 * Used for \fad, \fade implementation.
194 */
195 static int
interpolate_alpha(long long now,int32_t t1,int32_t t2,int32_t t3,int32_t t4,int a1,int a2,int a3)196 interpolate_alpha(long long now, int32_t t1, int32_t t2, int32_t t3,
197 int32_t t4, int a1, int a2, int a3)
198 {
199 int a;
200 double cf;
201
202 if (now < t1) {
203 a = a1;
204 } else if (now < t2) {
205 cf = ((double) (int32_t) ((uint32_t) now - t1)) /
206 (int32_t) ((uint32_t) t2 - t1);
207 a = a1 * (1 - cf) + a2 * cf;
208 } else if (now < t3) {
209 a = a2;
210 } else if (now < t4) {
211 cf = ((double) (int32_t) ((uint32_t) now - t3)) /
212 (int32_t) ((uint32_t) t4 - t3);
213 a = a2 * (1 - cf) + a3 * cf;
214 } else { // now >= t4
215 a = a3;
216 }
217
218 return a;
219 }
220
221 /**
222 * Parse a vector clip into an outline, using the proper scaling
223 * parameters. Translate it to correct for screen borders, if needed.
224 */
parse_vector_clip(ASS_Renderer * render_priv,struct arg * args,int nargs)225 static bool parse_vector_clip(ASS_Renderer *render_priv,
226 struct arg *args, int nargs)
227 {
228 if (nargs != 1 && nargs != 2)
229 return false;
230
231 int scale = 1;
232 if (nargs == 2)
233 scale = argtoi(args[0]);
234
235 struct arg text = args[nargs - 1];
236 render_priv->state.clip_drawing_text.str = text.start;
237 render_priv->state.clip_drawing_text.len = text.end - text.start;
238 render_priv->state.clip_drawing_scale = scale;
239 return true;
240 }
241
242 /**
243 * \brief Parse style override tags.
244 * \param p string to parse
245 * \param end end of string to parse, which must be '}', ')', or the first
246 * of a number of spaces immediately preceding '}' or ')'
247 * \param pwr multiplier for some tag effects (comes from \t tags)
248 */
parse_tags(ASS_Renderer * render_priv,char * p,char * end,double pwr,bool nested)249 char *parse_tags(ASS_Renderer *render_priv, char *p, char *end, double pwr,
250 bool nested)
251 {
252 for (char *q; p < end; p = q) {
253 while (*p != '\\' && p != end)
254 ++p;
255 if (*p != '\\')
256 break;
257 ++p;
258 if (p != end)
259 skip_spaces(&p);
260
261 q = p;
262 while (*q != '(' && *q != '\\' && q != end)
263 ++q;
264 if (q == p)
265 continue;
266
267 char *name_end = q;
268
269 // Store one extra element to be able to detect excess arguments
270 struct arg args[MAX_VALID_NARGS + 1];
271 int nargs = 0;
272 bool has_backslash_arg = false;
273 for (int i = 0; i <= MAX_VALID_NARGS; ++i)
274 args[i].start = args[i].end = "";
275
276 // Split parenthesized arguments. Do this for all tags and before
277 // any non-parenthesized argument because that's what VSFilter does.
278 if (*q == '(') {
279 ++q;
280 while (1) {
281 if (q != end)
282 skip_spaces(&q);
283
284 // Split on commas. If there is a backslash, ignore any
285 // commas following it and lump everything starting from
286 // the last comma, through the backslash and all the way
287 // to the end of the argument string into a single argument.
288
289 char *r = q;
290 while (*r != ',' && *r != '\\' && *r != ')' && r != end)
291 ++r;
292
293 if (*r == ',') {
294 push_arg(args, &nargs, q, r);
295 q = r + 1;
296 } else {
297 // Swallow the rest of the parenthesized string. This could
298 // be either a backslash-argument or simply the last argument.
299 if (*r == '\\') {
300 has_backslash_arg = true;
301 while (*r != ')' && r != end)
302 ++r;
303 }
304 push_arg(args, &nargs, q, r);
305 q = r;
306 // The closing parenthesis could be missing.
307 if (q != end)
308 ++q;
309 break;
310 }
311 }
312 }
313
314 #define tag(name) (mystrcmp(&p, (name)) && (push_arg(args, &nargs, p, name_end), 1))
315 #define complex_tag(name) mystrcmp(&p, (name))
316
317 // New tags introduced in vsfilter 2.39
318 if (tag("xbord")) {
319 double val;
320 if (nargs) {
321 val = argtod(*args);
322 val = render_priv->state.border_x * (1 - pwr) + val * pwr;
323 val = (val < 0) ? 0 : val;
324 } else
325 val = render_priv->state.style->Outline;
326 render_priv->state.border_x = val;
327 } else if (tag("ybord")) {
328 double val;
329 if (nargs) {
330 val = argtod(*args);
331 val = render_priv->state.border_y * (1 - pwr) + val * pwr;
332 val = (val < 0) ? 0 : val;
333 } else
334 val = render_priv->state.style->Outline;
335 render_priv->state.border_y = val;
336 } else if (tag("xshad")) {
337 double val;
338 if (nargs) {
339 val = argtod(*args);
340 val = render_priv->state.shadow_x * (1 - pwr) + val * pwr;
341 } else
342 val = render_priv->state.style->Shadow;
343 render_priv->state.shadow_x = val;
344 } else if (tag("yshad")) {
345 double val;
346 if (nargs) {
347 val = argtod(*args);
348 val = render_priv->state.shadow_y * (1 - pwr) + val * pwr;
349 } else
350 val = render_priv->state.style->Shadow;
351 render_priv->state.shadow_y = val;
352 } else if (tag("fax")) {
353 double val;
354 if (nargs) {
355 val = argtod(*args);
356 render_priv->state.fax =
357 val * pwr + render_priv->state.fax * (1 - pwr);
358 } else
359 render_priv->state.fax = 0.;
360 } else if (tag("fay")) {
361 double val;
362 if (nargs) {
363 val = argtod(*args);
364 render_priv->state.fay =
365 val * pwr + render_priv->state.fay * (1 - pwr);
366 } else
367 render_priv->state.fay = 0.;
368 } else if (complex_tag("iclip")) {
369 if (nargs == 4) {
370 int x0, y0, x1, y1;
371 x0 = argtoi(args[0]);
372 y0 = argtoi(args[1]);
373 x1 = argtoi(args[2]);
374 y1 = argtoi(args[3]);
375 render_priv->state.clip_x0 =
376 render_priv->state.clip_x0 * (1 - pwr) + x0 * pwr;
377 render_priv->state.clip_x1 =
378 render_priv->state.clip_x1 * (1 - pwr) + x1 * pwr;
379 render_priv->state.clip_y0 =
380 render_priv->state.clip_y0 * (1 - pwr) + y0 * pwr;
381 render_priv->state.clip_y1 =
382 render_priv->state.clip_y1 * (1 - pwr) + y1 * pwr;
383 render_priv->state.clip_mode = 1;
384 } else if (!render_priv->state.clip_drawing_text.str) {
385 if (parse_vector_clip(render_priv, args, nargs))
386 render_priv->state.clip_drawing_mode = 1;
387 }
388 } else if (tag("blur")) {
389 double val;
390 if (nargs) {
391 val = argtod(*args);
392 val = render_priv->state.blur * (1 - pwr) + val * pwr;
393 val = (val < 0) ? 0 : val;
394 val = (val > BLUR_MAX_RADIUS) ? BLUR_MAX_RADIUS : val;
395 render_priv->state.blur = val;
396 } else
397 render_priv->state.blur = 0.0;
398 // ASS standard tags
399 } else if (tag("fscx")) {
400 double val;
401 if (nargs) {
402 val = argtod(*args) / 100;
403 val = render_priv->state.scale_x * (1 - pwr) + val * pwr;
404 val = (val < 0) ? 0 : val;
405 } else
406 val = render_priv->state.style->ScaleX;
407 render_priv->state.scale_x = val;
408 } else if (tag("fscy")) {
409 double val;
410 if (nargs) {
411 val = argtod(*args) / 100;
412 val = render_priv->state.scale_y * (1 - pwr) + val * pwr;
413 val = (val < 0) ? 0 : val;
414 } else
415 val = render_priv->state.style->ScaleY;
416 render_priv->state.scale_y = val;
417 } else if (tag("fsc")) {
418 render_priv->state.scale_x = render_priv->state.style->ScaleX;
419 render_priv->state.scale_y = render_priv->state.style->ScaleY;
420 } else if (tag("fsp")) {
421 double val;
422 if (nargs) {
423 val = argtod(*args);
424 render_priv->state.hspacing =
425 render_priv->state.hspacing * (1 - pwr) + val * pwr;
426 } else
427 render_priv->state.hspacing = render_priv->state.style->Spacing;
428 } else if (tag("fs")) {
429 double val = 0;
430 if (nargs) {
431 val = argtod(*args);
432 if (*args->start == '+' || *args->start == '-')
433 val = render_priv->state.font_size * (1 + pwr * val / 10);
434 else
435 val = render_priv->state.font_size * (1 - pwr) + val * pwr;
436 }
437 if (val <= 0)
438 val = render_priv->state.style->FontSize;
439 render_priv->state.font_size = val;
440 } else if (tag("bord")) {
441 double val, xval, yval;
442 if (nargs) {
443 val = argtod(*args);
444 xval = render_priv->state.border_x * (1 - pwr) + val * pwr;
445 yval = render_priv->state.border_y * (1 - pwr) + val * pwr;
446 xval = (xval < 0) ? 0 : xval;
447 yval = (yval < 0) ? 0 : yval;
448 } else
449 xval = yval = render_priv->state.style->Outline;
450 render_priv->state.border_x = xval;
451 render_priv->state.border_y = yval;
452 } else if (complex_tag("move")) {
453 double x1, x2, y1, y2;
454 int32_t t1, t2, delta_t, t;
455 double x, y;
456 double k;
457 if (nargs == 4 || nargs == 6) {
458 x1 = argtod(args[0]);
459 y1 = argtod(args[1]);
460 x2 = argtod(args[2]);
461 y2 = argtod(args[3]);
462 t1 = t2 = 0;
463 if (nargs == 6) {
464 t1 = argtoi32(args[4]);
465 t2 = argtoi32(args[5]);
466 if (t1 > t2) {
467 long long tmp = t2;
468 t2 = t1;
469 t1 = tmp;
470 }
471 }
472 } else
473 continue;
474 if (t1 <= 0 && t2 <= 0) {
475 t1 = 0;
476 t2 = render_priv->state.event->Duration;
477 }
478 delta_t = (uint32_t) t2 - t1;
479 t = render_priv->time - render_priv->state.event->Start;
480 if (t <= t1)
481 k = 0.;
482 else if (t >= t2)
483 k = 1.;
484 else
485 k = ((double) (int32_t) ((uint32_t) t - t1)) / delta_t;
486 x = k * (x2 - x1) + x1;
487 y = k * (y2 - y1) + y1;
488 if (!(render_priv->state.evt_type & EVENT_POSITIONED)) {
489 render_priv->state.pos_x = x;
490 render_priv->state.pos_y = y;
491 render_priv->state.detect_collisions = 0;
492 render_priv->state.evt_type |= EVENT_POSITIONED;
493 }
494 } else if (tag("frx")) {
495 double val;
496 if (nargs) {
497 val = argtod(*args);
498 render_priv->state.frx =
499 val * pwr + render_priv->state.frx * (1 - pwr);
500 } else
501 render_priv->state.frx = 0.;
502 } else if (tag("fry")) {
503 double val;
504 if (nargs) {
505 val = argtod(*args);
506 render_priv->state.fry =
507 val * pwr + render_priv->state.fry * (1 - pwr);
508 } else
509 render_priv->state.fry = 0.;
510 } else if (tag("frz") || tag("fr")) {
511 double val;
512 if (nargs) {
513 val = argtod(*args);
514 render_priv->state.frz =
515 val * pwr + render_priv->state.frz * (1 - pwr);
516 } else
517 render_priv->state.frz =
518 render_priv->state.style->Angle;
519 } else if (tag("fn")) {
520 char *start = args->start;
521 if (nargs && strncmp(start, "0", args->end - start)) {
522 skip_spaces(&start);
523 render_priv->state.family.str = start;
524 render_priv->state.family.len = args->end - start;
525 } else {
526 render_priv->state.family.str = render_priv->state.style->FontName;
527 render_priv->state.family.len = strlen(render_priv->state.style->FontName);
528 }
529 update_font(render_priv);
530 } else if (tag("alpha")) {
531 int i;
532 if (nargs) {
533 int32_t a = parse_alpha_tag(args->start);
534 for (i = 0; i < 4; ++i)
535 change_alpha(&render_priv->state.c[i], a, pwr);
536 } else {
537 change_alpha(&render_priv->state.c[0],
538 _a(render_priv->state.style->PrimaryColour), 1);
539 change_alpha(&render_priv->state.c[1],
540 _a(render_priv->state.style->SecondaryColour), 1);
541 change_alpha(&render_priv->state.c[2],
542 _a(render_priv->state.style->OutlineColour), 1);
543 change_alpha(&render_priv->state.c[3],
544 _a(render_priv->state.style->BackColour), 1);
545 }
546 // FIXME: simplify
547 } else if (tag("an")) {
548 int val = argtoi(*args);
549 if ((render_priv->state.parsed_tags & PARSED_A) == 0) {
550 if (val >= 1 && val <= 9)
551 render_priv->state.alignment = numpad2align(val);
552 else
553 render_priv->state.alignment =
554 render_priv->state.style->Alignment;
555 render_priv->state.parsed_tags |= PARSED_A;
556 }
557 } else if (tag("a")) {
558 int val = argtoi(*args);
559 if ((render_priv->state.parsed_tags & PARSED_A) == 0) {
560 if (val >= 1 && val <= 11)
561 // take care of a vsfilter quirk:
562 // handle illegal \a8 and \a4 like \a5
563 render_priv->state.alignment = ((val & 3) == 0) ? 5 : val;
564 else
565 render_priv->state.alignment =
566 render_priv->state.style->Alignment;
567 render_priv->state.parsed_tags |= PARSED_A;
568 }
569 } else if (complex_tag("pos")) {
570 double v1, v2;
571 if (nargs == 2) {
572 v1 = argtod(args[0]);
573 v2 = argtod(args[1]);
574 } else
575 continue;
576 if (render_priv->state.evt_type & EVENT_POSITIONED) {
577 ass_msg(render_priv->library, MSGL_V, "Subtitle has a new \\pos "
578 "after \\move or \\pos, ignoring");
579 } else {
580 render_priv->state.evt_type |= EVENT_POSITIONED;
581 render_priv->state.detect_collisions = 0;
582 render_priv->state.pos_x = v1;
583 render_priv->state.pos_y = v2;
584 }
585 } else if (complex_tag("fade") || complex_tag("fad")) {
586 int a1, a2, a3;
587 int32_t t1, t2, t3, t4;
588 if (nargs == 2) {
589 // 2-argument version (\fad, according to specs)
590 a1 = 0xFF;
591 a2 = 0;
592 a3 = 0xFF;
593 t1 = -1;
594 t2 = argtoi32(args[0]);
595 t3 = argtoi32(args[1]);
596 t4 = -1;
597 } else if (nargs == 7) {
598 // 7-argument version (\fade)
599 a1 = argtoi(args[0]);
600 a2 = argtoi(args[1]);
601 a3 = argtoi(args[2]);
602 t1 = argtoi32(args[3]);
603 t2 = argtoi32(args[4]);
604 t3 = argtoi32(args[5]);
605 t4 = argtoi32(args[6]);
606 } else
607 continue;
608 if (t1 == -1 && t4 == -1) {
609 t1 = 0;
610 t4 = render_priv->state.event->Duration;
611 t3 = (uint32_t) t4 - t3;
612 }
613 if ((render_priv->state.parsed_tags & PARSED_FADE) == 0) {
614 render_priv->state.fade =
615 interpolate_alpha(render_priv->time -
616 render_priv->state.event->Start, t1, t2,
617 t3, t4, a1, a2, a3);
618 render_priv->state.parsed_tags |= PARSED_FADE;
619 }
620 } else if (complex_tag("org")) {
621 double v1, v2;
622 if (nargs == 2) {
623 v1 = argtod(args[0]);
624 v2 = argtod(args[1]);
625 } else
626 continue;
627 if (!render_priv->state.have_origin) {
628 render_priv->state.org_x = v1;
629 render_priv->state.org_y = v2;
630 render_priv->state.have_origin = 1;
631 render_priv->state.detect_collisions = 0;
632 }
633 } else if (complex_tag("t")) {
634 double accel;
635 int cnt = nargs - 1;
636 int32_t t1, t2, t, delta_t;
637 double k;
638 // VSFilter compatibility (because we can): parse the
639 // timestamps differently depending on argument count.
640 if (cnt == 3) {
641 t1 = argtoi32(args[0]);
642 t2 = argtoi32(args[1]);
643 accel = argtod(args[2]);
644 } else if (cnt == 2) {
645 t1 = dtoi32(argtod(args[0]));
646 t2 = dtoi32(argtod(args[1]));
647 accel = 1.;
648 } else if (cnt == 1) {
649 t1 = 0;
650 t2 = 0;
651 accel = argtod(args[0]);
652 } else {
653 t1 = 0;
654 t2 = 0;
655 accel = 1.;
656 }
657 render_priv->state.detect_collisions = 0;
658 if (t2 == 0)
659 t2 = render_priv->state.event->Duration;
660 delta_t = (uint32_t) t2 - t1;
661 t = render_priv->time - render_priv->state.event->Start; // FIXME: move to render_context
662 if (t < t1)
663 k = 0.;
664 else if (t >= t2)
665 k = 1.;
666 else {
667 assert(delta_t != 0.);
668 k = pow((double) (int32_t) ((uint32_t) t - t1) / delta_t, accel);
669 }
670 if (nested)
671 pwr = k;
672 if (cnt < 0 || cnt > 3)
673 continue;
674 // If there's no backslash in the arguments, there are no
675 // override tags, so it's pointless to try to parse them.
676 if (!has_backslash_arg)
677 continue;
678 p = args[cnt].start;
679 if (args[cnt].end < end) {
680 assert(!nested);
681 p = parse_tags(render_priv, p, args[cnt].end, k, true);
682 } else {
683 assert(q == end);
684 // No other tags can possibly follow this \t tag,
685 // so we don't need to restore pwr after parsing \t.
686 // The recursive call is now essentially a tail call,
687 // so optimize it away.
688 pwr = k;
689 nested = true;
690 q = p;
691 }
692 } else if (complex_tag("clip")) {
693 if (nargs == 4) {
694 int x0, y0, x1, y1;
695 x0 = argtoi(args[0]);
696 y0 = argtoi(args[1]);
697 x1 = argtoi(args[2]);
698 y1 = argtoi(args[3]);
699 render_priv->state.clip_x0 =
700 render_priv->state.clip_x0 * (1 - pwr) + x0 * pwr;
701 render_priv->state.clip_x1 =
702 render_priv->state.clip_x1 * (1 - pwr) + x1 * pwr;
703 render_priv->state.clip_y0 =
704 render_priv->state.clip_y0 * (1 - pwr) + y0 * pwr;
705 render_priv->state.clip_y1 =
706 render_priv->state.clip_y1 * (1 - pwr) + y1 * pwr;
707 render_priv->state.clip_mode = 0;
708 } else if (!render_priv->state.clip_drawing_text.str) {
709 if (parse_vector_clip(render_priv, args, nargs))
710 render_priv->state.clip_drawing_mode = 0;
711 }
712 } else if (tag("c") || tag("1c")) {
713 if (nargs) {
714 uint32_t val = parse_color_tag(args->start);
715 change_color(&render_priv->state.c[0], val, pwr);
716 } else
717 change_color(&render_priv->state.c[0],
718 render_priv->state.style->PrimaryColour, 1);
719 } else if (tag("2c")) {
720 if (nargs) {
721 uint32_t val = parse_color_tag(args->start);
722 change_color(&render_priv->state.c[1], val, pwr);
723 } else
724 change_color(&render_priv->state.c[1],
725 render_priv->state.style->SecondaryColour, 1);
726 } else if (tag("3c")) {
727 if (nargs) {
728 uint32_t val = parse_color_tag(args->start);
729 change_color(&render_priv->state.c[2], val, pwr);
730 } else
731 change_color(&render_priv->state.c[2],
732 render_priv->state.style->OutlineColour, 1);
733 } else if (tag("4c")) {
734 if (nargs) {
735 uint32_t val = parse_color_tag(args->start);
736 change_color(&render_priv->state.c[3], val, pwr);
737 } else
738 change_color(&render_priv->state.c[3],
739 render_priv->state.style->BackColour, 1);
740 } else if (tag("1a")) {
741 if (nargs) {
742 uint32_t val = parse_alpha_tag(args->start);
743 change_alpha(&render_priv->state.c[0], val, pwr);
744 } else
745 change_alpha(&render_priv->state.c[0],
746 _a(render_priv->state.style->PrimaryColour), 1);
747 } else if (tag("2a")) {
748 if (nargs) {
749 uint32_t val = parse_alpha_tag(args->start);
750 change_alpha(&render_priv->state.c[1], val, pwr);
751 } else
752 change_alpha(&render_priv->state.c[1],
753 _a(render_priv->state.style->SecondaryColour), 1);
754 } else if (tag("3a")) {
755 if (nargs) {
756 uint32_t val = parse_alpha_tag(args->start);
757 change_alpha(&render_priv->state.c[2], val, pwr);
758 } else
759 change_alpha(&render_priv->state.c[2],
760 _a(render_priv->state.style->OutlineColour), 1);
761 } else if (tag("4a")) {
762 if (nargs) {
763 uint32_t val = parse_alpha_tag(args->start);
764 change_alpha(&render_priv->state.c[3], val, pwr);
765 } else
766 change_alpha(&render_priv->state.c[3],
767 _a(render_priv->state.style->BackColour), 1);
768 } else if (tag("r")) {
769 if (nargs) {
770 int len = args->end - args->start;
771 reset_render_context(render_priv,
772 lookup_style_strict(render_priv->track, args->start, len));
773 } else
774 reset_render_context(render_priv, NULL);
775 } else if (tag("be")) {
776 double dval;
777 if (nargs) {
778 int val;
779 dval = argtod(*args);
780 // VSFilter always adds +0.5, even if the value is negative
781 val = (int) (render_priv->state.be * (1 - pwr) + dval * pwr + 0.5);
782 // Clamp to a safe upper limit, since high values need excessive CPU
783 val = (val < 0) ? 0 : val;
784 val = (val > MAX_BE) ? MAX_BE : val;
785 render_priv->state.be = val;
786 } else
787 render_priv->state.be = 0;
788 } else if (tag("b")) {
789 int val = argtoi(*args);
790 if (!nargs || !(val == 0 || val == 1 || val >= 100))
791 val = render_priv->state.style->Bold;
792 render_priv->state.bold = val;
793 update_font(render_priv);
794 } else if (tag("i")) {
795 int val = argtoi(*args);
796 if (!nargs || !(val == 0 || val == 1))
797 val = render_priv->state.style->Italic;
798 render_priv->state.italic = val;
799 update_font(render_priv);
800 } else if (tag("kf") || tag("K")) {
801 double val = 100;
802 if (nargs)
803 val = argtod(*args);
804 render_priv->state.effect_type = EF_KARAOKE_KF;
805 if (render_priv->state.effect_timing)
806 render_priv->state.effect_skip_timing +=
807 render_priv->state.effect_timing;
808 render_priv->state.effect_timing = val * 10;
809 } else if (tag("ko")) {
810 double val = 100;
811 if (nargs)
812 val = argtod(*args);
813 render_priv->state.effect_type = EF_KARAOKE_KO;
814 if (render_priv->state.effect_timing)
815 render_priv->state.effect_skip_timing +=
816 render_priv->state.effect_timing;
817 render_priv->state.effect_timing = val * 10;
818 } else if (tag("k")) {
819 double val = 100;
820 if (nargs)
821 val = argtod(*args);
822 render_priv->state.effect_type = EF_KARAOKE;
823 if (render_priv->state.effect_timing)
824 render_priv->state.effect_skip_timing +=
825 render_priv->state.effect_timing;
826 render_priv->state.effect_timing = val * 10;
827 } else if (tag("shad")) {
828 double val, xval, yval;
829 if (nargs) {
830 val = argtod(*args);
831 xval = render_priv->state.shadow_x * (1 - pwr) + val * pwr;
832 yval = render_priv->state.shadow_y * (1 - pwr) + val * pwr;
833 // VSFilter compatibility: clip for \shad but not for \[xy]shad
834 xval = (xval < 0) ? 0 : xval;
835 yval = (yval < 0) ? 0 : yval;
836 } else
837 xval = yval = render_priv->state.style->Shadow;
838 render_priv->state.shadow_x = xval;
839 render_priv->state.shadow_y = yval;
840 } else if (tag("s")) {
841 int val = argtoi(*args);
842 if (!nargs || !(val == 0 || val == 1))
843 val = render_priv->state.style->StrikeOut;
844 if (val)
845 render_priv->state.flags |= DECO_STRIKETHROUGH;
846 else
847 render_priv->state.flags &= ~DECO_STRIKETHROUGH;
848 } else if (tag("u")) {
849 int val = argtoi(*args);
850 if (!nargs || !(val == 0 || val == 1))
851 val = render_priv->state.style->Underline;
852 if (val)
853 render_priv->state.flags |= DECO_UNDERLINE;
854 else
855 render_priv->state.flags &= ~DECO_UNDERLINE;
856 } else if (tag("pbo")) {
857 double val = argtod(*args);
858 render_priv->state.pbo = val;
859 } else if (tag("p")) {
860 int val = argtoi(*args);
861 val = (val < 0) ? 0 : val;
862 render_priv->state.drawing_scale = val;
863 } else if (tag("q")) {
864 int val = argtoi(*args);
865 if (!nargs || !(val >= 0 && val <= 3))
866 val = render_priv->track->WrapStyle;
867 render_priv->state.wrap_style = val;
868 } else if (tag("fe")) {
869 int val;
870 if (nargs)
871 val = argtoi(*args);
872 else
873 val = render_priv->state.style->Encoding;
874 render_priv->state.font_encoding = val;
875 }
876 }
877
878 return p;
879 }
880
apply_transition_effects(ASS_Renderer * render_priv,ASS_Event * event)881 void apply_transition_effects(ASS_Renderer *render_priv, ASS_Event *event)
882 {
883 int v[4];
884 int cnt;
885 char *p = event->Effect;
886
887 if (!p || !*p)
888 return;
889
890 cnt = 0;
891 while (cnt < 4 && (p = strchr(p, ';'))) {
892 v[cnt++] = atoi(++p);
893 }
894
895 if (strncmp(event->Effect, "Banner;", 7) == 0) {
896 int delay;
897 if (cnt < 1) {
898 ass_msg(render_priv->library, MSGL_V,
899 "Error parsing effect: '%s'", event->Effect);
900 return;
901 }
902 if (cnt >= 2 && v[1]) // left-to-right
903 render_priv->state.scroll_direction = SCROLL_LR;
904 else // right-to-left
905 render_priv->state.scroll_direction = SCROLL_RL;
906
907 delay = v[0];
908 if (delay == 0)
909 delay = 1; // ?
910 render_priv->state.scroll_shift =
911 (render_priv->time - render_priv->state.event->Start) / delay;
912 render_priv->state.evt_type |= EVENT_HSCROLL;
913 render_priv->state.detect_collisions = 0;
914 render_priv->state.wrap_style = 2;
915 return;
916 }
917
918 if (strncmp(event->Effect, "Scroll up;", 10) == 0) {
919 render_priv->state.scroll_direction = SCROLL_BT;
920 } else if (strncmp(event->Effect, "Scroll down;", 12) == 0) {
921 render_priv->state.scroll_direction = SCROLL_TB;
922 } else {
923 ass_msg(render_priv->library, MSGL_DBG2,
924 "Unknown transition effect: '%s'", event->Effect);
925 return;
926 }
927 // parse scroll up/down parameters
928 {
929 int delay;
930 int y0, y1;
931 if (cnt < 3) {
932 ass_msg(render_priv->library, MSGL_V,
933 "Error parsing effect: '%s'", event->Effect);
934 return;
935 }
936 delay = v[2];
937 if (delay == 0)
938 delay = 1; // ?
939 render_priv->state.scroll_shift =
940 (render_priv->time - render_priv->state.event->Start) / delay;
941 if (v[0] < v[1]) {
942 y0 = v[0];
943 y1 = v[1];
944 } else {
945 y0 = v[1];
946 y1 = v[0];
947 }
948 render_priv->state.scroll_y0 = y0;
949 render_priv->state.scroll_y1 = y1;
950 render_priv->state.evt_type |= EVENT_VSCROLL;
951 render_priv->state.detect_collisions = 0;
952 }
953
954 }
955
956 /**
957 * \brief determine karaoke effects
958 * Karaoke effects cannot be calculated during parse stage (get_next_char()),
959 * so they are done in a separate step.
960 * Parse stage: when karaoke style override is found, its parameters are stored in the next glyph's
961 * (the first glyph of the karaoke word)'s effect_type and effect_timing.
962 * This function:
963 * 1. sets effect_type for all glyphs in the word (_karaoke_ word)
964 * 2. sets effect_timing for all glyphs to x coordinate of the border line between the left and right karaoke parts
965 * (left part is filled with PrimaryColour, right one - with SecondaryColour).
966 */
process_karaoke_effects(ASS_Renderer * render_priv)967 void process_karaoke_effects(ASS_Renderer *render_priv)
968 {
969 long long tm_current = render_priv->time - render_priv->state.event->Start;
970
971 int timing = 0, skip_timing = 0;
972 Effect effect_type = EF_NONE;
973 GlyphInfo *last_boundary = NULL;
974 for (int i = 0; i <= render_priv->text_info.length; i++) {
975 if (i < render_priv->text_info.length &&
976 !render_priv->text_info.glyphs[i].starts_new_run) {
977 // VSFilter compatibility: if we have \k12345\k0 without a run
978 // break, subsequent text is still part of the same karaoke word,
979 // the current word's starting and ending time stay unchanged,
980 // but the starting time of the next karaoke word is advanced.
981 skip_timing += render_priv->text_info.glyphs[i].effect_skip_timing;
982 continue;
983 }
984
985 GlyphInfo *start = last_boundary;
986 GlyphInfo *end = render_priv->text_info.glyphs + i;
987 last_boundary = end;
988 if (!start)
989 continue;
990
991 if (start->effect_type != EF_NONE)
992 effect_type = start->effect_type;
993 if (effect_type == EF_NONE)
994 continue;
995
996 long long tm_start = timing + start->effect_skip_timing;
997 long long tm_end = tm_start + start->effect_timing;
998 timing = tm_end + skip_timing;
999 skip_timing = 0;
1000
1001 if (effect_type != EF_KARAOKE_KF)
1002 tm_end = tm_start;
1003
1004 int x;
1005 if (tm_current < tm_start)
1006 x = -100000000;
1007 else if (tm_current >= tm_end)
1008 x = 100000000;
1009 else {
1010 GlyphInfo *first_visible = start, *last_visible = end - 1;
1011 while (first_visible < last_visible && first_visible->skip)
1012 ++first_visible;
1013 while (first_visible < last_visible && last_visible->skip)
1014 --last_visible;
1015
1016 int x_start = first_visible->pos.x;
1017 int x_end = last_visible->pos.x + last_visible->advance.x;
1018 double dt = (double) (tm_current - tm_start) / (tm_end - tm_start);
1019 double frz = fmod(start->frz, 360);
1020 if (frz > 90 && frz < 270) {
1021 // Fill from right to left
1022 dt = 1 - dt;
1023 for (GlyphInfo *info = start; info < end; info++) {
1024 uint32_t tmp = info->c[0];
1025 info->c[0] = info->c[1];
1026 info->c[1] = tmp;
1027 }
1028 }
1029 x = x_start + lrint((x_end - x_start) * dt);
1030 }
1031
1032 for (GlyphInfo *info = start; info < end; info++) {
1033 info->effect_type = effect_type;
1034 info->effect_timing = x - info->pos.x;
1035 }
1036 }
1037 }
1038
1039
1040 /**
1041 * \brief Get next ucs4 char from string, parsing UTF-8 and escapes
1042 * \param str string pointer
1043 * \return ucs4 code of the next char
1044 * On return str points to the unparsed part of the string
1045 */
get_next_char(ASS_Renderer * render_priv,char ** str)1046 unsigned get_next_char(ASS_Renderer *render_priv, char **str)
1047 {
1048 char *p = *str;
1049 unsigned chr;
1050 if (*p == '\t') {
1051 ++p;
1052 *str = p;
1053 return ' ';
1054 }
1055 if (*p == '\\') {
1056 if ((p[1] == 'N') || ((p[1] == 'n') &&
1057 (render_priv->state.wrap_style == 2))) {
1058 p += 2;
1059 *str = p;
1060 return '\n';
1061 } else if (p[1] == 'n') {
1062 p += 2;
1063 *str = p;
1064 return ' ';
1065 } else if (p[1] == 'h') {
1066 p += 2;
1067 *str = p;
1068 return NBSP;
1069 } else if (p[1] == '{') {
1070 p += 2;
1071 *str = p;
1072 return '{';
1073 } else if (p[1] == '}') {
1074 p += 2;
1075 *str = p;
1076 return '}';
1077 }
1078 }
1079 chr = ass_utf8_get_char((char **) &p);
1080 *str = p;
1081 return chr;
1082 }
1083
1084 // Return 1 if the event contains tags that will apply overrides the selective
1085 // style override code should not touch. Return 0 otherwise.
event_has_hard_overrides(char * str)1086 int event_has_hard_overrides(char *str)
1087 {
1088 // look for \pos and \move tags inside {...}
1089 // mirrors get_next_char, but is faster and doesn't change any global state
1090 while (*str) {
1091 if (str[0] == '\\' && str[1] != '\0') {
1092 str += 2;
1093 } else if (str[0] == '{') {
1094 str++;
1095 while (*str && *str != '}') {
1096 if (*str == '\\') {
1097 char *p = str + 1;
1098 if (mystrcmp(&p, "pos") || mystrcmp(&p, "move") ||
1099 mystrcmp(&p, "clip") || mystrcmp(&p, "iclip") ||
1100 mystrcmp(&p, "org") || mystrcmp(&p, "pbo") ||
1101 mystrcmp(&p, "p"))
1102 return 1;
1103 }
1104 str++;
1105 }
1106 } else {
1107 str++;
1108 }
1109 }
1110 return 0;
1111 }
1112