/src/libass/libass/ass_parse.c
Line | Count | Source |
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 | 5.28M | #define MAX_VALID_NARGS 7 |
32 | 3.61k | #define MAX_BE 127 |
33 | 2.11k | #define NBSP 0xa0 // unicode non-breaking space character |
34 | | |
35 | | struct arg { |
36 | | char *start, *end; |
37 | | }; |
38 | | |
39 | | static inline int32_t argtoi32(struct arg arg) |
40 | 161k | { |
41 | 161k | int32_t value; |
42 | 161k | mystrtoi32(&arg.start, 10, &value); |
43 | 161k | return value; |
44 | 161k | } |
45 | | |
46 | | static inline double argtod(struct arg arg) |
47 | 171k | { |
48 | 171k | double value; |
49 | 171k | mystrtod(&arg.start, &value); |
50 | 171k | return value; |
51 | 171k | } |
52 | | |
53 | | static inline void push_arg(struct arg *args, int *nargs, char *start, char *end) |
54 | 885k | { |
55 | 885k | if (*nargs <= MAX_VALID_NARGS) { |
56 | 838k | rskip_spaces(&end, start); |
57 | 838k | if (end > start) { |
58 | 689k | args[*nargs] = (struct arg) {start, end}; |
59 | 689k | ++*nargs; |
60 | 689k | } |
61 | 838k | } |
62 | 885k | } |
63 | | |
64 | | /** |
65 | | * \brief Check if starting part of (*p) matches sample. |
66 | | * If true, shift p to the first symbol after the matching part. |
67 | | */ |
68 | | static inline int mystrcmp(char **p, const char *sample) |
69 | 20.6M | { |
70 | 20.6M | char *p2; |
71 | 22.7M | for (p2 = *p; *sample != 0 && *p2 == *sample; p2++, sample++) |
72 | 2.14M | ; |
73 | 20.6M | if (*sample == 0) { |
74 | 462k | *p = p2; |
75 | 462k | return 1; |
76 | 462k | } |
77 | 20.1M | return 0; |
78 | 20.6M | } |
79 | | |
80 | | /** |
81 | | * \brief Change current font, using setting from render_priv->state. |
82 | | */ |
83 | | void ass_update_font(RenderContext *state) |
84 | 452k | { |
85 | 452k | unsigned val; |
86 | 452k | ASS_FontDesc desc; |
87 | | |
88 | 452k | desc.family = state->family; |
89 | 452k | if (!desc.family.str) |
90 | 0 | return; |
91 | 452k | if (desc.family.len && desc.family.str[0] == '@') { |
92 | 8.09k | desc.vertical = 1; |
93 | 8.09k | desc.family.str++; |
94 | 8.09k | desc.family.len--; |
95 | 443k | } else { |
96 | 443k | desc.vertical = 0; |
97 | 443k | } |
98 | | |
99 | 452k | val = state->bold; |
100 | | // 0 = normal, 1 = bold, >1 = exact weight |
101 | 452k | if (val == 1 || val == -1) |
102 | 2.55k | val = 700; // bold |
103 | 449k | else if (val <= 0) |
104 | 12.6k | val = 400; // normal |
105 | 452k | desc.bold = val; |
106 | | |
107 | 452k | val = state->italic; |
108 | 452k | if (val == 1) |
109 | 6.39k | val = 100; // italic |
110 | 445k | else if (val <= 0) |
111 | 445k | val = 0; // normal |
112 | 452k | desc.italic = val; |
113 | | |
114 | 452k | state->font = ass_font_new(state->renderer, &desc); |
115 | 452k | } |
116 | | |
117 | | /** |
118 | | * \brief Convert double to int32_t without UB |
119 | | * on out-of-range values; match x86 behavior |
120 | | */ |
121 | | static inline int32_t dtoi32(double val) |
122 | 198k | { |
123 | 198k | if (isnan(val) || val <= INT32_MIN || val >= INT32_MAX + 1LL) |
124 | 50.4k | return INT32_MIN; |
125 | 148k | return val; |
126 | 198k | } |
127 | | |
128 | | static double calc_anim(double new, double old, double pwr) |
129 | 112k | { |
130 | 112k | return (1 - pwr) * old + new * pwr; |
131 | 112k | } |
132 | | |
133 | | static int32_t calc_anim_int32(uint32_t new, uint32_t old, double pwr) |
134 | 112k | { |
135 | 112k | return dtoi32(calc_anim(new, old, pwr)); |
136 | 112k | } |
137 | | |
138 | | /** |
139 | | * \brief Calculate a weighted average of two colors |
140 | | * calculates c1*(1-a) + c2*a, but separately for each component except alpha |
141 | | */ |
142 | | static void change_color(uint32_t *var, uint32_t new, double pwr) |
143 | 25.8k | { |
144 | 25.8k | uint32_t co = ass_bswap32(*var); |
145 | 25.8k | uint32_t cn = ass_bswap32(new); |
146 | | |
147 | 25.8k | uint32_t cc = (calc_anim_int32(cn & 0xff0000, co & 0xff0000, pwr) & 0xff0000) | |
148 | 25.8k | (calc_anim_int32(cn & 0x00ff00, co & 0x00ff00, pwr) & 0x00ff00) | |
149 | 25.8k | (calc_anim_int32(cn & 0x0000ff, co & 0x0000ff, pwr) & 0x0000ff); |
150 | | |
151 | 25.8k | (*var) = (ass_bswap32(cc & 0xffffff)) | _a(*var); |
152 | 25.8k | } |
153 | | |
154 | | // like change_color, but for alpha component only |
155 | | static inline void change_alpha(uint32_t *var, int32_t new, double pwr) |
156 | 35.1k | { |
157 | 35.1k | *var = (*var & 0xFFFFFF00) | (uint8_t)calc_anim_int32(new, _a(*var), pwr); |
158 | 35.1k | } |
159 | | |
160 | | /** |
161 | | * \brief Multiply two alpha values |
162 | | * \param a first value |
163 | | * \param b second value |
164 | | * \return result of multiplication |
165 | | * At least one of the parameters must be less than or equal to 0xFF. |
166 | | * The result is less than or equal to max(a, b, 0xFF). |
167 | | */ |
168 | | static inline uint32_t mult_alpha(uint32_t a, uint32_t b) |
169 | 1.23k | { |
170 | 1.23k | return a - ((uint64_t) a * b + 0x7F) / 0xFF + b; |
171 | 1.23k | } |
172 | | |
173 | | void ass_apply_fade(uint32_t *clr, int fade) |
174 | 429k | { |
175 | | // VSFilter compatibility: apply fade only when it's positive |
176 | 429k | if (fade > 0) |
177 | 1.23k | change_alpha(clr, mult_alpha(_a(*clr), fade), 1); |
178 | 429k | } |
179 | | |
180 | | /** |
181 | | * \brief Calculate alpha value by piecewise linear function |
182 | | * Used for \fad, \fade implementation. |
183 | | */ |
184 | | static int |
185 | | interpolate_alpha(long long now, int32_t t1, int32_t t2, int32_t t3, |
186 | | int32_t t4, int a1, int a2, int a3) |
187 | 4.56k | { |
188 | 4.56k | int a; |
189 | 4.56k | double cf; |
190 | | |
191 | 4.56k | if (now < t1) { |
192 | 308 | a = a1; |
193 | 4.25k | } else if (now < t2) { |
194 | 346 | cf = ((double) (int32_t) ((uint32_t) now - t1)) / |
195 | 346 | (int32_t) ((uint32_t) t2 - t1); |
196 | 346 | a = a1 * (1 - cf) + a2 * cf; |
197 | 3.90k | } else if (now < t3) { |
198 | 1.61k | a = a2; |
199 | 2.29k | } else if (now < t4) { |
200 | 379 | cf = ((double) (int32_t) ((uint32_t) now - t3)) / |
201 | 379 | (int32_t) ((uint32_t) t4 - t3); |
202 | 379 | a = a2 * (1 - cf) + a3 * cf; |
203 | 1.91k | } else { // now >= t4 |
204 | 1.91k | a = a3; |
205 | 1.91k | } |
206 | | |
207 | 4.56k | return a; |
208 | 4.56k | } |
209 | | |
210 | | /** |
211 | | * Parse a vector clip into an outline, using the proper scaling |
212 | | * parameters. Translate it to correct for screen borders, if needed. |
213 | | */ |
214 | | static bool parse_vector_clip(RenderContext *state, |
215 | | struct arg *args, int nargs) |
216 | 6.42k | { |
217 | 6.42k | if (nargs != 1 && nargs != 2) |
218 | 2.02k | return false; |
219 | | |
220 | 4.40k | int scale = 1; |
221 | 4.40k | if (nargs == 2) |
222 | 2.77k | scale = argtoi32(args[0]); |
223 | | |
224 | 4.40k | struct arg text = args[nargs - 1]; |
225 | 4.40k | state->clip_drawing_text.str = text.start; |
226 | 4.40k | state->clip_drawing_text.len = text.end - text.start; |
227 | 4.40k | state->clip_drawing_scale = scale; |
228 | 4.40k | return true; |
229 | 6.42k | } |
230 | | |
231 | | static int32_t parse_alpha_tag(char *str) |
232 | 14.9k | { |
233 | 14.9k | int32_t alpha = 0; |
234 | | |
235 | 17.5k | while (*str == '&' || *str == 'H') |
236 | 2.61k | ++str; |
237 | | |
238 | 14.9k | mystrtoi32(&str, 16, &alpha); |
239 | 14.9k | return alpha; |
240 | 14.9k | } |
241 | | |
242 | | static uint32_t parse_color_tag(char *str) |
243 | 16.5k | { |
244 | 16.5k | int32_t color = 0; |
245 | | |
246 | 18.0k | while (*str == '&' || *str == 'H') |
247 | 1.51k | ++str; |
248 | | |
249 | 16.5k | mystrtoi32(&str, 16, &color); |
250 | 16.5k | return ass_bswap32((uint32_t) color); |
251 | 16.5k | } |
252 | | |
253 | | /** |
254 | | * \brief find style by name as in \r |
255 | | * \param track track |
256 | | * \param name style name |
257 | | * \param len style name length |
258 | | * \return style in track->styles |
259 | | * Returns NULL if no style has the given name. |
260 | | */ |
261 | | static ASS_Style *lookup_style_strict(ASS_Track *track, char *name, size_t len) |
262 | 6.58k | { |
263 | 6.58k | int i; |
264 | 20.9k | for (i = track->n_styles - 1; i >= 0; --i) { |
265 | 15.0k | if (strncmp(track->styles[i].Name, name, len) == 0 && |
266 | 7.35k | track->styles[i].Name[len] == '\0') |
267 | 716 | return track->styles + i; |
268 | 15.0k | } |
269 | 5.87k | ass_msg(track->library, MSGL_WARN, |
270 | 5.87k | "[%p]: Warning: no style named '%.*s' found", |
271 | 5.87k | track, (int) len, name); |
272 | 5.87k | return NULL; |
273 | 6.58k | } |
274 | | |
275 | | /** |
276 | | * \brief Parse style override tags. |
277 | | * \param p string to parse |
278 | | * \param end end of string to parse, which must be '}', ')', or the first |
279 | | * of a number of spaces immediately preceding '}' or ')' |
280 | | * \param pwr multiplier for some tag effects (comes from \t tags) |
281 | | */ |
282 | | char *ass_parse_tags(RenderContext *state, char *p, char *end, double pwr, |
283 | | bool nested) |
284 | 205k | { |
285 | 205k | ASS_Renderer *render_priv = state->renderer; |
286 | 757k | for (char *q; p < end; p = q) { |
287 | 846k | while (*p != '\\' && p != end) |
288 | 289k | ++p; |
289 | 557k | if (*p != '\\') |
290 | 5.45k | break; |
291 | 551k | ++p; |
292 | 551k | if (p != end) |
293 | 523k | skip_spaces(&p); |
294 | | |
295 | 551k | q = p; |
296 | 3.18M | while (*q != '(' && *q != '\\' && q != end) |
297 | 2.63M | ++q; |
298 | 551k | if (q == p) |
299 | 62.5k | continue; |
300 | | |
301 | 489k | char *name_end = q; |
302 | | |
303 | | // Store one extra element to be able to detect excess arguments |
304 | 489k | struct arg args[MAX_VALID_NARGS + 1]; |
305 | 489k | int nargs = 0; |
306 | 489k | bool has_backslash_arg = false; |
307 | 4.40M | for (int i = 0; i <= MAX_VALID_NARGS; ++i) |
308 | 3.91M | args[i].start = args[i].end = ""; |
309 | | |
310 | | // Split parenthesized arguments. Do this for all tags and before |
311 | | // any non-parenthesized argument because that's what VSFilter does. |
312 | 489k | if (*q == '(') { |
313 | 120k | ++q; |
314 | 541k | while (1) { |
315 | 541k | if (q != end) |
316 | 534k | skip_spaces(&q); |
317 | | |
318 | | // Split on commas. If there is a backslash, ignore any |
319 | | // commas following it and lump everything starting from |
320 | | // the last comma, through the backslash and all the way |
321 | | // to the end of the argument string into a single argument. |
322 | | |
323 | 541k | char *r = q; |
324 | 1.36M | while (*r != ',' && *r != '\\' && *r != ')' && r != end) |
325 | 823k | ++r; |
326 | | |
327 | 541k | if (*r == ',') { |
328 | 420k | push_arg(args, &nargs, q, r); |
329 | 420k | q = r + 1; |
330 | 420k | } else { |
331 | | // Swallow the rest of the parenthesized string. This could |
332 | | // be either a backslash-argument or simply the last argument. |
333 | 120k | if (*r == '\\') { |
334 | 44.6k | has_backslash_arg = true; |
335 | 44.6k | char *paren = memchr(r, ')', end - r); |
336 | 44.6k | if (paren) |
337 | 27.2k | r = paren; |
338 | 17.4k | else |
339 | 17.4k | r = end; |
340 | 44.6k | } |
341 | 120k | push_arg(args, &nargs, q, r); |
342 | 120k | q = r; |
343 | | // The closing parenthesis could be missing. |
344 | 120k | if (q != end) |
345 | 30.3k | ++q; |
346 | 120k | break; |
347 | 120k | } |
348 | 541k | } |
349 | 120k | } |
350 | | |
351 | 14.7M | #define tag(name) (mystrcmp(&p, (name)) && (push_arg(args, &nargs, p, name_end), 1)) |
352 | 3.32M | #define complex_tag(name) mystrcmp(&p, (name)) |
353 | | |
354 | | // New tags introduced in vsfilter 2.39 |
355 | 489k | if (tag("xbord")) { |
356 | 4.78k | double val; |
357 | 4.78k | if (nargs) { |
358 | 3.03k | val = argtod(*args); |
359 | 3.03k | val = state->border_x * (1 - pwr) + val * pwr; |
360 | 3.03k | val = (val < 0) ? 0 : val; |
361 | 3.03k | } else |
362 | 1.75k | val = state->style->Outline; |
363 | 4.78k | state->border_x = val; |
364 | 484k | } else if (tag("ybord")) { |
365 | 4.81k | double val; |
366 | 4.81k | if (nargs) { |
367 | 2.58k | val = argtod(*args); |
368 | 2.58k | val = state->border_y * (1 - pwr) + val * pwr; |
369 | 2.58k | val = (val < 0) ? 0 : val; |
370 | 2.58k | } else |
371 | 2.23k | val = state->style->Outline; |
372 | 4.81k | state->border_y = val; |
373 | 479k | } else if (tag("xshad")) { |
374 | 3.59k | double val; |
375 | 3.59k | if (nargs) { |
376 | 2.15k | val = argtod(*args); |
377 | 2.15k | val = state->shadow_x * (1 - pwr) + val * pwr; |
378 | 2.15k | } else |
379 | 1.43k | val = state->style->Shadow; |
380 | 3.59k | state->shadow_x = val; |
381 | 476k | } else if (tag("yshad")) { |
382 | 9.45k | double val; |
383 | 9.45k | if (nargs) { |
384 | 8.32k | val = argtod(*args); |
385 | 8.32k | val = state->shadow_y * (1 - pwr) + val * pwr; |
386 | 8.32k | } else |
387 | 1.12k | val = state->style->Shadow; |
388 | 9.45k | state->shadow_y = val; |
389 | 466k | } else if (tag("fax")) { |
390 | 3.51k | double val; |
391 | 3.51k | if (nargs) { |
392 | 1.85k | val = argtod(*args); |
393 | 1.85k | state->fax = |
394 | 1.85k | val * pwr + state->fax * (1 - pwr); |
395 | 1.85k | } else |
396 | 1.66k | state->fax = 0.; |
397 | 463k | } else if (tag("fay")) { |
398 | 4.81k | double val; |
399 | 4.81k | if (nargs) { |
400 | 2.73k | val = argtod(*args); |
401 | 2.73k | state->fay = |
402 | 2.73k | val * pwr + state->fay * (1 - pwr); |
403 | 2.73k | } else |
404 | 2.08k | state->fay = 0.; |
405 | 458k | } else if (complex_tag("iclip")) { |
406 | 4.72k | if (nargs == 4) { |
407 | 717 | int32_t x0, y0, x1, y1; |
408 | 717 | x0 = argtoi32(args[0]); |
409 | 717 | y0 = argtoi32(args[1]); |
410 | 717 | x1 = argtoi32(args[2]); |
411 | 717 | y1 = argtoi32(args[3]); |
412 | 717 | state->clip_x0 = |
413 | 717 | state->clip_x0 * (1 - pwr) + x0 * pwr; |
414 | 717 | state->clip_x1 = |
415 | 717 | state->clip_x1 * (1 - pwr) + x1 * pwr; |
416 | 717 | state->clip_y0 = |
417 | 717 | state->clip_y0 * (1 - pwr) + y0 * pwr; |
418 | 717 | state->clip_y1 = |
419 | 717 | state->clip_y1 * (1 - pwr) + y1 * pwr; |
420 | 717 | state->clip_mode = 1; |
421 | 4.00k | } else if (!state->clip_drawing_text.str) { |
422 | 2.36k | if (parse_vector_clip(state, args, nargs)) |
423 | 1.51k | state->clip_drawing_mode = 1; |
424 | 2.36k | } |
425 | 453k | } else if (tag("blur")) { |
426 | 3.65k | double val; |
427 | 3.65k | if (nargs) { |
428 | 2.59k | val = argtod(*args); |
429 | 2.59k | val = state->blur * (1 - pwr) + val * pwr; |
430 | 2.59k | val = (val < 0) ? 0 : val; |
431 | 2.59k | val = (val > BLUR_MAX_RADIUS) ? BLUR_MAX_RADIUS : val; |
432 | 2.59k | state->blur = val; |
433 | 2.59k | } else |
434 | 1.06k | state->blur = 0.0; |
435 | | // ASS standard tags |
436 | 449k | } else if (tag("fscx")) { |
437 | 3.42k | double val; |
438 | 3.42k | if (nargs) { |
439 | 2.18k | val = argtod(*args) / 100; |
440 | 2.18k | val = state->scale_x * (1 - pwr) + val * pwr; |
441 | 2.18k | val = (val < 0) ? 0 : val; |
442 | 2.18k | } else |
443 | 1.23k | val = state->style->ScaleX; |
444 | 3.42k | state->scale_x = val; |
445 | 446k | } else if (tag("fscy")) { |
446 | 3.20k | double val; |
447 | 3.20k | if (nargs) { |
448 | 1.98k | val = argtod(*args) / 100; |
449 | 1.98k | val = state->scale_y * (1 - pwr) + val * pwr; |
450 | 1.98k | val = (val < 0) ? 0 : val; |
451 | 1.98k | } else |
452 | 1.22k | val = state->style->ScaleY; |
453 | 3.20k | state->scale_y = val; |
454 | 443k | } else if (tag("fsc")) { |
455 | 3.43k | state->scale_x = state->style->ScaleX; |
456 | 3.43k | state->scale_y = state->style->ScaleY; |
457 | 439k | } else if (tag("fsp")) { |
458 | 5.55k | double val; |
459 | 5.55k | if (nargs) { |
460 | 3.07k | val = argtod(*args); |
461 | 3.07k | state->hspacing = |
462 | 3.07k | state->hspacing * (1 - pwr) + val * pwr; |
463 | 3.07k | } else |
464 | 2.48k | state->hspacing = state->style->Spacing; |
465 | 434k | } else if (tag("fs")) { |
466 | 7.54k | double val = 0; |
467 | 7.54k | if (nargs) { |
468 | 4.69k | val = argtod(*args); |
469 | 4.69k | if (*args->start == '+' || *args->start == '-') |
470 | 1.95k | val = state->font_size * (1 + pwr * val / 10); |
471 | 2.73k | else |
472 | 2.73k | val = state->font_size * (1 - pwr) + val * pwr; |
473 | 4.69k | } |
474 | 7.54k | if (val <= 0) |
475 | 5.55k | val = state->style->FontSize; |
476 | 7.54k | state->font_size = val; |
477 | 426k | } else if (tag("bord")) { |
478 | 3.09k | double val, xval, yval; |
479 | 3.09k | if (nargs) { |
480 | 2.28k | val = argtod(*args); |
481 | 2.28k | xval = state->border_x * (1 - pwr) + val * pwr; |
482 | 2.28k | yval = state->border_y * (1 - pwr) + val * pwr; |
483 | 2.28k | xval = (xval < 0) ? 0 : xval; |
484 | 2.28k | yval = (yval < 0) ? 0 : yval; |
485 | 2.28k | } else |
486 | 808 | xval = yval = state->style->Outline; |
487 | 3.09k | state->border_x = xval; |
488 | 3.09k | state->border_y = yval; |
489 | 423k | } else if (complex_tag("move")) { |
490 | 9.51k | double x1, x2, y1, y2; |
491 | 9.51k | int32_t t1, t2, delta_t, t; |
492 | 9.51k | double x, y; |
493 | 9.51k | double k; |
494 | 9.51k | if (nargs == 4 || nargs == 6) { |
495 | 6.81k | x1 = argtod(args[0]); |
496 | 6.81k | y1 = argtod(args[1]); |
497 | 6.81k | x2 = argtod(args[2]); |
498 | 6.81k | y2 = argtod(args[3]); |
499 | 6.81k | t1 = t2 = 0; |
500 | 6.81k | if (nargs == 6) { |
501 | 3.85k | t1 = argtoi32(args[4]); |
502 | 3.85k | t2 = argtoi32(args[5]); |
503 | 3.85k | if (t1 > t2) { |
504 | 2.37k | long long tmp = t2; |
505 | 2.37k | t2 = t1; |
506 | 2.37k | t1 = tmp; |
507 | 2.37k | } |
508 | 3.85k | } |
509 | 6.81k | } else |
510 | 2.70k | continue; |
511 | 6.81k | if (t1 <= 0 && t2 <= 0) { |
512 | 3.40k | t1 = 0; |
513 | 3.40k | t2 = state->event->Duration; |
514 | 3.40k | } |
515 | 6.81k | delta_t = (uint32_t) t2 - t1; |
516 | 6.81k | t = render_priv->time - state->event->Start; |
517 | 6.81k | if (t <= t1) |
518 | 2.54k | k = 0.; |
519 | 4.26k | else if (t >= t2) |
520 | 2.07k | k = 1.; |
521 | 2.19k | else |
522 | 2.19k | k = ((double) (int32_t) ((uint32_t) t - t1)) / delta_t; |
523 | 6.81k | x = k * (x2 - x1) + x1; |
524 | 6.81k | y = k * (y2 - y1) + y1; |
525 | 6.81k | if (!(state->evt_type & EVENT_POSITIONED)) { |
526 | 5.41k | state->pos_x = x; |
527 | 5.41k | state->pos_y = y; |
528 | 5.41k | state->detect_collisions = 0; |
529 | 5.41k | state->evt_type |= EVENT_POSITIONED; |
530 | 5.41k | } |
531 | 414k | } else if (tag("frx")) { |
532 | 4.65k | double val; |
533 | 4.65k | if (nargs) { |
534 | 3.67k | val = argtod(*args); |
535 | 3.67k | state->frx = |
536 | 3.67k | val * pwr + state->frx * (1 - pwr); |
537 | 3.67k | } else |
538 | 987 | state->frx = 0.; |
539 | 409k | } else if (tag("fry")) { |
540 | 3.78k | double val; |
541 | 3.78k | if (nargs) { |
542 | 1.62k | val = argtod(*args); |
543 | 1.62k | state->fry = |
544 | 1.62k | val * pwr + state->fry * (1 - pwr); |
545 | 1.62k | } else |
546 | 2.15k | state->fry = 0.; |
547 | 405k | } else if (tag("frz") || tag("fr")) { |
548 | 8.75k | double val; |
549 | 8.75k | if (nargs) { |
550 | 5.07k | val = argtod(*args); |
551 | 5.07k | state->frz = |
552 | 5.07k | val * pwr + state->frz * (1 - pwr); |
553 | 5.07k | } else |
554 | 3.67k | state->frz = |
555 | 3.67k | state->style->Angle; |
556 | 396k | } else if (tag("fn")) { |
557 | 7.38k | char *start = args->start; |
558 | 7.38k | if (nargs && strncmp(start, "0", args->end - start)) { |
559 | 4.79k | skip_spaces(&start); |
560 | 4.79k | state->family.str = start; |
561 | 4.79k | state->family.len = args->end - start; |
562 | 4.79k | } else { |
563 | 2.59k | state->family.str = state->style->FontName; |
564 | 2.59k | state->family.len = strlen(state->style->FontName); |
565 | 2.59k | } |
566 | 7.38k | ass_update_font(state); |
567 | 389k | } else if (tag("alpha")) { |
568 | 3.90k | int i; |
569 | 3.90k | if (nargs) { |
570 | 2.84k | int32_t a = parse_alpha_tag(args->start); |
571 | 14.2k | for (i = 0; i < 4; ++i) |
572 | 11.3k | change_alpha(&state->c[i], a, pwr); |
573 | 2.84k | } else { |
574 | 1.06k | change_alpha(&state->c[0], |
575 | 1.06k | _a(state->style->PrimaryColour), 1); |
576 | 1.06k | change_alpha(&state->c[1], |
577 | 1.06k | _a(state->style->SecondaryColour), 1); |
578 | 1.06k | change_alpha(&state->c[2], |
579 | 1.06k | _a(state->style->OutlineColour), 1); |
580 | 1.06k | change_alpha(&state->c[3], |
581 | 1.06k | _a(state->style->BackColour), 1); |
582 | 1.06k | } |
583 | | // FIXME: simplify |
584 | 385k | } else if (tag("an")) { |
585 | 8.57k | int32_t val = argtoi32(*args); |
586 | 8.57k | if ((state->parsed_tags & PARSED_A) == 0) { |
587 | 6.11k | if (val >= 1 && val <= 9) |
588 | 2.43k | state->alignment = numpad2align(val); |
589 | 3.67k | else |
590 | 3.67k | state->alignment = |
591 | 3.67k | state->style->Alignment; |
592 | 6.11k | state->parsed_tags |= PARSED_A; |
593 | 6.11k | } |
594 | 377k | } else if (tag("a")) { |
595 | 13.2k | int32_t val = argtoi32(*args); |
596 | 13.2k | if ((state->parsed_tags & PARSED_A) == 0) { |
597 | 8.19k | if (val >= 1 && val <= 11) |
598 | | // take care of a vsfilter quirk: |
599 | | // handle illegal \a8 and \a4 like \a5 |
600 | 2.34k | state->alignment = ((val & 3) == 0) ? 5 : val; |
601 | 5.84k | else |
602 | 5.84k | state->alignment = |
603 | 5.84k | state->style->Alignment; |
604 | 8.19k | state->parsed_tags |= PARSED_A; |
605 | 8.19k | } |
606 | 363k | } else if (complex_tag("pos")) { |
607 | 2.83k | double v1, v2; |
608 | 2.83k | if (nargs == 2) { |
609 | 1.84k | v1 = argtod(args[0]); |
610 | 1.84k | v2 = argtod(args[1]); |
611 | 1.84k | } else |
612 | 985 | continue; |
613 | 1.84k | if (state->evt_type & EVENT_POSITIONED) { |
614 | 736 | ass_msg(render_priv->library, MSGL_V, "Subtitle has a new \\pos " |
615 | 736 | "after \\move or \\pos, ignoring"); |
616 | 1.11k | } else { |
617 | 1.11k | state->evt_type |= EVENT_POSITIONED; |
618 | 1.11k | state->detect_collisions = 0; |
619 | 1.11k | state->pos_x = v1; |
620 | 1.11k | state->pos_y = v2; |
621 | 1.11k | } |
622 | 361k | } else if (complex_tag("fade") || complex_tag("fad")) { |
623 | 7.29k | int32_t a1, a2, a3; |
624 | 7.29k | int32_t t1, t2, t3, t4; |
625 | 7.29k | if (nargs == 2) { |
626 | | // 2-argument version (\fad, according to specs) |
627 | 3.47k | a1 = 0xFF; |
628 | 3.47k | a2 = 0; |
629 | 3.47k | a3 = 0xFF; |
630 | 3.47k | t1 = -1; |
631 | 3.47k | t2 = argtoi32(args[0]); |
632 | 3.47k | t3 = argtoi32(args[1]); |
633 | 3.47k | t4 = -1; |
634 | 3.81k | } else if (nargs == 7) { |
635 | | // 7-argument version (\fade) |
636 | 1.99k | a1 = argtoi32(args[0]); |
637 | 1.99k | a2 = argtoi32(args[1]); |
638 | 1.99k | a3 = argtoi32(args[2]); |
639 | 1.99k | t1 = argtoi32(args[3]); |
640 | 1.99k | t2 = argtoi32(args[4]); |
641 | 1.99k | t3 = argtoi32(args[5]); |
642 | 1.99k | t4 = argtoi32(args[6]); |
643 | 1.99k | } else |
644 | 1.82k | continue; |
645 | 5.46k | if (t1 == -1 && t4 == -1) { |
646 | 3.47k | t1 = 0; |
647 | 3.47k | t4 = state->event->Duration; |
648 | 3.47k | t3 = (uint32_t) t4 - t3; |
649 | 3.47k | } |
650 | 5.46k | if ((state->parsed_tags & PARSED_FADE) == 0) { |
651 | 4.56k | state->fade = |
652 | 4.56k | interpolate_alpha(render_priv->time - |
653 | 4.56k | state->event->Start, t1, t2, |
654 | 4.56k | t3, t4, a1, a2, a3); |
655 | 4.56k | state->parsed_tags |= PARSED_FADE; |
656 | 4.56k | } |
657 | 353k | } else if (complex_tag("org")) { |
658 | 3.19k | double v1, v2; |
659 | 3.19k | if (nargs == 2) { |
660 | 1.97k | v1 = argtod(args[0]); |
661 | 1.97k | v2 = argtod(args[1]); |
662 | 1.97k | } else |
663 | 1.22k | continue; |
664 | 1.97k | if (!state->have_origin) { |
665 | 957 | state->org_x = v1; |
666 | 957 | state->org_y = v2; |
667 | 957 | state->have_origin = 1; |
668 | 957 | state->detect_collisions = 0; |
669 | 957 | } |
670 | 350k | } else if (complex_tag("t")) { |
671 | 55.5k | double accel; |
672 | 55.5k | int cnt = nargs - 1; |
673 | 55.5k | int32_t t1, t2, t, delta_t; |
674 | 55.5k | double k; |
675 | | // VSFilter compatibility (because we can): parse the |
676 | | // timestamps differently depending on argument count. |
677 | 55.5k | if (cnt == 3) { |
678 | 1.75k | t1 = argtoi32(args[0]); |
679 | 1.75k | t2 = argtoi32(args[1]); |
680 | 1.75k | accel = argtod(args[2]); |
681 | 53.7k | } else if (cnt == 2) { |
682 | 9.50k | t1 = dtoi32(argtod(args[0])); |
683 | 9.50k | t2 = dtoi32(argtod(args[1])); |
684 | 9.50k | accel = 1.; |
685 | 44.2k | } else if (cnt == 1) { |
686 | 3.30k | t1 = 0; |
687 | 3.30k | t2 = 0; |
688 | 3.30k | accel = argtod(args[0]); |
689 | 40.9k | } else { |
690 | 40.9k | t1 = 0; |
691 | 40.9k | t2 = 0; |
692 | 40.9k | accel = 1.; |
693 | 40.9k | } |
694 | 55.5k | state->detect_collisions = 0; |
695 | 55.5k | if (t2 == 0) |
696 | 47.4k | t2 = state->event->Duration; |
697 | 55.5k | delta_t = (uint32_t) t2 - t1; |
698 | 55.5k | t = render_priv->time - state->event->Start; // FIXME: move to render_context |
699 | 55.5k | if (t < t1) |
700 | 1.90k | k = 0.; |
701 | 53.6k | else if (t >= t2) |
702 | 5.95k | k = 1.; |
703 | 47.6k | else { |
704 | 47.6k | assert(delta_t != 0.); |
705 | 47.6k | k = pow((double) (int32_t) ((uint32_t) t - t1) / delta_t, accel); |
706 | 47.6k | } |
707 | 55.5k | if (nested) |
708 | 11.8k | pwr = k; |
709 | 55.5k | if (cnt < 0 || cnt > 3) |
710 | 12.9k | continue; |
711 | | // If there's no backslash in the arguments, there are no |
712 | | // override tags, so it's pointless to try to parse them. |
713 | 42.5k | if (!has_backslash_arg) |
714 | 5.93k | continue; |
715 | 36.6k | p = args[cnt].start; |
716 | 36.6k | if (args[cnt].end < end) { |
717 | 23.8k | assert(!nested); |
718 | 23.8k | p = ass_parse_tags(state, p, args[cnt].end, k, true); |
719 | 23.8k | } else { |
720 | 12.7k | assert(q == end); |
721 | | // No other tags can possibly follow this \t tag, |
722 | | // so we don't need to restore pwr after parsing \t. |
723 | | // The recursive call is now essentially a tail call, |
724 | | // so optimize it away. |
725 | 12.7k | pwr = k; |
726 | 12.7k | nested = true; |
727 | 12.7k | q = p; |
728 | 12.7k | } |
729 | 295k | } else if (complex_tag("clip")) { |
730 | 5.51k | if (nargs == 4) { |
731 | 711 | int32_t x0, y0, x1, y1; |
732 | 711 | x0 = argtoi32(args[0]); |
733 | 711 | y0 = argtoi32(args[1]); |
734 | 711 | x1 = argtoi32(args[2]); |
735 | 711 | y1 = argtoi32(args[3]); |
736 | 711 | state->clip_x0 = |
737 | 711 | state->clip_x0 * (1 - pwr) + x0 * pwr; |
738 | 711 | state->clip_x1 = |
739 | 711 | state->clip_x1 * (1 - pwr) + x1 * pwr; |
740 | 711 | state->clip_y0 = |
741 | 711 | state->clip_y0 * (1 - pwr) + y0 * pwr; |
742 | 711 | state->clip_y1 = |
743 | 711 | state->clip_y1 * (1 - pwr) + y1 * pwr; |
744 | 711 | state->clip_mode = 0; |
745 | 4.80k | } else if (!state->clip_drawing_text.str) { |
746 | 4.06k | if (parse_vector_clip(state, args, nargs)) |
747 | 2.89k | state->clip_drawing_mode = 0; |
748 | 4.06k | } |
749 | 289k | } else if (tag("c") || tag("1c")) { |
750 | 13.2k | if (nargs) { |
751 | 9.21k | uint32_t val = parse_color_tag(args->start); |
752 | 9.21k | change_color(&state->c[0], val, pwr); |
753 | 9.21k | } else |
754 | 4.02k | change_color(&state->c[0], |
755 | 4.02k | state->style->PrimaryColour, 1); |
756 | 276k | } else if (tag("2c")) { |
757 | 4.29k | if (nargs) { |
758 | 2.82k | uint32_t val = parse_color_tag(args->start); |
759 | 2.82k | change_color(&state->c[1], val, pwr); |
760 | 2.82k | } else |
761 | 1.46k | change_color(&state->c[1], |
762 | 1.46k | state->style->SecondaryColour, 1); |
763 | 271k | } else if (tag("3c")) { |
764 | 3.21k | if (nargs) { |
765 | 1.62k | uint32_t val = parse_color_tag(args->start); |
766 | 1.62k | change_color(&state->c[2], val, pwr); |
767 | 1.62k | } else |
768 | 1.59k | change_color(&state->c[2], |
769 | 1.59k | state->style->OutlineColour, 1); |
770 | 268k | } else if (tag("4c")) { |
771 | 5.10k | if (nargs) { |
772 | 2.85k | uint32_t val = parse_color_tag(args->start); |
773 | 2.85k | change_color(&state->c[3], val, pwr); |
774 | 2.85k | } else |
775 | 2.25k | change_color(&state->c[3], |
776 | 2.25k | state->style->BackColour, 1); |
777 | 263k | } else if (tag("1a")) { |
778 | 4.58k | if (nargs) { |
779 | 3.11k | uint32_t val = parse_alpha_tag(args->start); |
780 | 3.11k | change_alpha(&state->c[0], val, pwr); |
781 | 3.11k | } else |
782 | 1.46k | change_alpha(&state->c[0], |
783 | 1.46k | _a(state->style->PrimaryColour), 1); |
784 | 259k | } else if (tag("2a")) { |
785 | 5.23k | if (nargs) { |
786 | 3.29k | uint32_t val = parse_alpha_tag(args->start); |
787 | 3.29k | change_alpha(&state->c[1], val, pwr); |
788 | 3.29k | } else |
789 | 1.94k | change_alpha(&state->c[1], |
790 | 1.94k | _a(state->style->SecondaryColour), 1); |
791 | 253k | } else if (tag("3a")) { |
792 | 4.67k | if (nargs) { |
793 | 3.16k | uint32_t val = parse_alpha_tag(args->start); |
794 | 3.16k | change_alpha(&state->c[2], val, pwr); |
795 | 3.16k | } else |
796 | 1.50k | change_alpha(&state->c[2], |
797 | 1.50k | _a(state->style->OutlineColour), 1); |
798 | 249k | } else if (tag("4a")) { |
799 | 3.78k | if (nargs) { |
800 | 2.51k | uint32_t val = parse_alpha_tag(args->start); |
801 | 2.51k | change_alpha(&state->c[3], val, pwr); |
802 | 2.51k | } else |
803 | 1.27k | change_alpha(&state->c[3], |
804 | 1.27k | _a(state->style->BackColour), 1); |
805 | 245k | } else if (tag("r")) { |
806 | 10.4k | if (nargs) { |
807 | 6.58k | int len = args->end - args->start; |
808 | 6.58k | ass_reset_render_context(state, |
809 | 6.58k | lookup_style_strict(render_priv->track, args->start, len)); |
810 | 6.58k | } else |
811 | 3.84k | ass_reset_render_context(state, NULL); |
812 | 234k | } else if (tag("be")) { |
813 | 5.19k | double dval; |
814 | 5.19k | if (nargs) { |
815 | 3.31k | int32_t val; |
816 | 3.31k | dval = argtod(*args); |
817 | | // VSFilter always adds +0.5, even if the value is negative |
818 | 3.31k | val = dtoi32(state->be * (1 - pwr) + dval * pwr + 0.5); |
819 | | // Clamp to a safe upper limit, since high values need excessive CPU |
820 | 3.31k | val = (val < 0) ? 0 : val; |
821 | 3.31k | val = (val > MAX_BE) ? MAX_BE : val; |
822 | 3.31k | state->be = val; |
823 | 3.31k | } else |
824 | 1.87k | state->be = 0; |
825 | 229k | } else if (tag("b")) { |
826 | 22.5k | int32_t val = argtoi32(*args); |
827 | 22.5k | if (!nargs || !(val == 0 || val == 1 || val >= 100)) |
828 | 6.82k | val = state->style->Bold; |
829 | 22.5k | state->bold = val; |
830 | 22.5k | ass_update_font(state); |
831 | 207k | } else if (tag("i")) { |
832 | 36.2k | int32_t val = argtoi32(*args); |
833 | 36.2k | if (!nargs || !(val == 0 || val == 1)) |
834 | 24.3k | val = state->style->Italic; |
835 | 36.2k | state->italic = val; |
836 | 36.2k | ass_update_font(state); |
837 | 170k | } else if (tag("kt")) { |
838 | | // v4++ |
839 | 6.23k | double val = 0; |
840 | 6.23k | if (nargs) |
841 | 4.36k | val = argtod(*args) * 10; |
842 | 6.23k | state->effect_skip_timing = dtoi32(val); |
843 | 6.23k | state->effect_timing = 0; |
844 | 6.23k | state->reset_effect = true; |
845 | 164k | } else if (tag("kf") || tag("K")) { |
846 | 10.0k | double val = 100; |
847 | 10.0k | if (nargs) |
848 | 7.95k | val = argtod(*args); |
849 | 10.0k | state->effect_type = EF_KARAOKE_KF; |
850 | 10.0k | state->effect_skip_timing += |
851 | 10.0k | (uint32_t) state->effect_timing; |
852 | 10.0k | state->effect_timing = dtoi32(val * 10); |
853 | 154k | } else if (tag("ko")) { |
854 | 33.8k | double val = 100; |
855 | 33.8k | if (nargs) |
856 | 32.5k | val = argtod(*args); |
857 | 33.8k | state->effect_type = EF_KARAOKE_KO; |
858 | 33.8k | state->effect_skip_timing += |
859 | 33.8k | (uint32_t) state->effect_timing; |
860 | 33.8k | state->effect_timing = dtoi32(val * 10); |
861 | 120k | } else if (tag("k")) { |
862 | 13.6k | double val = 100; |
863 | 13.6k | if (nargs) |
864 | 10.2k | val = argtod(*args); |
865 | 13.6k | state->effect_type = EF_KARAOKE; |
866 | 13.6k | state->effect_skip_timing += |
867 | 13.6k | (uint32_t) state->effect_timing; |
868 | 13.6k | state->effect_timing = dtoi32(val * 10); |
869 | 107k | } else if (tag("shad")) { |
870 | 4.89k | double val, xval, yval; |
871 | 4.89k | if (nargs) { |
872 | 2.41k | val = argtod(*args); |
873 | 2.41k | xval = state->shadow_x * (1 - pwr) + val * pwr; |
874 | 2.41k | yval = state->shadow_y * (1 - pwr) + val * pwr; |
875 | | // VSFilter compatibility: clip for \shad but not for \[xy]shad |
876 | 2.41k | xval = (xval < 0) ? 0 : xval; |
877 | 2.41k | yval = (yval < 0) ? 0 : yval; |
878 | 2.41k | } else |
879 | 2.47k | xval = yval = state->style->Shadow; |
880 | 4.89k | state->shadow_x = xval; |
881 | 4.89k | state->shadow_y = yval; |
882 | 102k | } else if (tag("s")) { |
883 | 7.92k | int32_t val = argtoi32(*args); |
884 | 7.92k | if (!nargs || !(val == 0 || val == 1)) |
885 | 3.31k | val = state->style->StrikeOut; |
886 | 7.92k | if (val) |
887 | 847 | state->flags |= DECO_STRIKETHROUGH; |
888 | 7.07k | else |
889 | 7.07k | state->flags &= ~DECO_STRIKETHROUGH; |
890 | 94.2k | } else if (tag("u")) { |
891 | 8.33k | int32_t val = argtoi32(*args); |
892 | 8.33k | if (!nargs || !(val == 0 || val == 1)) |
893 | 4.52k | val = state->style->Underline; |
894 | 8.33k | if (val) |
895 | 1.06k | state->flags |= DECO_UNDERLINE; |
896 | 7.27k | else |
897 | 7.27k | state->flags &= ~DECO_UNDERLINE; |
898 | 85.9k | } else if (tag("pbo")) { |
899 | 3.63k | double val = argtod(*args); |
900 | 3.63k | state->pbo = val; |
901 | 82.3k | } else if (tag("p")) { |
902 | 8.67k | int32_t val = argtoi32(*args); |
903 | 8.67k | val = (val < 0) ? 0 : val; |
904 | 8.67k | state->drawing_scale = val; |
905 | 73.6k | } else if (tag("q")) { |
906 | 12.3k | int32_t val = argtoi32(*args); |
907 | 12.3k | if (!nargs || !(val >= 0 && val <= 3)) |
908 | 8.92k | val = render_priv->track->WrapStyle; |
909 | 12.3k | state->wrap_style = val; |
910 | 61.3k | } else if (tag("fe")) { |
911 | 5.06k | int32_t val; |
912 | 5.06k | if (nargs) |
913 | 3.05k | val = argtoi32(*args); |
914 | 2.01k | else |
915 | 2.01k | val = state->style->Encoding; |
916 | 5.06k | state->font_encoding = val; |
917 | 5.06k | } |
918 | 489k | } |
919 | | |
920 | 205k | return p; |
921 | 205k | } |
922 | | |
923 | | void ass_apply_transition_effects(RenderContext *state) |
924 | 375k | { |
925 | 375k | ASS_Renderer *render_priv = state->renderer; |
926 | 375k | int v[4]; |
927 | 375k | int cnt; |
928 | 375k | ASS_Event *event = state->event; |
929 | 375k | char *p = event->Effect; |
930 | | |
931 | 375k | if (!p || !*p) |
932 | 297k | return; |
933 | | |
934 | 77.8k | cnt = 0; |
935 | 100k | while (cnt < 4 && (p = strchr(p, ';'))) { |
936 | 23.0k | v[cnt++] = atoi(++p); |
937 | 23.0k | } |
938 | | |
939 | 77.8k | ASS_Vector layout_res = ass_layout_res(render_priv); |
940 | 77.8k | if (strncmp(event->Effect, "Banner;", 7) == 0) { |
941 | 4.60k | double delay; |
942 | 4.60k | if (cnt < 1) { |
943 | 0 | ass_msg(render_priv->library, MSGL_V, |
944 | 0 | "Error parsing effect: '%s'", event->Effect); |
945 | 0 | return; |
946 | 0 | } |
947 | 4.60k | if (cnt >= 2 && v[1]) // left-to-right |
948 | 2.09k | state->scroll_direction = SCROLL_LR; |
949 | 2.50k | else // right-to-left |
950 | 2.50k | state->scroll_direction = SCROLL_RL; |
951 | | |
952 | 4.60k | delay = v[0]; |
953 | | // VSF works in storage coordinates, but scales delay to PlayRes canvas |
954 | | // before applying max(scaled_ delay, 1). This means, if scaled_delay < 1 |
955 | | // (esp. delay=0) we end up with 1 ms per _storage pixel_ without any |
956 | | // PlayRes scaling. |
957 | | // The way libass deals with delay, it is automatically relative to the |
958 | | // PlayRes canvas, so we only want to "unscale" the small delay values. |
959 | | // |
960 | | // VSF also casts the scaled delay to int, which if not emulated leads to |
961 | | // easily noticeable deviations from VSFilter as the effect goes on. |
962 | | // To achieve both we need to keep our Playres-relative delay with high precision, |
963 | | // but must temporarily convert to storage-relative and truncate and take the |
964 | | // maxuimum there, before converting back. |
965 | 4.60k | double scale_x = ((double) layout_res.x) / render_priv->track->PlayResX; |
966 | 4.60k | delay = ((int) FFMAX(delay / scale_x, 1)) * scale_x; |
967 | 4.60k | state->scroll_shift = |
968 | 4.60k | (render_priv->time - event->Start) / delay; |
969 | 4.60k | state->evt_type |= EVENT_HSCROLL; |
970 | 4.60k | state->detect_collisions = 0; |
971 | 4.60k | state->wrap_style = 2; |
972 | 4.60k | return; |
973 | 4.60k | } |
974 | | |
975 | 73.2k | if (strncmp(event->Effect, "Scroll up;", 10) == 0) { |
976 | 3.30k | state->scroll_direction = SCROLL_BT; |
977 | 69.9k | } else if (strncmp(event->Effect, "Scroll down;", 12) == 0) { |
978 | 734 | state->scroll_direction = SCROLL_TB; |
979 | 69.1k | } else { |
980 | 69.1k | ass_msg(render_priv->library, MSGL_DBG2, |
981 | 69.1k | "Unknown transition effect: '%s'", event->Effect); |
982 | 69.1k | return; |
983 | 69.1k | } |
984 | | // parse scroll up/down parameters |
985 | 4.04k | { |
986 | 4.04k | double delay; |
987 | 4.04k | int y0, y1; |
988 | 4.04k | if (cnt < 3) { |
989 | 1.34k | ass_msg(render_priv->library, MSGL_V, |
990 | 1.34k | "Error parsing effect: '%s'", event->Effect); |
991 | 1.34k | return; |
992 | 1.34k | } |
993 | 2.69k | delay = v[2]; |
994 | | // See explanation for Banner |
995 | 2.69k | double scale_y = ((double) layout_res.y) / render_priv->track->PlayResY; |
996 | 2.69k | delay = ((int) FFMAX(delay / scale_y, 1)) * scale_y; |
997 | 2.69k | state->scroll_shift = |
998 | 2.69k | (render_priv->time - event->Start) / delay; |
999 | 2.69k | if (v[0] < v[1]) { |
1000 | 1.00k | y0 = v[0]; |
1001 | 1.00k | y1 = v[1]; |
1002 | 1.69k | } else { |
1003 | 1.69k | y0 = v[1]; |
1004 | 1.69k | y1 = v[0]; |
1005 | 1.69k | } |
1006 | 2.69k | state->scroll_y0 = y0; |
1007 | 2.69k | state->scroll_y1 = y1; |
1008 | 2.69k | state->evt_type |= EVENT_VSCROLL; |
1009 | 2.69k | state->detect_collisions = 0; |
1010 | 2.69k | } |
1011 | | |
1012 | 2.69k | } |
1013 | | |
1014 | | /** |
1015 | | * \brief determine karaoke effects |
1016 | | * Karaoke effects cannot be calculated during parse stage (ass_get_next_char()), |
1017 | | * so they are done in a separate step. |
1018 | | * Parse stage: when karaoke style override is found, its parameters are stored in the next glyph's |
1019 | | * (the first glyph of the karaoke word)'s effect_type and effect_timing. |
1020 | | * This function: |
1021 | | * 1. sets effect_type for all glyphs in the word (_karaoke_ word) |
1022 | | * 2. sets effect_timing for all glyphs to x coordinate of the border line between the left and right karaoke parts |
1023 | | * (left part is filled with PrimaryColour, right one - with SecondaryColour). |
1024 | | */ |
1025 | | void ass_process_karaoke_effects(RenderContext *state) |
1026 | 108k | { |
1027 | 108k | TextInfo *text_info = &state->text_info; |
1028 | 108k | long long tm_current = state->renderer->time - state->event->Start; |
1029 | | |
1030 | 108k | int32_t timing = 0, skip_timing = 0; |
1031 | 108k | Effect effect_type = EF_NONE; |
1032 | 108k | GlyphInfo *last_boundary = NULL; |
1033 | 108k | bool has_reset = false; |
1034 | 948k | for (int i = 0; i <= text_info->length; i++) { |
1035 | 839k | if (i < text_info->length && |
1036 | 730k | !text_info->glyphs[i].starts_new_run) { |
1037 | | |
1038 | 618k | if (text_info->glyphs[i].reset_effect) { |
1039 | 49 | has_reset = true; |
1040 | 49 | skip_timing = 0; |
1041 | 49 | } |
1042 | | |
1043 | | // VSFilter compatibility: if we have \k12345\k0 without a run |
1044 | | // break, subsequent text is still part of the same karaoke word, |
1045 | | // the current word's starting and ending time stay unchanged, |
1046 | | // but the starting time of the next karaoke word is advanced. |
1047 | 618k | skip_timing += (uint32_t) text_info->glyphs[i].effect_skip_timing; |
1048 | 618k | continue; |
1049 | 618k | } |
1050 | | |
1051 | 221k | GlyphInfo *start = last_boundary; |
1052 | 221k | GlyphInfo *end = text_info->glyphs + i; |
1053 | 221k | last_boundary = end; |
1054 | 221k | if (!start) |
1055 | 108k | continue; |
1056 | | |
1057 | 112k | if (start->effect_type != EF_NONE) |
1058 | 6.86k | effect_type = start->effect_type; |
1059 | 112k | if (effect_type == EF_NONE) |
1060 | 105k | continue; |
1061 | | |
1062 | 6.87k | if (start->reset_effect) |
1063 | 49 | timing = 0; |
1064 | | |
1065 | 6.87k | long long tm_start = timing + start->effect_skip_timing; |
1066 | 6.87k | long long tm_end = tm_start + start->effect_timing; |
1067 | 6.87k | timing = !has_reset * tm_end + skip_timing; |
1068 | 6.87k | skip_timing = 0; |
1069 | 6.87k | has_reset = false; |
1070 | | |
1071 | 6.87k | if (effect_type != EF_KARAOKE_KF) |
1072 | 4.30k | tm_end = tm_start; |
1073 | | |
1074 | 6.87k | int x; |
1075 | 6.87k | if (tm_current < tm_start) |
1076 | 372 | x = -100000000; |
1077 | 6.50k | else if (tm_current >= tm_end) |
1078 | 6.15k | x = 100000000; |
1079 | 354 | else { |
1080 | 354 | GlyphInfo *first_visible = start, *last_visible = end - 1; |
1081 | 354 | while (first_visible < last_visible && first_visible->skip) |
1082 | 0 | ++first_visible; |
1083 | 354 | while (first_visible < last_visible && last_visible->skip) |
1084 | 0 | --last_visible; |
1085 | | |
1086 | 354 | int x_start = first_visible->pos.x; |
1087 | 354 | int x_end = last_visible->pos.x + last_visible->advance.x; |
1088 | 354 | double dt = (double) (tm_current - tm_start) / (tm_end - tm_start); |
1089 | 354 | double frz = fmod(start->frz, 360); |
1090 | 354 | if (frz > 90 && frz < 270) { |
1091 | | // Fill from right to left |
1092 | 0 | dt = 1 - dt; |
1093 | 0 | for (GlyphInfo *info = start; info < end; info++) { |
1094 | 0 | uint32_t tmp = info->c[0]; |
1095 | 0 | info->c[0] = info->c[1]; |
1096 | 0 | info->c[1] = tmp; |
1097 | 0 | } |
1098 | 0 | } |
1099 | 354 | x = x_start + ass_lrint((x_end - x_start) * dt); |
1100 | 354 | } |
1101 | | |
1102 | 29.5k | for (GlyphInfo *info = start; info < end; info++) { |
1103 | 22.6k | info->effect_type = effect_type; |
1104 | 22.6k | info->effect_timing = x - info->pos.x; |
1105 | 22.6k | } |
1106 | 6.87k | } |
1107 | 108k | } |
1108 | | |
1109 | | |
1110 | | /** |
1111 | | * \brief Get next ucs4 char from string, parsing UTF-8 and escapes |
1112 | | * \param str string pointer |
1113 | | * \return ucs4 code of the next char |
1114 | | * On return str points to the unparsed part of the string |
1115 | | */ |
1116 | | unsigned ass_get_next_char(RenderContext *state, char **str) |
1117 | 727k | { |
1118 | 727k | char *p = *str; |
1119 | 727k | unsigned chr; |
1120 | 727k | if (*p == '\t') { |
1121 | 2.19k | ++p; |
1122 | 2.19k | *str = p; |
1123 | 2.19k | return ' '; |
1124 | 2.19k | } |
1125 | 725k | if (*p == '\\') { |
1126 | 52.3k | if ((p[1] == 'N') || ((p[1] == 'n') && |
1127 | 1.69k | (state->wrap_style == 2))) { |
1128 | 1.48k | p += 2; |
1129 | 1.48k | *str = p; |
1130 | 1.48k | return '\n'; |
1131 | 50.8k | } else if (p[1] == 'n') { |
1132 | 971 | p += 2; |
1133 | 971 | *str = p; |
1134 | 971 | return ' '; |
1135 | 49.8k | } else if (p[1] == 'h') { |
1136 | 2.11k | p += 2; |
1137 | 2.11k | *str = p; |
1138 | 2.11k | return NBSP; |
1139 | 47.7k | } else if (p[1] == '{') { |
1140 | 881 | p += 2; |
1141 | 881 | *str = p; |
1142 | 881 | return '{'; |
1143 | 46.8k | } else if (p[1] == '}') { |
1144 | 1.46k | p += 2; |
1145 | 1.46k | *str = p; |
1146 | 1.46k | return '}'; |
1147 | 1.46k | } |
1148 | 52.3k | } |
1149 | 718k | chr = ass_utf8_get_char((char **) &p); |
1150 | 718k | *str = p; |
1151 | 718k | return chr; |
1152 | 725k | } |
1153 | | |
1154 | | // Return 1 if the event contains tags that will apply overrides the selective |
1155 | | // style override code should not touch. Return 0 otherwise. |
1156 | | int ass_event_has_hard_overrides(char *str) |
1157 | 368k | { |
1158 | | // look for \pos and \move tags inside {...} |
1159 | | // mirrors ass_get_next_char, but is faster and doesn't change any global state |
1160 | 1.08M | while (*str) { |
1161 | 742k | if (str[0] == '\\' && str[1] != '\0') { |
1162 | 14.6k | str += 2; |
1163 | 728k | } else if (str[0] == '{') { |
1164 | 201k | str++; |
1165 | 4.74M | while (*str && *str != '}') { |
1166 | 4.56M | if (*str == '\\') { |
1167 | 561k | char *p = str + 1; |
1168 | 561k | if (mystrcmp(&p, "pos") || mystrcmp(&p, "move") || |
1169 | 548k | mystrcmp(&p, "clip") || mystrcmp(&p, "iclip") || |
1170 | 541k | mystrcmp(&p, "org") || mystrcmp(&p, "pbo") || |
1171 | 537k | mystrcmp(&p, "p")) |
1172 | 29.5k | return 1; |
1173 | 561k | } |
1174 | 4.53M | str++; |
1175 | 4.53M | } |
1176 | 526k | } else { |
1177 | 526k | str++; |
1178 | 526k | } |
1179 | 742k | } |
1180 | 338k | return 0; |
1181 | 368k | } |