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 /**
|
35262
|
108 * \brief Calculate valid border size. Makes sure the border sizes make sense.
|
|
109 *
|
|
110 * \param priv renderer state object
|
|
111 * \param border_x requested x border size
|
|
112 * \param border_y requested y border size
|
30200
|
113 */
|
35262
|
114 void calc_border(ASS_Renderer *priv, double border_x, double border_y)
|
30200
|
115 {
|
|
116 if (border_x < 0 && border_y < 0) {
|
35262
|
117 if (priv->state.border_style == 1 ||
|
|
118 priv->state.border_style == 3)
|
|
119 border_x = border_y = priv->state.style->Outline;
|
30200
|
120 else
|
|
121 border_x = border_y = 1.;
|
|
122 }
|
|
123
|
35262
|
124 priv->state.border_x = border_x;
|
|
125 priv->state.border_y = border_y;
|
|
126 }
|
30200
|
127
|
35262
|
128 /**
|
|
129 * \brief Change border width
|
|
130 *
|
|
131 * \param render_priv renderer state object
|
|
132 * \param info glyph state object
|
|
133 */
|
|
134 void change_border(ASS_Renderer *render_priv, double border_x, double border_y)
|
|
135 {
|
|
136 int bord = 64 * border_x * render_priv->border_scale;
|
|
137
|
30200
|
138 if (bord > 0 && border_x == border_y) {
|
|
139 if (!render_priv->state.stroker) {
|
|
140 int error;
|
|
141 error =
|
|
142 FT_Stroker_New(render_priv->ftlibrary,
|
|
143 &render_priv->state.stroker);
|
|
144 if (error) {
|
|
145 ass_msg(render_priv->library, MSGL_V,
|
|
146 "failed to get stroker");
|
|
147 render_priv->state.stroker = 0;
|
|
148 }
|
35262
|
149 render_priv->state.stroker_radius = -1.0;
|
30200
|
150 }
|
35262
|
151 if (render_priv->state.stroker && render_priv->state.stroker_radius != bord) {
|
30200
|
152 FT_Stroker_Set(render_priv->state.stroker, bord,
|
|
153 FT_STROKER_LINECAP_ROUND,
|
|
154 FT_STROKER_LINEJOIN_ROUND, 0);
|
35262
|
155 render_priv->state.stroker_radius = bord;
|
|
156 }
|
30200
|
157 } else {
|
|
158 FT_Stroker_Done(render_priv->state.stroker);
|
|
159 render_priv->state.stroker = 0;
|
|
160 }
|
|
161 }
|
|
162
|
|
163 /**
|
|
164 * \brief Calculate a weighted average of two colors
|
|
165 * calculates c1*(1-a) + c2*a, but separately for each component except alpha
|
|
166 */
|
|
167 static void change_color(uint32_t *var, uint32_t new, double pwr)
|
|
168 {
|
|
169 (*var) = ((uint32_t) (_r(*var) * (1 - pwr) + _r(new) * pwr) << 24) +
|
|
170 ((uint32_t) (_g(*var) * (1 - pwr) + _g(new) * pwr) << 16) +
|
|
171 ((uint32_t) (_b(*var) * (1 - pwr) + _b(new) * pwr) << 8) + _a(*var);
|
|
172 }
|
|
173
|
|
174 // like change_color, but for alpha component only
|
|
175 inline void change_alpha(uint32_t *var, uint32_t new, double pwr)
|
|
176 {
|
|
177 *var =
|
|
178 (_r(*var) << 24) + (_g(*var) << 16) + (_b(*var) << 8) +
|
|
179 (uint32_t) (_a(*var) * (1 - pwr) + _a(new) * pwr);
|
|
180 }
|
|
181
|
|
182 /**
|
|
183 * \brief Multiply two alpha values
|
|
184 * \param a first value
|
|
185 * \param b second value
|
|
186 * \return result of multiplication
|
|
187 * Parameters and result are limited by 0xFF.
|
|
188 */
|
|
189 inline uint32_t mult_alpha(uint32_t a, uint32_t b)
|
|
190 {
|
|
191 return 0xFF - (0xFF - a) * (0xFF - b) / 0xFF;
|
|
192 }
|
|
193
|
|
194 /**
|
|
195 * \brief Calculate alpha value by piecewise linear function
|
|
196 * Used for \fad, \fade implementation.
|
|
197 */
|
|
198 static unsigned
|
|
199 interpolate_alpha(long long now, long long t1, long long t2, long long t3,
|
|
200 long long t4, unsigned a1, unsigned a2, unsigned a3)
|
|
201 {
|
|
202 unsigned a;
|
|
203 double cf;
|
34295
|
204
|
|
205 if (now < t1) {
|
30200
|
206 a = a1;
|
|
207 } else if (now >= t4) {
|
|
208 a = a3;
|
34295
|
209 } else if (now < t2 && t2 > t1) {
|
30200
|
210 cf = ((double) (now - t1)) / (t2 - t1);
|
|
211 a = a1 * (1 - cf) + a2 * cf;
|
34295
|
212 } else if (now >= t3 && t4 > t3) {
|
30200
|
213 cf = ((double) (now - t3)) / (t4 - t3);
|
|
214 a = a2 * (1 - cf) + a3 * cf;
|
34295
|
215 } else { // t2 <= now < t3
|
30200
|
216 a = a2;
|
|
217 }
|
|
218
|
|
219 return a;
|
|
220 }
|
|
221
|
|
222 /**
|
|
223 * Parse a vector clip into an outline, using the proper scaling
|
|
224 * parameters. Translate it to correct for screen borders, if needed.
|
|
225 */
|
|
226 static char *parse_vector_clip(ASS_Renderer *render_priv, char *p)
|
|
227 {
|
|
228 int scale = 1;
|
|
229 int res = 0;
|
31875
|
230 ASS_Drawing *drawing = render_priv->state.clip_drawing;
|
30200
|
231
|
31875
|
232 ass_drawing_free(drawing);
|
34295
|
233 render_priv->state.clip_drawing =
|
|
234 ass_drawing_new(render_priv->library, render_priv->ftlibrary);
|
30200
|
235 drawing = render_priv->state.clip_drawing;
|
|
236 skipopt('(');
|
|
237 res = mystrtoi(&p, &scale);
|
|
238 skipopt(',')
|
|
239 if (!res)
|
|
240 scale = 1;
|
|
241 drawing->scale = scale;
|
|
242 drawing->scale_x = render_priv->font_scale_x * render_priv->font_scale;
|
|
243 drawing->scale_y = render_priv->font_scale;
|
35701
|
244 while (*p != ')' && *p != '}' && *p != 0)
|
30200
|
245 ass_drawing_add_char(drawing, *p++);
|
|
246 skipopt(')');
|
|
247
|
|
248 return p;
|
|
249 }
|
|
250
|
|
251 /**
|
|
252 * \brief Parse style override tag.
|
|
253 * \param p string to parse
|
|
254 * \param pwr multiplier for some tag effects (comes from \t tags)
|
|
255 */
|
35262
|
256 char *parse_tag(ASS_Renderer *render_priv, char *p, double pwr)
|
30200
|
257 {
|
|
258 skip_to('\\');
|
|
259 skip('\\');
|
|
260 if ((*p == '}') || (*p == 0))
|
|
261 return p;
|
|
262
|
|
263 // New tags introduced in vsfilter 2.39
|
|
264 if (mystrcmp(&p, "xbord")) {
|
|
265 double val;
|
|
266 if (mystrtod(&p, &val))
|
|
267 val = render_priv->state.border_x * (1 - pwr) + val * pwr;
|
|
268 else
|
|
269 val = -1.;
|
35262
|
270 calc_border(render_priv, val, render_priv->state.border_y);
|
34295
|
271 render_priv->state.bm_run_id++;
|
30200
|
272 } else if (mystrcmp(&p, "ybord")) {
|
|
273 double val;
|
|
274 if (mystrtod(&p, &val))
|
|
275 val = render_priv->state.border_y * (1 - pwr) + val * pwr;
|
|
276 else
|
|
277 val = -1.;
|
35262
|
278 calc_border(render_priv, render_priv->state.border_x, val);
|
|
279 render_priv->state.bm_run_id++;
|
30200
|
280 } else if (mystrcmp(&p, "xshad")) {
|
|
281 double val;
|
|
282 if (mystrtod(&p, &val))
|
|
283 val = render_priv->state.shadow_x * (1 - pwr) + val * pwr;
|
|
284 else
|
|
285 val = 0.;
|
|
286 render_priv->state.shadow_x = val;
|
34295
|
287 render_priv->state.bm_run_id++;
|
30200
|
288 } else if (mystrcmp(&p, "yshad")) {
|
|
289 double val;
|
|
290 if (mystrtod(&p, &val))
|
|
291 val = render_priv->state.shadow_y * (1 - pwr) + val * pwr;
|
|
292 else
|
|
293 val = 0.;
|
|
294 render_priv->state.shadow_y = val;
|
34295
|
295 render_priv->state.bm_run_id++;
|
30200
|
296 } else if (mystrcmp(&p, "fax")) {
|
|
297 double val;
|
|
298 if (mystrtod(&p, &val))
|
|
299 render_priv->state.fax =
|
|
300 val * pwr + render_priv->state.fax * (1 - pwr);
|
|
301 else
|
|
302 render_priv->state.fax = 0.;
|
|
303 } else if (mystrcmp(&p, "fay")) {
|
|
304 double val;
|
|
305 if (mystrtod(&p, &val))
|
|
306 render_priv->state.fay =
|
|
307 val * pwr + render_priv->state.fay * (1 - pwr);
|
|
308 else
|
|
309 render_priv->state.fay = 0.;
|
|
310 } else if (mystrcmp(&p, "iclip")) {
|
|
311 int x0, y0, x1, y1;
|
|
312 int res = 1;
|
|
313 char *start = p;
|
|
314 skipopt('(');
|
|
315 res &= mystrtoi(&p, &x0);
|
|
316 skipopt(',');
|
|
317 res &= mystrtoi(&p, &y0);
|
|
318 skipopt(',');
|
|
319 res &= mystrtoi(&p, &x1);
|
|
320 skipopt(',');
|
|
321 res &= mystrtoi(&p, &y1);
|
|
322 skipopt(')');
|
|
323 if (res) {
|
|
324 render_priv->state.clip_x0 =
|
|
325 render_priv->state.clip_x0 * (1 - pwr) + x0 * pwr;
|
|
326 render_priv->state.clip_x1 =
|
|
327 render_priv->state.clip_x1 * (1 - pwr) + x1 * pwr;
|
|
328 render_priv->state.clip_y0 =
|
|
329 render_priv->state.clip_y0 * (1 - pwr) + y0 * pwr;
|
|
330 render_priv->state.clip_y1 =
|
|
331 render_priv->state.clip_y1 * (1 - pwr) + y1 * pwr;
|
|
332 render_priv->state.clip_mode = 1;
|
|
333 } else if (!render_priv->state.clip_drawing) {
|
|
334 p = parse_vector_clip(render_priv, start);
|
|
335 render_priv->state.clip_drawing_mode = 1;
|
|
336 } else
|
|
337 render_priv->state.clip_mode = 0;
|
|
338 } else if (mystrcmp(&p, "blur")) {
|
|
339 double val;
|
|
340 if (mystrtod(&p, &val)) {
|
|
341 val = render_priv->state.blur * (1 - pwr) + val * pwr;
|
|
342 val = (val < 0) ? 0 : val;
|
|
343 val = (val > BLUR_MAX_RADIUS) ? BLUR_MAX_RADIUS : val;
|
|
344 render_priv->state.blur = val;
|
|
345 } else
|
|
346 render_priv->state.blur = 0.0;
|
34295
|
347 render_priv->state.bm_run_id++;
|
30200
|
348 // ASS standard tags
|
|
349 } else if (mystrcmp(&p, "fsc")) {
|
|
350 char tp = *p++;
|
|
351 double val;
|
|
352 if (tp == 'x') {
|
|
353 if (mystrtod(&p, &val)) {
|
|
354 val /= 100;
|
|
355 render_priv->state.scale_x =
|
|
356 render_priv->state.scale_x * (1 - pwr) + val * pwr;
|
|
357 } else
|
|
358 render_priv->state.scale_x =
|
|
359 render_priv->state.style->ScaleX;
|
|
360 } else if (tp == 'y') {
|
|
361 if (mystrtod(&p, &val)) {
|
|
362 val /= 100;
|
|
363 render_priv->state.scale_y =
|
|
364 render_priv->state.scale_y * (1 - pwr) + val * pwr;
|
|
365 } else
|
|
366 render_priv->state.scale_y =
|
|
367 render_priv->state.style->ScaleY;
|
|
368 }
|
|
369 } else if (mystrcmp(&p, "fsp")) {
|
|
370 double val;
|
|
371 if (mystrtod(&p, &val))
|
|
372 render_priv->state.hspacing =
|
|
373 render_priv->state.hspacing * (1 - pwr) + val * pwr;
|
|
374 else
|
|
375 render_priv->state.hspacing = render_priv->state.style->Spacing;
|
34011
|
376 } else if (mystrcmp(&p, "fs+")) {
|
|
377 double val;
|
|
378 if (mystrtod(&p, &val)) {
|
|
379 val = render_priv->state.font_size + pwr * val;
|
|
380 } else
|
|
381 val = render_priv->state.style->FontSize;
|
|
382 if (render_priv->state.font)
|
|
383 change_font_size(render_priv, val);
|
|
384 } else if (mystrcmp(&p, "fs-")) {
|
|
385 double val;
|
|
386 if (mystrtod(&p, &val))
|
|
387 val = render_priv->state.font_size - pwr * val;
|
|
388 else
|
|
389 val = render_priv->state.style->FontSize;
|
|
390 if (render_priv->state.font)
|
|
391 change_font_size(render_priv, val);
|
30200
|
392 } else if (mystrcmp(&p, "fs")) {
|
|
393 double val;
|
|
394 if (mystrtod(&p, &val))
|
|
395 val = render_priv->state.font_size * (1 - pwr) + val * pwr;
|
|
396 else
|
|
397 val = render_priv->state.style->FontSize;
|
|
398 if (render_priv->state.font)
|
|
399 change_font_size(render_priv, val);
|
|
400 } else if (mystrcmp(&p, "bord")) {
|
|
401 double val;
|
|
402 if (mystrtod(&p, &val)) {
|
|
403 val = render_priv->state.border_x * (1 - pwr) + val * pwr;
|
|
404 } else
|
|
405 val = -1.; // reset to default
|
35262
|
406 calc_border(render_priv, val, val);
|
34295
|
407 render_priv->state.bm_run_id++;
|
30200
|
408 } else if (mystrcmp(&p, "move")) {
|
|
409 double x1, x2, y1, y2;
|
|
410 long long t1, t2, delta_t, t;
|
|
411 double x, y;
|
|
412 double k;
|
|
413 skip('(');
|
|
414 mystrtod(&p, &x1);
|
|
415 skip(',');
|
|
416 mystrtod(&p, &y1);
|
|
417 skip(',');
|
|
418 mystrtod(&p, &x2);
|
|
419 skip(',');
|
|
420 mystrtod(&p, &y2);
|
|
421 if (*p == ',') {
|
|
422 skip(',');
|
|
423 mystrtoll(&p, &t1);
|
|
424 skip(',');
|
|
425 mystrtoll(&p, &t2);
|
|
426 ass_msg(render_priv->library, MSGL_DBG2,
|
|
427 "movement6: (%f, %f) -> (%f, %f), (%" PRId64 " .. %"
|
|
428 PRId64 ")\n", x1, y1, x2, y2, (int64_t) t1,
|
|
429 (int64_t) t2);
|
|
430 } else {
|
|
431 t1 = 0;
|
|
432 t2 = render_priv->state.event->Duration;
|
|
433 ass_msg(render_priv->library, MSGL_DBG2,
|
|
434 "movement: (%f, %f) -> (%f, %f)", x1, y1, x2, y2);
|
|
435 }
|
|
436 skip(')');
|
|
437 delta_t = t2 - t1;
|
|
438 t = render_priv->time - render_priv->state.event->Start;
|
|
439 if (t < t1)
|
|
440 k = 0.;
|
|
441 else if (t > t2)
|
|
442 k = 1.;
|
|
443 else
|
|
444 k = ((double) (t - t1)) / delta_t;
|
|
445 x = k * (x2 - x1) + x1;
|
|
446 y = k * (y2 - y1) + y1;
|
|
447 if (render_priv->state.evt_type != EVENT_POSITIONED) {
|
|
448 render_priv->state.pos_x = x;
|
|
449 render_priv->state.pos_y = y;
|
|
450 render_priv->state.detect_collisions = 0;
|
|
451 render_priv->state.evt_type = EVENT_POSITIONED;
|
|
452 }
|
|
453 } else if (mystrcmp(&p, "frx")) {
|
|
454 double val;
|
|
455 if (mystrtod(&p, &val)) {
|
|
456 val *= M_PI / 180;
|
|
457 render_priv->state.frx =
|
|
458 val * pwr + render_priv->state.frx * (1 - pwr);
|
|
459 } else
|
|
460 render_priv->state.frx = 0.;
|
|
461 } else if (mystrcmp(&p, "fry")) {
|
|
462 double val;
|
|
463 if (mystrtod(&p, &val)) {
|
|
464 val *= M_PI / 180;
|
|
465 render_priv->state.fry =
|
|
466 val * pwr + render_priv->state.fry * (1 - pwr);
|
|
467 } else
|
|
468 render_priv->state.fry = 0.;
|
|
469 } else if (mystrcmp(&p, "frz") || mystrcmp(&p, "fr")) {
|
|
470 double val;
|
|
471 if (mystrtod(&p, &val)) {
|
|
472 val *= M_PI / 180;
|
|
473 render_priv->state.frz =
|
|
474 val * pwr + render_priv->state.frz * (1 - pwr);
|
|
475 } else
|
|
476 render_priv->state.frz =
|
|
477 M_PI * render_priv->state.style->Angle / 180.;
|
|
478 } else if (mystrcmp(&p, "fn")) {
|
|
479 char *start = p;
|
|
480 char *family;
|
|
481 skip_to('\\');
|
|
482 if (p > start) {
|
|
483 family = malloc(p - start + 1);
|
|
484 strncpy(family, start, p - start);
|
|
485 family[p - start] = '\0';
|
|
486 } else
|
|
487 family = strdup(render_priv->state.style->FontName);
|
31875
|
488 free(render_priv->state.family);
|
30200
|
489 render_priv->state.family = family;
|
|
490 update_font(render_priv);
|
|
491 } else if (mystrcmp(&p, "alpha")) {
|
|
492 uint32_t val;
|
|
493 int i;
|
|
494 int hex = render_priv->track->track_type == TRACK_TYPE_ASS;
|
|
495 if (strtocolor(render_priv->library, &p, &val, hex)) {
|
|
496 unsigned char a = val >> 24;
|
|
497 for (i = 0; i < 4; ++i)
|
|
498 change_alpha(&render_priv->state.c[i], a, pwr);
|
|
499 } else {
|
|
500 change_alpha(&render_priv->state.c[0],
|
|
501 render_priv->state.style->PrimaryColour, pwr);
|
|
502 change_alpha(&render_priv->state.c[1],
|
|
503 render_priv->state.style->SecondaryColour, pwr);
|
|
504 change_alpha(&render_priv->state.c[2],
|
|
505 render_priv->state.style->OutlineColour, pwr);
|
|
506 change_alpha(&render_priv->state.c[3],
|
|
507 render_priv->state.style->BackColour, pwr);
|
|
508 }
|
34295
|
509 render_priv->state.bm_run_id++;
|
30200
|
510 // FIXME: simplify
|
|
511 } else if (mystrcmp(&p, "an")) {
|
|
512 int val;
|
|
513 if (mystrtoi(&p, &val) && val) {
|
|
514 int v = (val - 1) / 3; // 0, 1 or 2 for vertical alignment
|
|
515 ass_msg(render_priv->library, MSGL_DBG2, "an %d", val);
|
|
516 if (v != 0)
|
|
517 v = 3 - v;
|
|
518 val = ((val - 1) % 3) + 1; // horizontal alignment
|
|
519 val += v * 4;
|
|
520 ass_msg(render_priv->library, MSGL_DBG2, "align %d", val);
|
34011
|
521 if ((render_priv->state.parsed_tags & PARSED_A) == 0) {
|
|
522 render_priv->state.alignment = val;
|
|
523 render_priv->state.parsed_tags |= PARSED_A;
|
|
524 }
|
30200
|
525 } else
|
|
526 render_priv->state.alignment =
|
|
527 render_priv->state.style->Alignment;
|
|
528 } else if (mystrcmp(&p, "a")) {
|
|
529 int val;
|
34011
|
530 if (mystrtoi(&p, &val) && val) {
|
|
531 if ((render_priv->state.parsed_tags & PARSED_A) == 0) {
|
|
532 // take care of a vsfilter quirk: handle illegal \a8 like \a5
|
|
533 render_priv->state.alignment = (val == 8) ? 5 : val;
|
|
534 render_priv->state.parsed_tags |= PARSED_A;
|
|
535 }
|
|
536 } else
|
30200
|
537 render_priv->state.alignment =
|
|
538 render_priv->state.style->Alignment;
|
|
539 } else if (mystrcmp(&p, "pos")) {
|
|
540 double v1, v2;
|
|
541 skip('(');
|
|
542 mystrtod(&p, &v1);
|
|
543 skip(',');
|
|
544 mystrtod(&p, &v2);
|
|
545 skip(')');
|
|
546 ass_msg(render_priv->library, MSGL_DBG2, "pos(%f, %f)", v1, v2);
|
|
547 if (render_priv->state.evt_type == EVENT_POSITIONED) {
|
|
548 ass_msg(render_priv->library, MSGL_V, "Subtitle has a new \\pos "
|
|
549 "after \\move or \\pos, ignoring");
|
|
550 } else {
|
|
551 render_priv->state.evt_type = EVENT_POSITIONED;
|
|
552 render_priv->state.detect_collisions = 0;
|
|
553 render_priv->state.pos_x = v1;
|
|
554 render_priv->state.pos_y = v2;
|
|
555 }
|
|
556 } else if (mystrcmp(&p, "fad")) {
|
|
557 int a1, a2, a3;
|
|
558 long long t1, t2, t3, t4;
|
|
559 if (*p == 'e')
|
|
560 ++p; // either \fad or \fade
|
|
561 skip('(');
|
|
562 mystrtoi(&p, &a1);
|
|
563 skip(',');
|
|
564 mystrtoi(&p, &a2);
|
|
565 if (*p == ')') {
|
|
566 // 2-argument version (\fad, according to specs)
|
|
567 // a1 and a2 are fade-in and fade-out durations
|
|
568 t1 = 0;
|
|
569 t4 = render_priv->state.event->Duration;
|
|
570 t2 = a1;
|
|
571 t3 = t4 - a2;
|
|
572 a1 = 0xFF;
|
|
573 a2 = 0;
|
|
574 a3 = 0xFF;
|
|
575 } else {
|
|
576 // 6-argument version (\fade)
|
|
577 // a1 and a2 (and a3) are opacity values
|
|
578 skip(',');
|
|
579 mystrtoi(&p, &a3);
|
|
580 skip(',');
|
|
581 mystrtoll(&p, &t1);
|
|
582 skip(',');
|
|
583 mystrtoll(&p, &t2);
|
|
584 skip(',');
|
|
585 mystrtoll(&p, &t3);
|
|
586 skip(',');
|
|
587 mystrtoll(&p, &t4);
|
|
588 }
|
|
589 skip(')');
|
34011
|
590 if ((render_priv->state.parsed_tags & PARSED_FADE) == 0) {
|
|
591 render_priv->state.fade =
|
|
592 interpolate_alpha(render_priv->time -
|
|
593 render_priv->state.event->Start, t1, t2,
|
|
594 t3, t4, a1, a2, a3);
|
|
595 render_priv->state.parsed_tags |= PARSED_FADE;
|
|
596 }
|
30200
|
597 } else if (mystrcmp(&p, "org")) {
|
|
598 int v1, v2;
|
|
599 skip('(');
|
|
600 mystrtoi(&p, &v1);
|
|
601 skip(',');
|
|
602 mystrtoi(&p, &v2);
|
|
603 skip(')');
|
|
604 ass_msg(render_priv->library, MSGL_DBG2, "org(%d, %d)", v1, v2);
|
|
605 if (!render_priv->state.have_origin) {
|
|
606 render_priv->state.org_x = v1;
|
|
607 render_priv->state.org_y = v2;
|
|
608 render_priv->state.have_origin = 1;
|
|
609 render_priv->state.detect_collisions = 0;
|
|
610 }
|
|
611 } else if (mystrcmp(&p, "t")) {
|
|
612 double v[3];
|
|
613 int v1, v2;
|
|
614 double v3;
|
|
615 int cnt;
|
|
616 long long t1, t2, t, delta_t;
|
|
617 double k;
|
|
618 skip('(');
|
|
619 for (cnt = 0; cnt < 3; ++cnt) {
|
|
620 if (*p == '\\')
|
|
621 break;
|
31853
|
622 mystrtod(&p, &v[cnt]);
|
30200
|
623 skip(',');
|
|
624 }
|
|
625 if (cnt == 3) {
|
|
626 v1 = v[0];
|
|
627 v2 = (v[1] < v1) ? render_priv->state.event->Duration : v[1];
|
|
628 v3 = v[2];
|
|
629 } else if (cnt == 2) {
|
|
630 v1 = v[0];
|
|
631 v2 = (v[1] < v1) ? render_priv->state.event->Duration : v[1];
|
|
632 v3 = 1.;
|
|
633 } else if (cnt == 1) {
|
|
634 v1 = 0;
|
|
635 v2 = render_priv->state.event->Duration;
|
|
636 v3 = v[0];
|
|
637 } else { // cnt == 0
|
|
638 v1 = 0;
|
|
639 v2 = render_priv->state.event->Duration;
|
|
640 v3 = 1.;
|
|
641 }
|
|
642 render_priv->state.detect_collisions = 0;
|
|
643 t1 = v1;
|
|
644 t2 = v2;
|
|
645 delta_t = v2 - v1;
|
|
646 if (v3 < 0.)
|
|
647 v3 = 0.;
|
|
648 t = render_priv->time - render_priv->state.event->Start; // FIXME: move to render_context
|
|
649 if (t <= t1)
|
|
650 k = 0.;
|
|
651 else if (t >= t2)
|
|
652 k = 1.;
|
|
653 else {
|
|
654 assert(delta_t != 0.);
|
|
655 k = pow(((double) (t - t1)) / delta_t, v3);
|
|
656 }
|
|
657 while (*p == '\\')
|
|
658 p = parse_tag(render_priv, p, k); // maybe k*pwr ? no, specs forbid nested \t's
|
|
659 skip_to(')'); // in case there is some unknown tag or a comment
|
|
660 skip(')');
|
|
661 } else if (mystrcmp(&p, "clip")) {
|
|
662 char *start = p;
|
|
663 int x0, y0, x1, y1;
|
|
664 int res = 1;
|
|
665 skipopt('(');
|
|
666 res &= mystrtoi(&p, &x0);
|
|
667 skipopt(',');
|
|
668 res &= mystrtoi(&p, &y0);
|
|
669 skipopt(',');
|
|
670 res &= mystrtoi(&p, &x1);
|
|
671 skipopt(',');
|
|
672 res &= mystrtoi(&p, &y1);
|
|
673 skipopt(')');
|
|
674 if (res) {
|
|
675 render_priv->state.clip_x0 =
|
|
676 render_priv->state.clip_x0 * (1 - pwr) + x0 * pwr;
|
|
677 render_priv->state.clip_x1 =
|
|
678 render_priv->state.clip_x1 * (1 - pwr) + x1 * pwr;
|
|
679 render_priv->state.clip_y0 =
|
|
680 render_priv->state.clip_y0 * (1 - pwr) + y0 * pwr;
|
|
681 render_priv->state.clip_y1 =
|
|
682 render_priv->state.clip_y1 * (1 - pwr) + y1 * pwr;
|
|
683 // Might be a vector clip
|
|
684 } else if (!render_priv->state.clip_drawing) {
|
|
685 p = parse_vector_clip(render_priv, start);
|
|
686 render_priv->state.clip_drawing_mode = 0;
|
|
687 } else {
|
|
688 render_priv->state.clip_x0 = 0;
|
|
689 render_priv->state.clip_y0 = 0;
|
|
690 render_priv->state.clip_x1 = render_priv->track->PlayResX;
|
|
691 render_priv->state.clip_y1 = render_priv->track->PlayResY;
|
|
692 }
|
|
693 } else if (mystrcmp(&p, "c")) {
|
|
694 uint32_t val;
|
|
695 int hex = render_priv->track->track_type == TRACK_TYPE_ASS;
|
|
696 if (!strtocolor(render_priv->library, &p, &val, hex))
|
|
697 val = render_priv->state.style->PrimaryColour;
|
|
698 ass_msg(render_priv->library, MSGL_DBG2, "color: %X", val);
|
|
699 change_color(&render_priv->state.c[0], val, pwr);
|
34295
|
700 render_priv->state.bm_run_id++;
|
30200
|
701 } else if ((*p >= '1') && (*p <= '4') && (++p)
|
|
702 && (mystrcmp(&p, "c") || mystrcmp(&p, "a"))) {
|
|
703 char n = *(p - 2);
|
|
704 int cidx = n - '1';
|
|
705 char cmd = *(p - 1);
|
|
706 uint32_t val;
|
|
707 int hex = render_priv->track->track_type == TRACK_TYPE_ASS;
|
|
708 assert((n >= '1') && (n <= '4'));
|
|
709 if (!strtocolor(render_priv->library, &p, &val, hex))
|
|
710 switch (n) {
|
|
711 case '1':
|
|
712 val = render_priv->state.style->PrimaryColour;
|
|
713 break;
|
|
714 case '2':
|
|
715 val = render_priv->state.style->SecondaryColour;
|
|
716 break;
|
|
717 case '3':
|
|
718 val = render_priv->state.style->OutlineColour;
|
|
719 break;
|
|
720 case '4':
|
|
721 val = render_priv->state.style->BackColour;
|
|
722 break;
|
|
723 default:
|
|
724 val = 0;
|
|
725 break; // impossible due to assert; avoid compilation warning
|
|
726 }
|
|
727 switch (cmd) {
|
|
728 case 'c':
|
|
729 change_color(render_priv->state.c + cidx, val, pwr);
|
34295
|
730 render_priv->state.bm_run_id++;
|
30200
|
731 break;
|
|
732 case 'a':
|
|
733 change_alpha(render_priv->state.c + cidx, val >> 24, pwr);
|
34295
|
734 render_priv->state.bm_run_id++;
|
30200
|
735 break;
|
|
736 default:
|
|
737 ass_msg(render_priv->library, MSGL_WARN, "Bad command: %c%c",
|
|
738 n, cmd);
|
|
739 break;
|
|
740 }
|
|
741 ass_msg(render_priv->library, MSGL_DBG2, "single c/a at %f: %c%c = %X",
|
|
742 pwr, n, cmd, render_priv->state.c[cidx]);
|
|
743 } else if (mystrcmp(&p, "r")) {
|
35262
|
744 char *start = p;
|
|
745 char *style;
|
|
746 skip_to('\\');
|
|
747 if (p > start) {
|
|
748 style = malloc(p - start + 1);
|
|
749 strncpy(style, start, p - start);
|
|
750 style[p - start] = '\0';
|
|
751 reset_render_context(render_priv,
|
|
752 render_priv->track->styles + lookup_style(render_priv->track, style));
|
|
753 free(style);
|
|
754 } else
|
|
755 reset_render_context(render_priv, NULL);
|
30200
|
756 } else if (mystrcmp(&p, "be")) {
|
|
757 int val;
|
|
758 if (mystrtoi(&p, &val)) {
|
|
759 // Clamp to a safe upper limit, since high values need excessive CPU
|
|
760 val = (val < 0) ? 0 : val;
|
|
761 val = (val > MAX_BE) ? MAX_BE : val;
|
|
762 render_priv->state.be = val;
|
|
763 } else
|
|
764 render_priv->state.be = 0;
|
34295
|
765 render_priv->state.bm_run_id++;
|
30200
|
766 } else if (mystrcmp(&p, "b")) {
|
|
767 int b;
|
|
768 if (mystrtoi(&p, &b)) {
|
|
769 if (pwr >= .5)
|
|
770 render_priv->state.bold = b;
|
|
771 } else
|
|
772 render_priv->state.bold = render_priv->state.style->Bold;
|
|
773 update_font(render_priv);
|
|
774 } else if (mystrcmp(&p, "i")) {
|
|
775 int i;
|
|
776 if (mystrtoi(&p, &i)) {
|
|
777 if (pwr >= .5)
|
|
778 render_priv->state.italic = i;
|
|
779 } else
|
|
780 render_priv->state.italic = render_priv->state.style->Italic;
|
|
781 update_font(render_priv);
|
|
782 } else if (mystrcmp(&p, "kf") || mystrcmp(&p, "K")) {
|
|
783 int val = 0;
|
|
784 mystrtoi(&p, &val);
|
|
785 render_priv->state.effect_type = EF_KARAOKE_KF;
|
|
786 if (render_priv->state.effect_timing)
|
|
787 render_priv->state.effect_skip_timing +=
|
|
788 render_priv->state.effect_timing;
|
|
789 render_priv->state.effect_timing = val * 10;
|
|
790 } else if (mystrcmp(&p, "ko")) {
|
|
791 int val = 0;
|
|
792 mystrtoi(&p, &val);
|
|
793 render_priv->state.effect_type = EF_KARAOKE_KO;
|
|
794 if (render_priv->state.effect_timing)
|
|
795 render_priv->state.effect_skip_timing +=
|
|
796 render_priv->state.effect_timing;
|
|
797 render_priv->state.effect_timing = val * 10;
|
|
798 } else if (mystrcmp(&p, "k")) {
|
|
799 int val = 0;
|
|
800 mystrtoi(&p, &val);
|
|
801 render_priv->state.effect_type = EF_KARAOKE;
|
|
802 if (render_priv->state.effect_timing)
|
|
803 render_priv->state.effect_skip_timing +=
|
|
804 render_priv->state.effect_timing;
|
|
805 render_priv->state.effect_timing = val * 10;
|
|
806 } else if (mystrcmp(&p, "shad")) {
|
|
807 double val;
|
|
808 if (mystrtod(&p, &val)) {
|
|
809 if (render_priv->state.shadow_x == render_priv->state.shadow_y)
|
|
810 val = render_priv->state.shadow_x * (1 - pwr) + val * pwr;
|
|
811 } else
|
|
812 val = 0.;
|
|
813 render_priv->state.shadow_x = render_priv->state.shadow_y = val;
|
34295
|
814 render_priv->state.bm_run_id++;
|
30200
|
815 } else if (mystrcmp(&p, "s")) {
|
|
816 int val;
|
|
817 if (mystrtoi(&p, &val) && val)
|
|
818 render_priv->state.flags |= DECO_STRIKETHROUGH;
|
|
819 else
|
|
820 render_priv->state.flags &= ~DECO_STRIKETHROUGH;
|
34295
|
821 render_priv->state.bm_run_id++;
|
30200
|
822 } else if (mystrcmp(&p, "u")) {
|
|
823 int val;
|
|
824 if (mystrtoi(&p, &val) && val)
|
|
825 render_priv->state.flags |= DECO_UNDERLINE;
|
|
826 else
|
|
827 render_priv->state.flags &= ~DECO_UNDERLINE;
|
34295
|
828 render_priv->state.bm_run_id++;
|
30200
|
829 } else if (mystrcmp(&p, "pbo")) {
|
|
830 double val = 0;
|
|
831 if (mystrtod(&p, &val))
|
|
832 render_priv->state.drawing->pbo = val;
|
|
833 } else if (mystrcmp(&p, "p")) {
|
|
834 int val;
|
|
835 if (!mystrtoi(&p, &val))
|
|
836 val = 0;
|
|
837 if (val)
|
|
838 render_priv->state.drawing->scale = val;
|
|
839 render_priv->state.drawing_mode = !!val;
|
|
840 } else if (mystrcmp(&p, "q")) {
|
|
841 int val;
|
|
842 if (!mystrtoi(&p, &val))
|
|
843 val = render_priv->track->WrapStyle;
|
|
844 render_priv->state.wrap_style = val;
|
34295
|
845 } else if (mystrcmp(&p, "fe")) {
|
|
846 int val;
|
|
847 if (!mystrtoi(&p, &val))
|
|
848 val = render_priv->state.style->Encoding;
|
|
849 render_priv->state.font_encoding = val;
|
30200
|
850 }
|
|
851
|
|
852 return p;
|
|
853 }
|
|
854
|
|
855 void apply_transition_effects(ASS_Renderer *render_priv, ASS_Event *event)
|
|
856 {
|
|
857 int v[4];
|
|
858 int cnt;
|
|
859 char *p = event->Effect;
|
|
860
|
|
861 if (!p || !*p)
|
|
862 return;
|
|
863
|
|
864 cnt = 0;
|
|
865 while (cnt < 4 && (p = strchr(p, ';'))) {
|
|
866 v[cnt++] = atoi(++p);
|
|
867 }
|
|
868
|
|
869 if (strncmp(event->Effect, "Banner;", 7) == 0) {
|
|
870 int delay;
|
|
871 if (cnt < 1) {
|
|
872 ass_msg(render_priv->library, MSGL_V,
|
|
873 "Error parsing effect: '%s'", event->Effect);
|
|
874 return;
|
|
875 }
|
|
876 if (cnt >= 2 && v[1] == 0) // right-to-left
|
|
877 render_priv->state.scroll_direction = SCROLL_RL;
|
|
878 else // left-to-right
|
|
879 render_priv->state.scroll_direction = SCROLL_LR;
|
|
880
|
|
881 delay = v[0];
|
|
882 if (delay == 0)
|
|
883 delay = 1; // ?
|
|
884 render_priv->state.scroll_shift =
|
|
885 (render_priv->time - render_priv->state.event->Start) / delay;
|
|
886 render_priv->state.evt_type = EVENT_HSCROLL;
|
|
887 return;
|
|
888 }
|
|
889
|
|
890 if (strncmp(event->Effect, "Scroll up;", 10) == 0) {
|
|
891 render_priv->state.scroll_direction = SCROLL_BT;
|
|
892 } else if (strncmp(event->Effect, "Scroll down;", 12) == 0) {
|
|
893 render_priv->state.scroll_direction = SCROLL_TB;
|
|
894 } else {
|
31853
|
895 ass_msg(render_priv->library, MSGL_DBG2,
|
30200
|
896 "Unknown transition effect: '%s'", event->Effect);
|
|
897 return;
|
|
898 }
|
|
899 // parse scroll up/down parameters
|
|
900 {
|
|
901 int delay;
|
|
902 int y0, y1;
|
|
903 if (cnt < 3) {
|
|
904 ass_msg(render_priv->library, MSGL_V,
|
|
905 "Error parsing effect: '%s'", event->Effect);
|
|
906 return;
|
|
907 }
|
|
908 delay = v[2];
|
|
909 if (delay == 0)
|
|
910 delay = 1; // ?
|
|
911 render_priv->state.scroll_shift =
|
|
912 (render_priv->time - render_priv->state.event->Start) / delay;
|
|
913 if (v[0] < v[1]) {
|
|
914 y0 = v[0];
|
|
915 y1 = v[1];
|
|
916 } else {
|
|
917 y0 = v[1];
|
|
918 y1 = v[0];
|
|
919 }
|
|
920 if (y1 == 0)
|
|
921 y1 = render_priv->track->PlayResY; // y0=y1=0 means fullscreen scrolling
|
|
922 render_priv->state.clip_y0 = y0;
|
|
923 render_priv->state.clip_y1 = y1;
|
|
924 render_priv->state.evt_type = EVENT_VSCROLL;
|
|
925 render_priv->state.detect_collisions = 0;
|
|
926 }
|
|
927
|
|
928 }
|
|
929
|
|
930 /**
|
34295
|
931 * \brief determine karaoke effects
|
|
932 * Karaoke effects cannot be calculated during parse stage (get_next_char()),
|
|
933 * so they are done in a separate step.
|
|
934 * Parse stage: when karaoke style override is found, its parameters are stored in the next glyph's
|
|
935 * (the first glyph of the karaoke word)'s effect_type and effect_timing.
|
|
936 * This function:
|
|
937 * 1. sets effect_type for all glyphs in the word (_karaoke_ word)
|
|
938 * 2. sets effect_timing for all glyphs to x coordinate of the border line between the left and right karaoke parts
|
|
939 * (left part is filled with PrimaryColour, right one - with SecondaryColour).
|
|
940 */
|
|
941 void process_karaoke_effects(ASS_Renderer *render_priv)
|
|
942 {
|
|
943 GlyphInfo *cur, *cur2;
|
|
944 GlyphInfo *s1, *e1; // start and end of the current word
|
|
945 GlyphInfo *s2; // start of the next word
|
|
946 int i;
|
|
947 int timing; // current timing
|
|
948 int tm_start, tm_end; // timings at start and end of the current word
|
|
949 int tm_current;
|
|
950 double dt;
|
|
951 int x;
|
|
952 int x_start, x_end;
|
|
953
|
|
954 tm_current = render_priv->time - render_priv->state.event->Start;
|
|
955 timing = 0;
|
|
956 s1 = s2 = 0;
|
|
957 for (i = 0; i <= render_priv->text_info.length; ++i) {
|
|
958 cur = render_priv->text_info.glyphs + i;
|
|
959 if ((i == render_priv->text_info.length)
|
|
960 || (cur->effect_type != EF_NONE)) {
|
|
961 s1 = s2;
|
|
962 s2 = cur;
|
|
963 if (s1) {
|
|
964 e1 = s2 - 1;
|
|
965 tm_start = timing + s1->effect_skip_timing;
|
|
966 tm_end = tm_start + s1->effect_timing;
|
|
967 timing = tm_end;
|
|
968 x_start = 1000000;
|
|
969 x_end = -1000000;
|
|
970 for (cur2 = s1; cur2 <= e1; ++cur2) {
|
|
971 x_start = FFMIN(x_start, d6_to_int(cur2->bbox.xMin + cur2->pos.x));
|
|
972 x_end = FFMAX(x_end, d6_to_int(cur2->bbox.xMax + cur2->pos.x));
|
|
973 }
|
|
974
|
|
975 dt = (tm_current - tm_start);
|
|
976 if ((s1->effect_type == EF_KARAOKE)
|
|
977 || (s1->effect_type == EF_KARAOKE_KO)) {
|
|
978 if (dt > 0)
|
|
979 x = x_end + 1;
|
|
980 else
|
|
981 x = x_start;
|
|
982 } else if (s1->effect_type == EF_KARAOKE_KF) {
|
|
983 dt /= (tm_end - tm_start);
|
|
984 x = x_start + (x_end - x_start) * dt;
|
|
985 } else {
|
|
986 ass_msg(render_priv->library, MSGL_ERR,
|
|
987 "Unknown effect type");
|
|
988 continue;
|
|
989 }
|
|
990
|
|
991 for (cur2 = s1; cur2 <= e1; ++cur2) {
|
|
992 cur2->effect_type = s1->effect_type;
|
|
993 cur2->effect_timing = x - d6_to_int(cur2->pos.x);
|
|
994 }
|
|
995 }
|
|
996 }
|
|
997 }
|
|
998 }
|
|
999
|
|
1000
|
|
1001 /**
|
35262
|
1002 * \brief Get next ucs4 char from string, parsing UTF-8 and escapes
|
30200
|
1003 * \param str string pointer
|
|
1004 * \return ucs4 code of the next char
|
|
1005 * On return str points to the unparsed part of the string
|
|
1006 */
|
|
1007 unsigned get_next_char(ASS_Renderer *render_priv, char **str)
|
|
1008 {
|
|
1009 char *p = *str;
|
|
1010 unsigned chr;
|
|
1011 if (*p == '\t') {
|
|
1012 ++p;
|
|
1013 *str = p;
|
|
1014 return ' ';
|
|
1015 }
|
|
1016 if (*p == '\\') {
|
|
1017 if ((p[1] == 'N') || ((p[1] == 'n') &&
|
|
1018 (render_priv->state.wrap_style == 2))) {
|
|
1019 p += 2;
|
|
1020 *str = p;
|
|
1021 return '\n';
|
|
1022 } else if (p[1] == 'n') {
|
|
1023 p += 2;
|
|
1024 *str = p;
|
|
1025 return ' ';
|
|
1026 } else if (p[1] == 'h') {
|
|
1027 p += 2;
|
|
1028 *str = p;
|
|
1029 return NBSP;
|
31853
|
1030 } else if (p[1] == '{') {
|
|
1031 p += 2;
|
|
1032 *str = p;
|
|
1033 return '{';
|
|
1034 } else if (p[1] == '}') {
|
|
1035 p += 2;
|
|
1036 *str = p;
|
|
1037 return '}';
|
30200
|
1038 }
|
|
1039 }
|
|
1040 chr = ass_utf8_get_char((char **) &p);
|
|
1041 *str = p;
|
|
1042 return chr;
|
|
1043 }
|