30200
|
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
|
|
21 #include <stdio.h>
|
|
22 #include <stdlib.h>
|
|
23 #include <string.h>
|
|
24 #include <math.h>
|
|
25
|
|
26 #include "ass_render.h"
|
|
27 #include "ass_parse.h"
|
|
28
|
|
29 #define MAX_BE 127
|
|
30 #define NBSP 0xa0 // unicode non-breaking space character
|
|
31
|
|
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; }
|
|
35
|
|
36 /**
|
|
37 * \brief Check if starting part of (*p) matches sample.
|
|
38 * If true, shift p to the first symbol after the matching part.
|
|
39 */
|
|
40 static inline int mystrcmp(char **p, const char *sample)
|
|
41 {
|
|
42 int len = strlen(sample);
|
|
43 if (strncmp(*p, sample, len) == 0) {
|
|
44 (*p) += len;
|
|
45 return 1;
|
|
46 } else
|
|
47 return 0;
|
|
48 }
|
|
49
|
34295
|
50 double ensure_font_size(ASS_Renderer *priv, double size)
|
30200
|
51 {
|
|
52 if (size < 1)
|
|
53 size = 1;
|
34295
|
54 else if (size > priv->height * 2)
|
|
55 size = priv->height * 2;
|
30200
|
56
|
34295
|
57 return size;
|
|
58 }
|
30200
|
59
|
34295
|
60 static void change_font_size(ASS_Renderer *render_priv, double sz)
|
|
61 {
|
30200
|
62 render_priv->state.font_size = sz;
|
|
63 }
|
|
64
|
|
65 /**
|
|
66 * \brief Change current font, using setting from render_priv->state.
|
|
67 */
|
|
68 void update_font(ASS_Renderer *render_priv)
|
|
69 {
|
|
70 unsigned val;
|
|
71 ASS_FontDesc desc;
|
31853
|
72 desc.treat_family_as_pattern = render_priv->state.treat_family_as_pattern;
|
|
73
|
|
74 if (render_priv->state.family[0] == '@') {
|
|
75 desc.vertical = 1;
|
|
76 desc.family = strdup(render_priv->state.family + 1);
|
|
77 } else {
|
|
78 desc.vertical = 0;
|
|
79 desc.family = strdup(render_priv->state.family);
|
|
80 }
|
30200
|
81
|
|
82 val = render_priv->state.bold;
|
|
83 // 0 = normal, 1 = bold, >1 = exact weight
|
|
84 if (val == 1 || val == -1)
|
|
85 val = 200; // bold
|
|
86 else if (val <= 0)
|
|
87 val = 80; // normal
|
|
88 desc.bold = val;
|
|
89
|
|
90 val = render_priv->state.italic;
|
|
91 if (val == 1 || val == -1)
|
|
92 val = 110; // italic
|
|
93 else if (val <= 0)
|
|
94 val = 0; // normal
|
|
95 desc.italic = val;
|
|
96
|
|
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,
|
|
100 &desc);
|
|
101 free(desc.family);
|
|
102
|
|
103 if (render_priv->state.font)
|
|
104 change_font_size(render_priv, render_priv->state.font_size);
|
|
105 }
|
|
106
|
|
107 /**
|
|
108 * \brief Change border width
|
|
109 * negative value resets border to style value
|
|
110 */
|
|
111 void change_border(ASS_Renderer *render_priv, double border_x,
|
|
112 double border_y)
|
|
113 {
|
|
114 int bord;
|
|
115 if (!render_priv->state.font)
|
|
116 return;
|
|
117
|
|
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;
|
|
122 else
|
|
123 border_x = border_y = 1.;
|
|
124 }
|
|
125
|
|
126 render_priv->state.border_x = border_x;
|
|
127 render_priv->state.border_y = border_y;
|
|
128
|
|
129 bord = 64 * border_x * render_priv->border_scale;
|
|
130 if (bord > 0 && border_x == border_y) {
|
|
131 if (!render_priv->state.stroker) {
|
|
132 int error;
|
|
133 error =
|
|
134 FT_Stroker_New(render_priv->ftlibrary,
|
|
135 &render_priv->state.stroker);
|
|
136 if (error) {
|
|
137 ass_msg(render_priv->library, MSGL_V,
|
|
138 "failed to get stroker");
|
|
139 render_priv->state.stroker = 0;
|
|
140 }
|
|
141 }
|
|
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);
|
|
146 } else {
|
|
147 FT_Stroker_Done(render_priv->state.stroker);
|
|
148 render_priv->state.stroker = 0;
|
|
149 }
|
|
150 }
|
|
151
|
|
152 /**
|
|
153 * \brief Calculate a weighted average of two colors
|
|
154 * calculates c1*(1-a) + c2*a, but separately for each component except alpha
|
|
155 */
|
|
156 static void change_color(uint32_t *var, uint32_t new, double pwr)
|
|
157 {
|
|
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);
|
|
161 }
|
|
162
|
|
163 // like change_color, but for alpha component only
|
|
164 inline void change_alpha(uint32_t *var, uint32_t new, double pwr)
|
|
165 {
|
|
166 *var =
|
|
167 (_r(*var) << 24) + (_g(*var) << 16) + (_b(*var) << 8) +
|
|
168 (uint32_t) (_a(*var) * (1 - pwr) + _a(new) * pwr);
|
|
169 }
|
|
170
|
|
171 /**
|
|
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.
|
|
177 */
|
|
178 inline uint32_t mult_alpha(uint32_t a, uint32_t b)
|
|
179 {
|
|
180 return 0xFF - (0xFF - a) * (0xFF - b) / 0xFF;
|
|
181 }
|
|
182
|
|
183 /**
|
|
184 * \brief Calculate alpha value by piecewise linear function
|
|
185 * Used for \fad, \fade implementation.
|
|
186 */
|
|
187 static unsigned
|
|
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)
|
|
190 {
|
|
191 unsigned a;
|
|
192 double cf;
|
34295
|
193
|
|
194 if (now < t1) {
|
30200
|
195 a = a1;
|
|
196 } else if (now >= t4) {
|
|
197 a = a3;
|
34295
|
198 } else if (now < t2 && t2 > t1) {
|
30200
|
199 cf = ((double) (now - t1)) / (t2 - t1);
|
|
200 a = a1 * (1 - cf) + a2 * cf;
|
34295
|
201 } else if (now >= t3 && t4 > t3) {
|
30200
|
202 cf = ((double) (now - t3)) / (t4 - t3);
|
|
203 a = a2 * (1 - cf) + a3 * cf;
|
34295
|
204 } else { // t2 <= now < t3
|
30200
|
205 a = a2;
|
|
206 }
|
|
207
|
|
208 return a;
|
|
209 }
|
|
210
|
|
211 /**
|
|
212 * Parse a vector clip into an outline, using the proper scaling
|
|
213 * parameters. Translate it to correct for screen borders, if needed.
|
|
214 */
|
|
215 static char *parse_vector_clip(ASS_Renderer *render_priv, char *p)
|
|
216 {
|
|
217 int scale = 1;
|
|
218 int res = 0;
|
31875
|
219 ASS_Drawing *drawing = render_priv->state.clip_drawing;
|
30200
|
220
|
31875
|
221 ass_drawing_free(drawing);
|
34295
|
222 render_priv->state.clip_drawing =
|
|
223 ass_drawing_new(render_priv->library, render_priv->ftlibrary);
|
30200
|
224 drawing = render_priv->state.clip_drawing;
|
|
225 skipopt('(');
|
|
226 res = mystrtoi(&p, &scale);
|
|
227 skipopt(',')
|
|
228 if (!res)
|
|
229 scale = 1;
|
|
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++);
|
|
235 skipopt(')');
|
|
236
|
|
237 return p;
|
|
238 }
|
|
239
|
|
240 /**
|
|
241 * \brief Parse style override tag.
|
|
242 * \param p string to parse
|
|
243 * \param pwr multiplier for some tag effects (comes from \t tags)
|
|
244 */
|
|
245 static char *parse_tag(ASS_Renderer *render_priv, char *p, double pwr)
|
|
246 {
|
|
247 skip_to('\\');
|
|
248 skip('\\');
|
|
249 if ((*p == '}') || (*p == 0))
|
|
250 return p;
|
|
251
|
|
252 // New tags introduced in vsfilter 2.39
|
|
253 if (mystrcmp(&p, "xbord")) {
|
|
254 double val;
|
|
255 if (mystrtod(&p, &val))
|
|
256 val = render_priv->state.border_x * (1 - pwr) + val * pwr;
|
|
257 else
|
|
258 val = -1.;
|
|
259 change_border(render_priv, val, render_priv->state.border_y);
|
34295
|
260 render_priv->state.bm_run_id++;
|
30200
|
261 } else if (mystrcmp(&p, "ybord")) {
|
|
262 double val;
|
|
263 if (mystrtod(&p, &val))
|
|
264 val = render_priv->state.border_y * (1 - pwr) + val * pwr;
|
|
265 else
|
|
266 val = -1.;
|
|
267 change_border(render_priv, render_priv->state.border_x, val);
|
|
268 } else if (mystrcmp(&p, "xshad")) {
|
|
269 double val;
|
|
270 if (mystrtod(&p, &val))
|
|
271 val = render_priv->state.shadow_x * (1 - pwr) + val * pwr;
|
|
272 else
|
|
273 val = 0.;
|
|
274 render_priv->state.shadow_x = val;
|
34295
|
275 render_priv->state.bm_run_id++;
|
30200
|
276 } else if (mystrcmp(&p, "yshad")) {
|
|
277 double val;
|
|
278 if (mystrtod(&p, &val))
|
|
279 val = render_priv->state.shadow_y * (1 - pwr) + val * pwr;
|
|
280 else
|
|
281 val = 0.;
|
|
282 render_priv->state.shadow_y = val;
|
34295
|
283 render_priv->state.bm_run_id++;
|
30200
|
284 } else if (mystrcmp(&p, "fax")) {
|
|
285 double val;
|
|
286 if (mystrtod(&p, &val))
|
|
287 render_priv->state.fax =
|
|
288 val * pwr + render_priv->state.fax * (1 - pwr);
|
|
289 else
|
|
290 render_priv->state.fax = 0.;
|
|
291 } else if (mystrcmp(&p, "fay")) {
|
|
292 double val;
|
|
293 if (mystrtod(&p, &val))
|
|
294 render_priv->state.fay =
|
|
295 val * pwr + render_priv->state.fay * (1 - pwr);
|
|
296 else
|
|
297 render_priv->state.fay = 0.;
|
|
298 } else if (mystrcmp(&p, "iclip")) {
|
|
299 int x0, y0, x1, y1;
|
|
300 int res = 1;
|
|
301 char *start = p;
|
|
302 skipopt('(');
|
|
303 res &= mystrtoi(&p, &x0);
|
|
304 skipopt(',');
|
|
305 res &= mystrtoi(&p, &y0);
|
|
306 skipopt(',');
|
|
307 res &= mystrtoi(&p, &x1);
|
|
308 skipopt(',');
|
|
309 res &= mystrtoi(&p, &y1);
|
|
310 skipopt(')');
|
|
311 if (res) {
|
|
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;
|
|
324 } else
|
|
325 render_priv->state.clip_mode = 0;
|
|
326 } else if (mystrcmp(&p, "blur")) {
|
|
327 double val;
|
|
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;
|
|
333 } else
|
|
334 render_priv->state.blur = 0.0;
|
34295
|
335 render_priv->state.bm_run_id++;
|
30200
|
336 // ASS standard tags
|
|
337 } else if (mystrcmp(&p, "fsc")) {
|
|
338 char tp = *p++;
|
|
339 double val;
|
|
340 if (tp == 'x') {
|
|
341 if (mystrtod(&p, &val)) {
|
|
342 val /= 100;
|
|
343 render_priv->state.scale_x =
|
|
344 render_priv->state.scale_x * (1 - pwr) + val * pwr;
|
|
345 } else
|
|
346 render_priv->state.scale_x =
|
|
347 render_priv->state.style->ScaleX;
|
|
348 } else if (tp == 'y') {
|
|
349 if (mystrtod(&p, &val)) {
|
|
350 val /= 100;
|
|
351 render_priv->state.scale_y =
|
|
352 render_priv->state.scale_y * (1 - pwr) + val * pwr;
|
|
353 } else
|
|
354 render_priv->state.scale_y =
|
|
355 render_priv->state.style->ScaleY;
|
|
356 }
|
|
357 } else if (mystrcmp(&p, "fsp")) {
|
|
358 double val;
|
|
359 if (mystrtod(&p, &val))
|
|
360 render_priv->state.hspacing =
|
|
361 render_priv->state.hspacing * (1 - pwr) + val * pwr;
|
|
362 else
|
|
363 render_priv->state.hspacing = render_priv->state.style->Spacing;
|
34011
|
364 } else if (mystrcmp(&p, "fs+")) {
|
|
365 double val;
|
|
366 if (mystrtod(&p, &val)) {
|
|
367 val = render_priv->state.font_size + pwr * val;
|
|
368 } else
|
|
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-")) {
|
|
373 double val;
|
|
374 if (mystrtod(&p, &val))
|
|
375 val = render_priv->state.font_size - pwr * val;
|
|
376 else
|
|
377 val = render_priv->state.style->FontSize;
|
|
378 if (render_priv->state.font)
|
|
379 change_font_size(render_priv, val);
|
30200
|
380 } else if (mystrcmp(&p, "fs")) {
|
|
381 double val;
|
|
382 if (mystrtod(&p, &val))
|
|
383 val = render_priv->state.font_size * (1 - pwr) + val * pwr;
|
|
384 else
|
|
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")) {
|
|
389 double val;
|
|
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;
|
|
393 } else
|
|
394 val = -1.; // reset to default
|
|
395 change_border(render_priv, val, val);
|
34295
|
396 render_priv->state.bm_run_id++;
|
30200
|
397 } else if (mystrcmp(&p, "move")) {
|
|
398 double x1, x2, y1, y2;
|
|
399 long long t1, t2, delta_t, t;
|
|
400 double x, y;
|
|
401 double k;
|
|
402 skip('(');
|
|
403 mystrtod(&p, &x1);
|
|
404 skip(',');
|
|
405 mystrtod(&p, &y1);
|
|
406 skip(',');
|
|
407 mystrtod(&p, &x2);
|
|
408 skip(',');
|
|
409 mystrtod(&p, &y2);
|
|
410 if (*p == ',') {
|
|
411 skip(',');
|
|
412 mystrtoll(&p, &t1);
|
|
413 skip(',');
|
|
414 mystrtoll(&p, &t2);
|
|
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,
|
|
418 (int64_t) t2);
|
|
419 } else {
|
|
420 t1 = 0;
|
|
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);
|
|
424 }
|
|
425 skip(')');
|
|
426 delta_t = t2 - t1;
|
|
427 t = render_priv->time - render_priv->state.event->Start;
|
|
428 if (t < t1)
|
|
429 k = 0.;
|
|
430 else if (t > t2)
|
|
431 k = 1.;
|
|
432 else
|
|
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;
|
|
441 }
|
|
442 } else if (mystrcmp(&p, "frx")) {
|
|
443 double val;
|
|
444 if (mystrtod(&p, &val)) {
|
|
445 val *= M_PI / 180;
|
|
446 render_priv->state.frx =
|
|
447 val * pwr + render_priv->state.frx * (1 - pwr);
|
|
448 } else
|
|
449 render_priv->state.frx = 0.;
|
|
450 } else if (mystrcmp(&p, "fry")) {
|
|
451 double val;
|
|
452 if (mystrtod(&p, &val)) {
|
|
453 val *= M_PI / 180;
|
|
454 render_priv->state.fry =
|
|
455 val * pwr + render_priv->state.fry * (1 - pwr);
|
|
456 } else
|
|
457 render_priv->state.fry = 0.;
|
|
458 } else if (mystrcmp(&p, "frz") || mystrcmp(&p, "fr")) {
|
|
459 double val;
|
|
460 if (mystrtod(&p, &val)) {
|
|
461 val *= M_PI / 180;
|
|
462 render_priv->state.frz =
|
|
463 val * pwr + render_priv->state.frz * (1 - pwr);
|
|
464 } else
|
|
465 render_priv->state.frz =
|
|
466 M_PI * render_priv->state.style->Angle / 180.;
|
|
467 } else if (mystrcmp(&p, "fn")) {
|
|
468 char *start = p;
|
|
469 char *family;
|
|
470 skip_to('\\');
|
|
471 if (p > start) {
|
|
472 family = malloc(p - start + 1);
|
|
473 strncpy(family, start, p - start);
|
|
474 family[p - start] = '\0';
|
|
475 } else
|
|
476 family = strdup(render_priv->state.style->FontName);
|
31875
|
477 free(render_priv->state.family);
|
30200
|
478 render_priv->state.family = family;
|
|
479 update_font(render_priv);
|
|
480 } else if (mystrcmp(&p, "alpha")) {
|
|
481 uint32_t val;
|
|
482 int i;
|
|
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);
|
|
488 } else {
|
|
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);
|
|
497 }
|
34295
|
498 render_priv->state.bm_run_id++;
|
30200
|
499 // FIXME: simplify
|
|
500 } else if (mystrcmp(&p, "an")) {
|
|
501 int val;
|
|
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);
|
|
505 if (v != 0)
|
|
506 v = 3 - v;
|
|
507 val = ((val - 1) % 3) + 1; // horizontal alignment
|
|
508 val += v * 4;
|
|
509 ass_msg(render_priv->library, MSGL_DBG2, "align %d", val);
|
34011
|
510 if ((render_priv->state.parsed_tags & PARSED_A) == 0) {
|
|
511 render_priv->state.alignment = val;
|
|
512 render_priv->state.parsed_tags |= PARSED_A;
|
|
513 }
|
30200
|
514 } else
|
|
515 render_priv->state.alignment =
|
|
516 render_priv->state.style->Alignment;
|
|
517 } else if (mystrcmp(&p, "a")) {
|
|
518 int val;
|
34011
|
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;
|
|
524 }
|
|
525 } else
|
30200
|
526 render_priv->state.alignment =
|
|
527 render_priv->state.style->Alignment;
|
|
528 } else if (mystrcmp(&p, "pos")) {
|
|
529 double v1, v2;
|
|
530 skip('(');
|
|
531 mystrtod(&p, &v1);
|
|
532 skip(',');
|
|
533 mystrtod(&p, &v2);
|
|
534 skip(')');
|
|
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");
|
|
539 } else {
|
|
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;
|
|
544 }
|
|
545 } else if (mystrcmp(&p, "fad")) {
|
|
546 int a1, a2, a3;
|
|
547 long long t1, t2, t3, t4;
|
|
548 if (*p == 'e')
|
|
549 ++p; // either \fad or \fade
|
|
550 skip('(');
|
|
551 mystrtoi(&p, &a1);
|
|
552 skip(',');
|
|
553 mystrtoi(&p, &a2);
|
|
554 if (*p == ')') {
|
|
555 // 2-argument version (\fad, according to specs)
|
|
556 // a1 and a2 are fade-in and fade-out durations
|
|
557 t1 = 0;
|
|
558 t4 = render_priv->state.event->Duration;
|
|
559 t2 = a1;
|
|
560 t3 = t4 - a2;
|
|
561 a1 = 0xFF;
|
|
562 a2 = 0;
|
|
563 a3 = 0xFF;
|
|
564 } else {
|
|
565 // 6-argument version (\fade)
|
|
566 // a1 and a2 (and a3) are opacity values
|
|
567 skip(',');
|
|
568 mystrtoi(&p, &a3);
|
|
569 skip(',');
|
|
570 mystrtoll(&p, &t1);
|
|
571 skip(',');
|
|
572 mystrtoll(&p, &t2);
|
|
573 skip(',');
|
|
574 mystrtoll(&p, &t3);
|
|
575 skip(',');
|
|
576 mystrtoll(&p, &t4);
|
|
577 }
|
|
578 skip(')');
|
34011
|
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,
|
|
583 t3, t4, a1, a2, a3);
|
|
584 render_priv->state.parsed_tags |= PARSED_FADE;
|
|
585 }
|
30200
|
586 } else if (mystrcmp(&p, "org")) {
|
|
587 int v1, v2;
|
|
588 skip('(');
|
|
589 mystrtoi(&p, &v1);
|
|
590 skip(',');
|
|
591 mystrtoi(&p, &v2);
|
|
592 skip(')');
|
|
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;
|
|
599 }
|
|
600 } else if (mystrcmp(&p, "t")) {
|
|
601 double v[3];
|
|
602 int v1, v2;
|
|
603 double v3;
|
|
604 int cnt;
|
|
605 long long t1, t2, t, delta_t;
|
|
606 double k;
|
|
607 skip('(');
|
|
608 for (cnt = 0; cnt < 3; ++cnt) {
|
|
609 if (*p == '\\')
|
|
610 break;
|
31853
|
611 mystrtod(&p, &v[cnt]);
|
30200
|
612 skip(',');
|
|
613 }
|
|
614 if (cnt == 3) {
|
|
615 v1 = v[0];
|
|
616 v2 = (v[1] < v1) ? render_priv->state.event->Duration : v[1];
|
|
617 v3 = v[2];
|
|
618 } else if (cnt == 2) {
|
|
619 v1 = v[0];
|
|
620 v2 = (v[1] < v1) ? render_priv->state.event->Duration : v[1];
|
|
621 v3 = 1.;
|
|
622 } else if (cnt == 1) {
|
|
623 v1 = 0;
|
|
624 v2 = render_priv->state.event->Duration;
|
|
625 v3 = v[0];
|
|
626 } else { // cnt == 0
|
|
627 v1 = 0;
|
|
628 v2 = render_priv->state.event->Duration;
|
|
629 v3 = 1.;
|
|
630 }
|
|
631 render_priv->state.detect_collisions = 0;
|
|
632 t1 = v1;
|
|
633 t2 = v2;
|
|
634 delta_t = v2 - v1;
|
|
635 if (v3 < 0.)
|
|
636 v3 = 0.;
|
|
637 t = render_priv->time - render_priv->state.event->Start; // FIXME: move to render_context
|
|
638 if (t <= t1)
|
|
639 k = 0.;
|
|
640 else if (t >= t2)
|
|
641 k = 1.;
|
|
642 else {
|
|
643 assert(delta_t != 0.);
|
|
644 k = pow(((double) (t - t1)) / delta_t, v3);
|
|
645 }
|
|
646 while (*p == '\\')
|
|
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
|
|
649 skip(')');
|
|
650 } else if (mystrcmp(&p, "clip")) {
|
|
651 char *start = p;
|
|
652 int x0, y0, x1, y1;
|
|
653 int res = 1;
|
|
654 skipopt('(');
|
|
655 res &= mystrtoi(&p, &x0);
|
|
656 skipopt(',');
|
|
657 res &= mystrtoi(&p, &y0);
|
|
658 skipopt(',');
|
|
659 res &= mystrtoi(&p, &x1);
|
|
660 skipopt(',');
|
|
661 res &= mystrtoi(&p, &y1);
|
|
662 skipopt(')');
|
|
663 if (res) {
|
|
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;
|
|
676 } else {
|
|
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;
|
|
681 }
|
|
682 } else if (mystrcmp(&p, "c")) {
|
|
683 uint32_t val;
|
|
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);
|
34295
|
689 render_priv->state.bm_run_id++;
|
30200
|
690 } else if ((*p >= '1') && (*p <= '4') && (++p)
|
|
691 && (mystrcmp(&p, "c") || mystrcmp(&p, "a"))) {
|
|
692 char n = *(p - 2);
|
|
693 int cidx = n - '1';
|
|
694 char cmd = *(p - 1);
|
|
695 uint32_t val;
|
|
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))
|
|
699 switch (n) {
|
|
700 case '1':
|
|
701 val = render_priv->state.style->PrimaryColour;
|
|
702 break;
|
|
703 case '2':
|
|
704 val = render_priv->state.style->SecondaryColour;
|
|
705 break;
|
|
706 case '3':
|
|
707 val = render_priv->state.style->OutlineColour;
|
|
708 break;
|
|
709 case '4':
|
|
710 val = render_priv->state.style->BackColour;
|
|
711 break;
|
|
712 default:
|
|
713 val = 0;
|
|
714 break; // impossible due to assert; avoid compilation warning
|
|
715 }
|
|
716 switch (cmd) {
|
|
717 case 'c':
|
|
718 change_color(render_priv->state.c + cidx, val, pwr);
|
34295
|
719 render_priv->state.bm_run_id++;
|
30200
|
720 break;
|
|
721 case 'a':
|
|
722 change_alpha(render_priv->state.c + cidx, val >> 24, pwr);
|
34295
|
723 render_priv->state.bm_run_id++;
|
30200
|
724 break;
|
|
725 default:
|
|
726 ass_msg(render_priv->library, MSGL_WARN, "Bad command: %c%c",
|
|
727 n, cmd);
|
|
728 break;
|
|
729 }
|
|
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")) {
|
|
735 int val;
|
|
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;
|
|
741 } else
|
|
742 render_priv->state.be = 0;
|
34295
|
743 render_priv->state.bm_run_id++;
|
30200
|
744 } else if (mystrcmp(&p, "b")) {
|
|
745 int b;
|
|
746 if (mystrtoi(&p, &b)) {
|
|
747 if (pwr >= .5)
|
|
748 render_priv->state.bold = b;
|
|
749 } else
|
|
750 render_priv->state.bold = render_priv->state.style->Bold;
|
|
751 update_font(render_priv);
|
|
752 } else if (mystrcmp(&p, "i")) {
|
|
753 int i;
|
|
754 if (mystrtoi(&p, &i)) {
|
|
755 if (pwr >= .5)
|
|
756 render_priv->state.italic = i;
|
|
757 } else
|
|
758 render_priv->state.italic = render_priv->state.style->Italic;
|
|
759 update_font(render_priv);
|
|
760 } else if (mystrcmp(&p, "kf") || mystrcmp(&p, "K")) {
|
|
761 int val = 0;
|
|
762 mystrtoi(&p, &val);
|
|
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")) {
|
|
769 int val = 0;
|
|
770 mystrtoi(&p, &val);
|
|
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")) {
|
|
777 int val = 0;
|
|
778 mystrtoi(&p, &val);
|
|
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")) {
|
|
785 double val;
|
|
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;
|
|
789 } else
|
|
790 val = 0.;
|
|
791 render_priv->state.shadow_x = render_priv->state.shadow_y = val;
|
34295
|
792 render_priv->state.bm_run_id++;
|
30200
|
793 } else if (mystrcmp(&p, "s")) {
|
|
794 int val;
|
|
795 if (mystrtoi(&p, &val) && val)
|
|
796 render_priv->state.flags |= DECO_STRIKETHROUGH;
|
|
797 else
|
|
798 render_priv->state.flags &= ~DECO_STRIKETHROUGH;
|
34295
|
799 render_priv->state.bm_run_id++;
|
30200
|
800 } else if (mystrcmp(&p, "u")) {
|
|
801 int val;
|
|
802 if (mystrtoi(&p, &val) && val)
|
|
803 render_priv->state.flags |= DECO_UNDERLINE;
|
|
804 else
|
|
805 render_priv->state.flags &= ~DECO_UNDERLINE;
|
34295
|
806 render_priv->state.bm_run_id++;
|
30200
|
807 } else if (mystrcmp(&p, "pbo")) {
|
|
808 double val = 0;
|
|
809 if (mystrtod(&p, &val))
|
|
810 render_priv->state.drawing->pbo = val;
|
|
811 } else if (mystrcmp(&p, "p")) {
|
|
812 int val;
|
|
813 if (!mystrtoi(&p, &val))
|
|
814 val = 0;
|
|
815 if (val)
|
|
816 render_priv->state.drawing->scale = val;
|
|
817 render_priv->state.drawing_mode = !!val;
|
|
818 } else if (mystrcmp(&p, "q")) {
|
|
819 int val;
|
|
820 if (!mystrtoi(&p, &val))
|
|
821 val = render_priv->track->WrapStyle;
|
|
822 render_priv->state.wrap_style = val;
|
34295
|
823 } else if (mystrcmp(&p, "fe")) {
|
|
824 int val;
|
|
825 if (!mystrtoi(&p, &val))
|
|
826 val = render_priv->state.style->Encoding;
|
|
827 render_priv->state.font_encoding = val;
|
30200
|
828 }
|
|
829
|
|
830 return p;
|
|
831 }
|
|
832
|
|
833 void apply_transition_effects(ASS_Renderer *render_priv, ASS_Event *event)
|
|
834 {
|
|
835 int v[4];
|
|
836 int cnt;
|
|
837 char *p = event->Effect;
|
|
838
|
|
839 if (!p || !*p)
|
|
840 return;
|
|
841
|
|
842 cnt = 0;
|
|
843 while (cnt < 4 && (p = strchr(p, ';'))) {
|
|
844 v[cnt++] = atoi(++p);
|
|
845 }
|
|
846
|
|
847 if (strncmp(event->Effect, "Banner;", 7) == 0) {
|
|
848 int delay;
|
|
849 if (cnt < 1) {
|
|
850 ass_msg(render_priv->library, MSGL_V,
|
|
851 "Error parsing effect: '%s'", event->Effect);
|
|
852 return;
|
|
853 }
|
|
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;
|
|
858
|
|
859 delay = v[0];
|
|
860 if (delay == 0)
|
|
861 delay = 1; // ?
|
|
862 render_priv->state.scroll_shift =
|
|
863 (render_priv->time - render_priv->state.event->Start) / delay;
|
|
864 render_priv->state.evt_type = EVENT_HSCROLL;
|
|
865 return;
|
|
866 }
|
|
867
|
|
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;
|
|
872 } else {
|
31853
|
873 ass_msg(render_priv->library, MSGL_DBG2,
|
30200
|
874 "Unknown transition effect: '%s'", event->Effect);
|
|
875 return;
|
|
876 }
|
|
877 // parse scroll up/down parameters
|
|
878 {
|
|
879 int delay;
|
|
880 int y0, y1;
|
|
881 if (cnt < 3) {
|
|
882 ass_msg(render_priv->library, MSGL_V,
|
|
883 "Error parsing effect: '%s'", event->Effect);
|
|
884 return;
|
|
885 }
|
|
886 delay = v[2];
|
|
887 if (delay == 0)
|
|
888 delay = 1; // ?
|
|
889 render_priv->state.scroll_shift =
|
|
890 (render_priv->time - render_priv->state.event->Start) / delay;
|
|
891 if (v[0] < v[1]) {
|
|
892 y0 = v[0];
|
|
893 y1 = v[1];
|
|
894 } else {
|
|
895 y0 = v[1];
|
|
896 y1 = v[0];
|
|
897 }
|
|
898 if (y1 == 0)
|
|
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;
|
|
904 }
|
|
905
|
|
906 }
|
|
907
|
|
908 /**
|
34295
|
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.
|
|
914 * This function:
|
|
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).
|
|
918 */
|
|
919 void process_karaoke_effects(ASS_Renderer *render_priv)
|
|
920 {
|
|
921 GlyphInfo *cur, *cur2;
|
|
922 GlyphInfo *s1, *e1; // start and end of the current word
|
|
923 GlyphInfo *s2; // start of the next word
|
|
924 int i;
|
|
925 int timing; // current timing
|
|
926 int tm_start, tm_end; // timings at start and end of the current word
|
|
927 int tm_current;
|
|
928 double dt;
|
|
929 int x;
|
|
930 int x_start, x_end;
|
|
931
|
|
932 tm_current = render_priv->time - render_priv->state.event->Start;
|
|
933 timing = 0;
|
|
934 s1 = s2 = 0;
|
|
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)) {
|
|
939 s1 = s2;
|
|
940 s2 = cur;
|
|
941 if (s1) {
|
|
942 e1 = s2 - 1;
|
|
943 tm_start = timing + s1->effect_skip_timing;
|
|
944 tm_end = tm_start + s1->effect_timing;
|
|
945 timing = tm_end;
|
|
946 x_start = 1000000;
|
|
947 x_end = -1000000;
|
|
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));
|
|
951 }
|
|
952
|
|
953 dt = (tm_current - tm_start);
|
|
954 if ((s1->effect_type == EF_KARAOKE)
|
|
955 || (s1->effect_type == EF_KARAOKE_KO)) {
|
|
956 if (dt > 0)
|
|
957 x = x_end + 1;
|
|
958 else
|
|
959 x = x_start;
|
|
960 } else if (s1->effect_type == EF_KARAOKE_KF) {
|
|
961 dt /= (tm_end - tm_start);
|
|
962 x = x_start + (x_end - x_start) * dt;
|
|
963 } else {
|
|
964 ass_msg(render_priv->library, MSGL_ERR,
|
|
965 "Unknown effect type");
|
|
966 continue;
|
|
967 }
|
|
968
|
|
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);
|
|
972 }
|
|
973 }
|
|
974 }
|
|
975 }
|
|
976 }
|
|
977
|
|
978
|
|
979 /**
|
30200
|
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
|
|
984 */
|
|
985 unsigned get_next_char(ASS_Renderer *render_priv, char **str)
|
|
986 {
|
|
987 char *p = *str;
|
|
988 unsigned chr;
|
|
989 if (*p == '{') { // '\0' goes here
|
|
990 p++;
|
|
991 while (1) {
|
|
992 p = parse_tag(render_priv, p, 1.);
|
|
993 if (*p == '}') { // end of tag
|
|
994 p++;
|
|
995 if (*p == '{') {
|
|
996 p++;
|
|
997 continue;
|
|
998 } else
|
|
999 break;
|
|
1000 } else if (*p != '\\')
|
|
1001 ass_msg(render_priv->library, MSGL_V,
|
31853
|
1002 "Unable to parse: '%.30s'", p);
|
30200
|
1003 if (*p == 0)
|
|
1004 break;
|
|
1005 }
|
|
1006 }
|
|
1007 if (*p == '\t') {
|
|
1008 ++p;
|
|
1009 *str = p;
|
|
1010 return ' ';
|
|
1011 }
|
|
1012 if (*p == '\\') {
|
|
1013 if ((p[1] == 'N') || ((p[1] == 'n') &&
|
|
1014 (render_priv->state.wrap_style == 2))) {
|
|
1015 p += 2;
|
|
1016 *str = p;
|
|
1017 return '\n';
|
|
1018 } else if (p[1] == 'n') {
|
|
1019 p += 2;
|
|
1020 *str = p;
|
|
1021 return ' ';
|
|
1022 } else if (p[1] == 'h') {
|
|
1023 p += 2;
|
|
1024 *str = p;
|
|
1025 return NBSP;
|
31853
|
1026 } else if (p[1] == '{') {
|
|
1027 p += 2;
|
|
1028 *str = p;
|
|
1029 return '{';
|
|
1030 } else if (p[1] == '}') {
|
|
1031 p += 2;
|
|
1032 *str = p;
|
|
1033 return '}';
|
30200
|
1034 }
|
|
1035 }
|
|
1036 chr = ass_utf8_get_char((char **) &p);
|
|
1037 *str = p;
|
|
1038 return chr;
|
|
1039 }
|