]> granicus.if.org Git - libass/blob - libass/ass.c
Fix parsing of unusual Alignment values in ASS style definitions
[libass] / libass / ass.c
1 /*
2  * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
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 <assert.h>
26 #include <errno.h>
27 #include <limits.h>
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <inttypes.h>
31
32 #ifdef CONFIG_ICONV
33 #include <iconv.h>
34 #endif
35
36 #include "ass.h"
37 #include "ass_utils.h"
38 #include "ass_library.h"
39 #include "ass_string.h"
40
41 #define ass_atof(STR) (ass_strtod((STR),NULL))
42
43 typedef enum {
44     PST_UNKNOWN = 0,
45     PST_INFO,
46     PST_STYLES,
47     PST_EVENTS,
48     PST_FONTS
49 } ParserState;
50
51 struct parser_priv {
52     ParserState state;
53     char *fontname;
54     char *fontdata;
55     int fontdata_size;
56     int fontdata_used;
57
58     // contains bitmap of ReadOrder IDs of all read events
59     uint32_t *read_order_bitmap;
60     int read_order_elems; // size in uint32_t units of read_order_bitmap
61     int check_readorder;
62 };
63
64 #define ASS_STYLES_ALLOC 20
65
66 int ass_library_version(void)
67 {
68     return LIBASS_VERSION;
69 }
70
71 void ass_free_track(ASS_Track *track)
72 {
73     int i;
74
75     if (track->parser_priv) {
76         free(track->parser_priv->read_order_bitmap);
77         free(track->parser_priv->fontname);
78         free(track->parser_priv->fontdata);
79         free(track->parser_priv);
80     }
81     free(track->style_format);
82     free(track->event_format);
83     free(track->Language);
84     if (track->styles) {
85         for (i = 0; i < track->n_styles; ++i)
86             ass_free_style(track, i);
87     }
88     free(track->styles);
89     if (track->events) {
90         for (i = 0; i < track->n_events; ++i)
91             ass_free_event(track, i);
92     }
93     free(track->events);
94     free(track->name);
95     free(track);
96 }
97
98 /// \brief Allocate a new style struct
99 /// \param track track
100 /// \return style id
101 int ass_alloc_style(ASS_Track *track)
102 {
103     int sid;
104
105     assert(track->n_styles <= track->max_styles);
106
107     if (track->n_styles == track->max_styles) {
108         track->max_styles += ASS_STYLES_ALLOC;
109         track->styles =
110             (ASS_Style *) realloc(track->styles,
111                                   sizeof(ASS_Style) *
112                                   track->max_styles);
113     }
114
115     sid = track->n_styles++;
116     memset(track->styles + sid, 0, sizeof(ASS_Style));
117     return sid;
118 }
119
120 /// \brief Allocate a new event struct
121 /// \param track track
122 /// \return event id
123 int ass_alloc_event(ASS_Track *track)
124 {
125     int eid;
126
127     assert(track->n_events <= track->max_events);
128
129     if (track->n_events == track->max_events) {
130         track->max_events = track->max_events * 2 + 1;
131         track->events =
132             (ASS_Event *) realloc(track->events,
133                                   sizeof(ASS_Event) *
134                                   track->max_events);
135     }
136
137     eid = track->n_events++;
138     memset(track->events + eid, 0, sizeof(ASS_Event));
139     return eid;
140 }
141
142 void ass_free_event(ASS_Track *track, int eid)
143 {
144     ASS_Event *event = track->events + eid;
145
146     free(event->Name);
147     free(event->Effect);
148     free(event->Text);
149     free(event->render_priv);
150 }
151
152 void ass_free_style(ASS_Track *track, int sid)
153 {
154     ASS_Style *style = track->styles + sid;
155
156     free(style->Name);
157     free(style->FontName);
158 }
159
160 static int resize_read_order_bitmap(ASS_Track *track, int max_id)
161 {
162     // Don't allow malicious files to OOM us easily. Also avoids int overflows.
163     if (max_id < 0 || max_id >= 10 * 1024 * 1024 * 8)
164         goto fail;
165     if (max_id >= track->parser_priv->read_order_elems * 32) {
166         int oldelems = track->parser_priv->read_order_elems;
167         int elems = ((max_id + 31) / 32 + 1) * 2;
168         assert(elems >= oldelems);
169         track->parser_priv->read_order_elems = elems;
170         void *new_bitmap =
171             realloc(track->parser_priv->read_order_bitmap, elems * 4);
172         if (!new_bitmap)
173             goto fail;
174         track->parser_priv->read_order_bitmap = new_bitmap;
175         memset(track->parser_priv->read_order_bitmap + oldelems, 0,
176                (elems - oldelems) * 4);
177     }
178     return 0;
179
180 fail:
181     free(track->parser_priv->read_order_bitmap);
182     track->parser_priv->read_order_bitmap = NULL;
183     track->parser_priv->read_order_elems = 0;
184     return -1;
185 }
186
187 static int test_and_set_read_order_bit(ASS_Track *track, int id)
188 {
189     if (resize_read_order_bitmap(track, id) < 0)
190         return -1;
191     int index = id / 32;
192     int bit = 1 << (id % 32);
193     if (track->parser_priv->read_order_bitmap[index] & bit)
194         return 1;
195     track->parser_priv->read_order_bitmap[index] |= bit;
196     return 0;
197 }
198
199 // ==============================================================================================
200
201 /**
202  * \brief Set up default style
203  * \param style style to edit to defaults
204  * The parameters are mostly taken directly from VSFilter source for
205  * best compatibility.
206  */
207 static void set_default_style(ASS_Style *style)
208 {
209     style->Name             = strdup("Default");
210     style->FontName         = strdup("Arial");
211     style->FontSize         = 18;
212     style->PrimaryColour    = 0xffffff00;
213     style->SecondaryColour  = 0x00ffff00;
214     style->OutlineColour    = 0x00000000;
215     style->BackColour       = 0x00000080;
216     style->Bold             = 200;
217     style->ScaleX           = 1.0;
218     style->ScaleY           = 1.0;
219     style->Spacing          = 0;
220     style->BorderStyle      = 1;
221     style->Outline          = 2;
222     style->Shadow           = 3;
223     style->Alignment        = 2;
224     style->MarginL = style->MarginR = style->MarginV = 20;
225 }
226
227 static long long string2timecode(ASS_Library *library, char *p)
228 {
229     int h, m, s, ms;
230     long long tm;
231     int res = sscanf(p, "%d:%d:%d.%d", &h, &m, &s, &ms);
232     if (res < 4) {
233         ass_msg(library, MSGL_WARN, "Bad timestamp");
234         return 0;
235     }
236     tm = ((h * 60LL + m) * 60 + s) * 1000 + ms * 10;
237     return tm;
238 }
239
240 /**
241  * \brief converts numpad-style align to align.
242  */
243 static int numpad2align(int val)
244 {
245     if (val < -INT_MAX)
246         // Pick an alignment somewhat arbitrarily. VSFilter handles
247         // INT32_MIN as a mix of 1, 2 and 3, so prefer one of those values.
248         val = 2;
249     else if (val < 0)
250         val = -val;
251
252     int res = ((val - 1) % 3) + 1;  // horizontal alignment
253     if (val <= 3)
254         res |= VALIGN_SUB;
255     else if (val <= 6)
256         res |= VALIGN_CENTER;
257     else
258         res |= VALIGN_TOP;
259     return res;
260 }
261
262 #define NEXT(str,token) \
263         token = next_token(&str); \
264         if (!token) break;
265
266
267 #define ALIAS(alias,name) \
268         if (ass_strcasecmp(tname, #alias) == 0) {tname = #name;}
269
270 /* One section started with PARSE_START and PARSE_END parses a single token
271  * (contained in the variable named token) for the header indicated by the
272  * variable tname. It does so by chaining a number of else-if statements, each
273  * of which checks if the tname variable indicates that this header should be
274  * parsed. The first parameter of the macro gives the name of the header.
275  *
276  * The string that is passed is in str. str is advanced to the next token if
277  * a header could be parsed. The parsed results are stored in the variable
278  * target, which has the type ASS_Style* or ASS_Event*.
279  */
280 #define PARSE_START if (0) {
281 #define PARSE_END   }
282
283 #define ANYVAL(name,func) \
284         } else if (ass_strcasecmp(tname, #name) == 0) { \
285                 target->name = func(token);
286
287 #define STRVAL(name) \
288         } else if (ass_strcasecmp(tname, #name) == 0) { \
289                 if (target->name != NULL) free(target->name); \
290                 target->name = strdup(token);
291
292 #define STARREDSTRVAL(name) \
293     } else if (ass_strcasecmp(tname, #name) == 0) { \
294         if (target->name != NULL) free(target->name); \
295         while (*token == '*') ++token; \
296         target->name = strdup(token);
297
298 #define COLORVAL(name) ANYVAL(name,parse_color_header)
299 #define INTVAL(name) ANYVAL(name,atoi)
300 #define FPVAL(name) ANYVAL(name,ass_atof)
301 #define TIMEVAL(name) \
302         } else if (ass_strcasecmp(tname, #name) == 0) { \
303                 target->name = string2timecode(track->library, token);
304
305 #define STYLEVAL(name) \
306         } else if (ass_strcasecmp(tname, #name) == 0) { \
307                 target->name = lookup_style(track, token);
308
309 static char *next_token(char **str)
310 {
311     char *p = *str;
312     char *start;
313     skip_spaces(&p);
314     if (*p == '\0') {
315         *str = p;
316         return 0;
317     }
318     start = p;                  // start of the token
319     for (; (*p != '\0') && (*p != ','); ++p) {
320     }
321     if (*p == '\0') {
322         *str = p;               // eos found, str will point to '\0' at exit
323     } else {
324         *p = '\0';
325         *str = p + 1;           // ',' found, str will point to the next char (beginning of the next token)
326     }
327     rskip_spaces(&p, start);    // end of current token: the first space character, or '\0'
328     *p = '\0';
329     return start;
330 }
331
332 /**
333  * \brief Parse the tail of Dialogue line
334  * \param track track
335  * \param event parsed data goes here
336  * \param str string to parse, zero-terminated
337  * \param n_ignored number of format options to skip at the beginning
338 */
339 static int process_event_tail(ASS_Track *track, ASS_Event *event,
340                               char *str, int n_ignored)
341 {
342     char *token;
343     char *tname;
344     char *p = str;
345     int i;
346     ASS_Event *target = event;
347
348     char *format = strdup(track->event_format);
349     char *q = format;           // format scanning pointer
350
351     if (track->n_styles == 0) {
352         // add "Default" style to the end
353         // will be used if track does not contain a default style (or even does not contain styles at all)
354         int sid = ass_alloc_style(track);
355         set_default_style(&track->styles[sid]);
356         track->default_style = sid;
357     }
358
359     for (i = 0; i < n_ignored; ++i) {
360         NEXT(q, tname);
361     }
362
363     while (1) {
364         NEXT(q, tname);
365         if (ass_strcasecmp(tname, "Text") == 0) {
366             char *last;
367             event->Text = strdup(p);
368             if (*event->Text != 0) {
369                 last = event->Text + strlen(event->Text) - 1;
370                 if (last >= event->Text && *last == '\r')
371                     *last = 0;
372             }
373             event->Duration -= event->Start;
374             free(format);
375             return 0;           // "Text" is always the last
376         }
377         NEXT(p, token);
378
379         ALIAS(End, Duration)    // temporarily store end timecode in event->Duration
380         PARSE_START
381             INTVAL(Layer)
382             STYLEVAL(Style)
383             STRVAL(Name)
384             STRVAL(Effect)
385             INTVAL(MarginL)
386             INTVAL(MarginR)
387             INTVAL(MarginV)
388             TIMEVAL(Start)
389             TIMEVAL(Duration)
390         PARSE_END
391     }
392     free(format);
393     return 1;
394 }
395
396 /**
397  * \brief Parse command line style overrides (--ass-force-style option)
398  * \param track track to apply overrides to
399  * The format for overrides is [StyleName.]Field=Value
400  */
401 void ass_process_force_style(ASS_Track *track)
402 {
403     char **fs, *eq, *dt, *style, *tname, *token;
404     ASS_Style *target;
405     int sid;
406     char **list = track->library->style_overrides;
407
408     if (!list)
409         return;
410
411     for (fs = list; *fs; ++fs) {
412         eq = strrchr(*fs, '=');
413         if (!eq)
414             continue;
415         *eq = '\0';
416         token = eq + 1;
417
418         if (!ass_strcasecmp(*fs, "PlayResX"))
419             track->PlayResX = atoi(token);
420         else if (!ass_strcasecmp(*fs, "PlayResY"))
421             track->PlayResY = atoi(token);
422         else if (!ass_strcasecmp(*fs, "Timer"))
423             track->Timer = ass_atof(token);
424         else if (!ass_strcasecmp(*fs, "WrapStyle"))
425             track->WrapStyle = atoi(token);
426         else if (!ass_strcasecmp(*fs, "ScaledBorderAndShadow"))
427             track->ScaledBorderAndShadow = parse_bool(token);
428         else if (!ass_strcasecmp(*fs, "Kerning"))
429             track->Kerning = parse_bool(token);
430         else if (!ass_strcasecmp(*fs, "YCbCr Matrix"))
431             track->YCbCrMatrix = parse_ycbcr_matrix(token);
432
433         dt = strrchr(*fs, '.');
434         if (dt) {
435             *dt = '\0';
436             style = *fs;
437             tname = dt + 1;
438         } else {
439             style = NULL;
440             tname = *fs;
441         }
442         for (sid = 0; sid < track->n_styles; ++sid) {
443             if (style == NULL
444                 || ass_strcasecmp(track->styles[sid].Name, style) == 0) {
445                 target = track->styles + sid;
446                 PARSE_START
447                     STRVAL(FontName)
448                     COLORVAL(PrimaryColour)
449                     COLORVAL(SecondaryColour)
450                     COLORVAL(OutlineColour)
451                     COLORVAL(BackColour)
452                     FPVAL(FontSize)
453                     INTVAL(Bold)
454                     INTVAL(Italic)
455                     INTVAL(Underline)
456                     INTVAL(StrikeOut)
457                     FPVAL(Spacing)
458                     FPVAL(Angle)
459                     INTVAL(BorderStyle)
460                     INTVAL(Alignment)
461                     INTVAL(Justify)
462                     INTVAL(MarginL)
463                     INTVAL(MarginR)
464                     INTVAL(MarginV)
465                     INTVAL(Encoding)
466                     FPVAL(ScaleX)
467                     FPVAL(ScaleY)
468                     FPVAL(Outline)
469                     FPVAL(Shadow)
470                     FPVAL(Blur)
471                 PARSE_END
472             }
473         }
474         *eq = '=';
475         if (dt)
476             *dt = '.';
477     }
478 }
479
480 /**
481  * \brief Parse the Style line
482  * \param track track
483  * \param str string to parse, zero-terminated
484  * Allocates a new style struct.
485 */
486 static int process_style(ASS_Track *track, char *str)
487 {
488
489     char *token;
490     char *tname;
491     char *p = str;
492     char *format;
493     char *q;                    // format scanning pointer
494     int sid;
495     ASS_Style *style;
496     ASS_Style *target;
497
498     if (!track->style_format) {
499         // no style format header
500         // probably an ancient script version
501         if (track->track_type == TRACK_TYPE_SSA)
502             track->style_format =
503                 strdup
504                 ("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
505                  "TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline,"
506                  "Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding");
507         else
508             track->style_format =
509                 strdup
510                 ("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
511                  "OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut,"
512                  "ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow,"
513                  "Alignment, MarginL, MarginR, MarginV, Encoding");
514     }
515
516     q = format = strdup(track->style_format);
517
518     // Add default style first
519     if (track->n_styles == 0) {
520         // will be used if track does not contain a default style (or even does not contain styles at all)
521         int sid = ass_alloc_style(track);
522         set_default_style(&track->styles[sid]);
523         track->default_style = sid;
524     }
525
526     ass_msg(track->library, MSGL_V, "[%p] Style: %s", track, str);
527
528     sid = ass_alloc_style(track);
529
530     style = track->styles + sid;
531     target = style;
532
533     // fill style with some default values
534     style->ScaleX = 100.;
535     style->ScaleY = 100.;
536
537     while (1) {
538         NEXT(q, tname);
539         NEXT(p, token);
540
541         PARSE_START
542             STARREDSTRVAL(Name)
543             if (strcmp(target->Name, "Default") == 0)
544                 track->default_style = sid;
545             STRVAL(FontName)
546             COLORVAL(PrimaryColour)
547             COLORVAL(SecondaryColour)
548             COLORVAL(OutlineColour) // TertiaryColor
549             COLORVAL(BackColour)
550             // SSA uses BackColour for both outline and shadow
551             // this will destroy SSA's TertiaryColour, but i'm not going to use it anyway
552             if (track->track_type == TRACK_TYPE_SSA)
553                 target->OutlineColour = target->BackColour;
554             FPVAL(FontSize)
555             INTVAL(Bold)
556             INTVAL(Italic)
557             INTVAL(Underline)
558             INTVAL(StrikeOut)
559             FPVAL(Spacing)
560             FPVAL(Angle)
561             INTVAL(BorderStyle)
562             INTVAL(Alignment)
563             if (track->track_type == TRACK_TYPE_ASS)
564                 target->Alignment = numpad2align(target->Alignment);
565             // VSFilter compatibility
566             else if (target->Alignment == 8)
567                 target->Alignment = 3;
568             else if (target->Alignment == 4)
569                 target->Alignment = 11;
570             INTVAL(MarginL)
571             INTVAL(MarginR)
572             INTVAL(MarginV)
573             INTVAL(Encoding)
574             FPVAL(ScaleX)
575             FPVAL(ScaleY)
576             FPVAL(Outline)
577             FPVAL(Shadow)
578         PARSE_END
579     }
580     style->ScaleX = FFMAX(style->ScaleX, 0.) / 100.;
581     style->ScaleY = FFMAX(style->ScaleY, 0.) / 100.;
582     style->Spacing = FFMAX(style->Spacing, 0.);
583     style->Outline = FFMAX(style->Outline, 0.);
584     style->Shadow = FFMAX(style->Shadow, 0.);
585     style->Bold = !!style->Bold;
586     style->Italic = !!style->Italic;
587     style->Underline = !!style->Underline;
588     style->StrikeOut = !!style->StrikeOut;
589     if (!style->Name)
590         style->Name = strdup("Default");
591     if (!style->FontName)
592         style->FontName = strdup("Arial");
593     free(format);
594     return 0;
595
596 }
597
598 static int process_styles_line(ASS_Track *track, char *str)
599 {
600     if (!strncmp(str, "Format:", 7)) {
601         char *p = str + 7;
602         skip_spaces(&p);
603         free(track->style_format);
604         track->style_format = strdup(p);
605         ass_msg(track->library, MSGL_DBG2, "Style format: %s",
606                track->style_format);
607     } else if (!strncmp(str, "Style:", 6)) {
608         char *p = str + 6;
609         skip_spaces(&p);
610         process_style(track, p);
611     }
612     return 0;
613 }
614
615 static int process_info_line(ASS_Track *track, char *str)
616 {
617     if (!strncmp(str, "PlayResX:", 9)) {
618         track->PlayResX = atoi(str + 9);
619     } else if (!strncmp(str, "PlayResY:", 9)) {
620         track->PlayResY = atoi(str + 9);
621     } else if (!strncmp(str, "Timer:", 6)) {
622         track->Timer = ass_atof(str + 6);
623     } else if (!strncmp(str, "WrapStyle:", 10)) {
624         track->WrapStyle = atoi(str + 10);
625     } else if (!strncmp(str, "ScaledBorderAndShadow:", 22)) {
626         track->ScaledBorderAndShadow = parse_bool(str + 22);
627     } else if (!strncmp(str, "Kerning:", 8)) {
628         track->Kerning = parse_bool(str + 8);
629     } else if (!strncmp(str, "YCbCr Matrix:", 13)) {
630         track->YCbCrMatrix = parse_ycbcr_matrix(str + 13);
631     } else if (!strncmp(str, "Language:", 9)) {
632         char *p = str + 9;
633         while (*p && ass_isspace(*p)) p++;
634         free(track->Language);
635         track->Language = strndup(p, 2);
636     }
637     return 0;
638 }
639
640 static void event_format_fallback(ASS_Track *track)
641 {
642     track->parser_priv->state = PST_EVENTS;
643     if (track->track_type == TRACK_TYPE_SSA)
644         track->event_format = strdup("Marked, Start, End, Style, "
645             "Name, MarginL, MarginR, MarginV, Effect, Text");
646     else
647         track->event_format = strdup("Layer, Start, End, Style, "
648             "Actor, MarginL, MarginR, MarginV, Effect, Text");
649     ass_msg(track->library, MSGL_V,
650             "No event format found, using fallback");
651 }
652
653 static int process_events_line(ASS_Track *track, char *str)
654 {
655     if (!strncmp(str, "Format:", 7)) {
656         char *p = str + 7;
657         skip_spaces(&p);
658         free(track->event_format);
659         track->event_format = strdup(p);
660         ass_msg(track->library, MSGL_DBG2, "Event format: %s", track->event_format);
661     } else if (!strncmp(str, "Dialogue:", 9)) {
662         // This should never be reached for embedded subtitles.
663         // They have slightly different format and are parsed in ass_process_chunk,
664         // called directly from demuxer
665         int eid;
666         ASS_Event *event;
667
668         str += 9;
669         skip_spaces(&str);
670
671         eid = ass_alloc_event(track);
672         event = track->events + eid;
673
674         // We can't parse events with event_format
675         if (!track->event_format)
676             event_format_fallback(track);
677
678         process_event_tail(track, event, str, 0);
679     } else {
680         ass_msg(track->library, MSGL_V, "Not understood: '%.30s'", str);
681     }
682     return 0;
683 }
684
685 // Copied from mkvtoolnix
686 static unsigned char *decode_chars(unsigned char c1, unsigned char c2,
687                                    unsigned char c3, unsigned char c4,
688                                    unsigned char *dst, int cnt)
689 {
690     uint32_t value;
691     unsigned char bytes[3];
692     int i;
693
694     value =
695         ((c1 - 33) << 18) + ((c2 - 33) << 12) + ((c3 - 33) << 6) + (c4 -
696                                                                     33);
697     bytes[2] = value & 0xff;
698     bytes[1] = (value & 0xff00) >> 8;
699     bytes[0] = (value & 0xff0000) >> 16;
700
701     for (i = 0; i < cnt; ++i)
702         *dst++ = bytes[i];
703     return dst;
704 }
705
706 static int decode_font(ASS_Track *track)
707 {
708     unsigned char *p;
709     unsigned char *q;
710     int i;
711     int size;                   // original size
712     int dsize;                  // decoded size
713     unsigned char *buf = 0;
714
715     ass_msg(track->library, MSGL_V, "Font: %d bytes encoded data",
716             track->parser_priv->fontdata_used);
717     size = track->parser_priv->fontdata_used;
718     if (size % 4 == 1) {
719         ass_msg(track->library, MSGL_ERR, "Bad encoded data size");
720         goto error_decode_font;
721     }
722     buf = malloc(size / 4 * 3 + 2);
723     if (!buf)
724         goto error_decode_font;
725     q = buf;
726     for (i = 0, p = (unsigned char *) track->parser_priv->fontdata;
727          i < size / 4; i++, p += 4) {
728         q = decode_chars(p[0], p[1], p[2], p[3], q, 3);
729     }
730     if (size % 4 == 2) {
731         q = decode_chars(p[0], p[1], 0, 0, q, 1);
732     } else if (size % 4 == 3) {
733         q = decode_chars(p[0], p[1], p[2], 0, q, 2);
734     }
735     dsize = q - buf;
736     assert(dsize <= size / 4 * 3 + 2);
737
738     if (track->library->extract_fonts) {
739         ass_add_font(track->library, track->parser_priv->fontname,
740                      (char *) buf, dsize);
741     }
742
743 error_decode_font:
744     free(buf);
745     free(track->parser_priv->fontname);
746     free(track->parser_priv->fontdata);
747     track->parser_priv->fontname = 0;
748     track->parser_priv->fontdata = 0;
749     track->parser_priv->fontdata_size = 0;
750     track->parser_priv->fontdata_used = 0;
751     return 0;
752 }
753
754 static int process_fonts_line(ASS_Track *track, char *str)
755 {
756     int len;
757
758     if (!strncmp(str, "fontname:", 9)) {
759         char *p = str + 9;
760         skip_spaces(&p);
761         if (track->parser_priv->fontname) {
762             decode_font(track);
763         }
764         track->parser_priv->fontname = strdup(p);
765         ass_msg(track->library, MSGL_V, "Fontname: %s",
766                track->parser_priv->fontname);
767         return 0;
768     }
769
770     if (!track->parser_priv->fontname) {
771         ass_msg(track->library, MSGL_V, "Not understood: '%s'", str);
772         return 0;
773     }
774
775     len = strlen(str);
776     if (len > 80) {
777         ass_msg(track->library, MSGL_WARN, "Font line too long: %d, %s",
778                 len, str);
779         return 0;
780     }
781     if (track->parser_priv->fontdata_used + len >
782         track->parser_priv->fontdata_size) {
783         track->parser_priv->fontdata_size += 100 * 1024;
784         track->parser_priv->fontdata =
785             realloc(track->parser_priv->fontdata,
786                     track->parser_priv->fontdata_size);
787     }
788     memcpy(track->parser_priv->fontdata + track->parser_priv->fontdata_used,
789            str, len);
790     track->parser_priv->fontdata_used += len;
791
792     return 0;
793 }
794
795 /**
796  * \brief Parse a header line
797  * \param track track
798  * \param str string to parse, zero-terminated
799 */
800 static int process_line(ASS_Track *track, char *str)
801 {
802     if (!ass_strncasecmp(str, "[Script Info]", 13)) {
803         track->parser_priv->state = PST_INFO;
804     } else if (!ass_strncasecmp(str, "[V4 Styles]", 11)) {
805         track->parser_priv->state = PST_STYLES;
806         track->track_type = TRACK_TYPE_SSA;
807     } else if (!ass_strncasecmp(str, "[V4+ Styles]", 12)) {
808         track->parser_priv->state = PST_STYLES;
809         track->track_type = TRACK_TYPE_ASS;
810     } else if (!ass_strncasecmp(str, "[Events]", 8)) {
811         track->parser_priv->state = PST_EVENTS;
812     } else if (!ass_strncasecmp(str, "[Fonts]", 7)) {
813         track->parser_priv->state = PST_FONTS;
814     } else {
815         switch (track->parser_priv->state) {
816         case PST_INFO:
817             process_info_line(track, str);
818             break;
819         case PST_STYLES:
820             process_styles_line(track, str);
821             break;
822         case PST_EVENTS:
823             process_events_line(track, str);
824             break;
825         case PST_FONTS:
826             process_fonts_line(track, str);
827             break;
828         default:
829             break;
830         }
831     }
832     return 0;
833 }
834
835 static int process_text(ASS_Track *track, char *str)
836 {
837     char *p = str;
838     while (1) {
839         char *q;
840         while (1) {
841             if ((*p == '\r') || (*p == '\n'))
842                 ++p;
843             else if (p[0] == '\xef' && p[1] == '\xbb' && p[2] == '\xbf')
844                 p += 3;         // U+FFFE (BOM)
845             else
846                 break;
847         }
848         for (q = p; ((*q != '\0') && (*q != '\r') && (*q != '\n')); ++q) {
849         };
850         if (q == p)
851             break;
852         if (*q != '\0')
853             *(q++) = '\0';
854         process_line(track, p);
855         if (*q == '\0')
856             break;
857         p = q;
858     }
859     // there is no explicit end-of-font marker in ssa/ass
860     if (track->parser_priv->fontname)
861         decode_font(track);
862     return 0;
863 }
864
865 /**
866  * \brief Process a chunk of subtitle stream data.
867  * \param track track
868  * \param data string to parse
869  * \param size length of data
870 */
871 void ass_process_data(ASS_Track *track, char *data, int size)
872 {
873     char *str = malloc(size + 1);
874     if (!str)
875         return;
876
877     memcpy(str, data, size);
878     str[size] = '\0';
879
880     ass_msg(track->library, MSGL_V, "Event: %s", str);
881     process_text(track, str);
882     free(str);
883 }
884
885 /**
886  * \brief Process CodecPrivate section of subtitle stream
887  * \param track track
888  * \param data string to parse
889  * \param size length of data
890  CodecPrivate section contains [Stream Info] and [V4+ Styles] ([V4 Styles] for SSA) sections
891 */
892 void ass_process_codec_private(ASS_Track *track, char *data, int size)
893 {
894     ass_process_data(track, data, size);
895
896     // probably an mkv produced by ancient mkvtoolnix
897     // such files don't have [Events] and Format: headers
898     if (!track->event_format)
899         event_format_fallback(track);
900
901     ass_process_force_style(track);
902 }
903
904 static int check_duplicate_event(ASS_Track *track, int ReadOrder)
905 {
906     if (track->parser_priv->read_order_bitmap)
907         return test_and_set_read_order_bit(track, ReadOrder) > 0;
908     // ignoring last event, it is the one we are comparing with
909     for (int i = 0; i < track->n_events - 1; i++)
910         if (track->events[i].ReadOrder == ReadOrder)
911             return 1;
912     return 0;
913 }
914
915 void ass_set_check_readorder(ASS_Track *track, int check_readorder)
916 {
917     track->parser_priv->check_readorder = check_readorder == 1;
918 }
919
920 /**
921  * \brief Process a chunk of subtitle stream data. In Matroska, this contains exactly 1 event (or a commentary).
922  * \param track track
923  * \param data string to parse
924  * \param size length of data
925  * \param timecode starting time of the event (milliseconds)
926  * \param duration duration of the event (milliseconds)
927 */
928 void ass_process_chunk(ASS_Track *track, char *data, int size,
929                        long long timecode, long long duration)
930 {
931     char *str;
932     int eid;
933     char *p;
934     char *token;
935     ASS_Event *event;
936     int check_readorder = track->parser_priv->check_readorder;
937
938     if (check_readorder && !track->parser_priv->read_order_bitmap) {
939         for (int i = 0; i < track->n_events; i++) {
940             if (test_and_set_read_order_bit(track, track->events[i].ReadOrder) < 0)
941                 break;
942         }
943     }
944
945     if (!track->event_format) {
946         ass_msg(track->library, MSGL_WARN, "Event format header missing");
947         return;
948     }
949
950     str = malloc(size + 1);
951     if (!str)
952         return;
953     memcpy(str, data, size);
954     str[size] = '\0';
955     ass_msg(track->library, MSGL_V, "Event at %" PRId64 ", +%" PRId64 ": %s",
956            (int64_t) timecode, (int64_t) duration, str);
957
958     eid = ass_alloc_event(track);
959     event = track->events + eid;
960
961     p = str;
962
963     do {
964         NEXT(p, token);
965         event->ReadOrder = atoi(token);
966         if (check_readorder && check_duplicate_event(track, event->ReadOrder))
967             break;
968
969         NEXT(p, token);
970         event->Layer = atoi(token);
971
972         process_event_tail(track, event, p, 3);
973
974         event->Start = timecode;
975         event->Duration = duration;
976
977         free(str);
978         return;
979 //              dump_events(tid);
980     } while (0);
981     // some error
982     ass_free_event(track, eid);
983     track->n_events--;
984     free(str);
985 }
986
987 /**
988  * \brief Flush buffered events.
989  * \param track track
990 */
991 void ass_flush_events(ASS_Track *track)
992 {
993     if (track->events) {
994         int eid;
995         for (eid = 0; eid < track->n_events; eid++)
996             ass_free_event(track, eid);
997         track->n_events = 0;
998     }
999     free(track->parser_priv->read_order_bitmap);
1000     track->parser_priv->read_order_bitmap = NULL;
1001     track->parser_priv->read_order_elems = 0;
1002 }
1003
1004 #ifdef CONFIG_ICONV
1005 /** \brief recode buffer to utf-8
1006  * constraint: codepage != 0
1007  * \param data pointer to text buffer
1008  * \param size buffer size
1009  * \return a pointer to recoded buffer, caller is responsible for freeing it
1010 **/
1011 static char *sub_recode(ASS_Library *library, char *data, size_t size,
1012                         char *codepage)
1013 {
1014     iconv_t icdsc;
1015     char *tocp = "UTF-8";
1016     char *outbuf;
1017     assert(codepage);
1018
1019     if ((icdsc = iconv_open(tocp, codepage)) != (iconv_t) (-1)) {
1020         ass_msg(library, MSGL_V, "Opened iconv descriptor");
1021     } else {
1022         ass_msg(library, MSGL_ERR, "Error opening iconv descriptor");
1023         return NULL;
1024     }
1025
1026     {
1027         size_t osize = size;
1028         size_t ileft = size;
1029         size_t oleft = size - 1;
1030         char *ip;
1031         char *op;
1032         size_t rc;
1033         int clear = 0;
1034
1035         outbuf = malloc(osize);
1036         if (!outbuf)
1037             goto out;
1038         ip = data;
1039         op = outbuf;
1040
1041         while (1) {
1042             if (ileft)
1043                 rc = iconv(icdsc, &ip, &ileft, &op, &oleft);
1044             else {              // clear the conversion state and leave
1045                 clear = 1;
1046                 rc = iconv(icdsc, NULL, NULL, &op, &oleft);
1047             }
1048             if (rc == (size_t) (-1)) {
1049                 if (errno == E2BIG) {
1050                     size_t offset = op - outbuf;
1051                     char *nbuf = realloc(outbuf, osize + size);
1052                     if (!nbuf) {
1053                         free(outbuf);
1054                         outbuf = 0;
1055                         goto out;
1056                     }
1057                     outbuf = nbuf;
1058                     op = outbuf + offset;
1059                     osize += size;
1060                     oleft += size;
1061                 } else {
1062                     ass_msg(library, MSGL_WARN, "Error recoding file");
1063                     free(outbuf);
1064                     outbuf = NULL;
1065                     goto out;
1066                 }
1067             } else if (clear)
1068                 break;
1069         }
1070         outbuf[osize - oleft - 1] = 0;
1071     }
1072
1073 out:
1074     if (icdsc != (iconv_t) (-1)) {
1075         (void) iconv_close(icdsc);
1076         ass_msg(library, MSGL_V, "Closed iconv descriptor");
1077     }
1078
1079     return outbuf;
1080 }
1081 #endif                          // ICONV
1082
1083 /**
1084  * \brief read file contents into newly allocated buffer
1085  * \param fname file name
1086  * \param bufsize out: file size
1087  * \return pointer to file contents. Caller is responsible for its deallocation.
1088  */
1089 char *read_file(ASS_Library *library, char *fname, size_t *bufsize)
1090 {
1091     int res;
1092     long sz;
1093     long bytes_read;
1094     char *buf;
1095
1096     FILE *fp = fopen(fname, "rb");
1097     if (!fp) {
1098         ass_msg(library, MSGL_WARN,
1099                 "ass_read_file(%s): fopen failed", fname);
1100         return 0;
1101     }
1102     res = fseek(fp, 0, SEEK_END);
1103     if (res == -1) {
1104         ass_msg(library, MSGL_WARN,
1105                 "ass_read_file(%s): fseek failed", fname);
1106         fclose(fp);
1107         return 0;
1108     }
1109
1110     sz = ftell(fp);
1111     rewind(fp);
1112
1113     ass_msg(library, MSGL_V, "File size: %ld", sz);
1114
1115     buf = sz < SIZE_MAX ? malloc(sz + 1) : NULL;
1116     if (!buf) {
1117         fclose(fp);
1118         return NULL;
1119     }
1120     assert(buf);
1121     bytes_read = 0;
1122     do {
1123         res = fread(buf + bytes_read, 1, sz - bytes_read, fp);
1124         if (res <= 0) {
1125             ass_msg(library, MSGL_INFO, "Read failed, %d: %s", errno,
1126                     strerror(errno));
1127             fclose(fp);
1128             free(buf);
1129             return 0;
1130         }
1131         bytes_read += res;
1132     } while (sz - bytes_read > 0);
1133     buf[sz] = '\0';
1134     fclose(fp);
1135
1136     if (bufsize)
1137         *bufsize = sz;
1138     return buf;
1139 }
1140
1141 /*
1142  * \param buf pointer to subtitle text in utf-8
1143  */
1144 static ASS_Track *parse_memory(ASS_Library *library, char *buf)
1145 {
1146     ASS_Track *track;
1147     int i;
1148
1149     track = ass_new_track(library);
1150
1151     // process header
1152     process_text(track, buf);
1153
1154     // external SSA/ASS subs does not have ReadOrder field
1155     for (i = 0; i < track->n_events; ++i)
1156         track->events[i].ReadOrder = i;
1157
1158     if (track->track_type == TRACK_TYPE_UNKNOWN) {
1159         ass_free_track(track);
1160         return 0;
1161     }
1162
1163     ass_process_force_style(track);
1164
1165     return track;
1166 }
1167
1168 /**
1169  * \brief Read subtitles from memory.
1170  * \param library libass library object
1171  * \param buf pointer to subtitles text
1172  * \param bufsize size of buffer
1173  * \param codepage recode buffer contents from given codepage
1174  * \return newly allocated track
1175 */
1176 ASS_Track *ass_read_memory(ASS_Library *library, char *buf,
1177                            size_t bufsize, char *codepage)
1178 {
1179     ASS_Track *track;
1180     int copied = 0;
1181
1182     if (!buf)
1183         return 0;
1184
1185 #ifdef CONFIG_ICONV
1186     if (codepage) {
1187         buf = sub_recode(library, buf, bufsize, codepage);
1188         if (!buf)
1189             return 0;
1190         else
1191             copied = 1;
1192     }
1193 #endif
1194     if (!copied) {
1195         char *newbuf = malloc(bufsize + 1);
1196         if (!newbuf)
1197             return 0;
1198         memcpy(newbuf, buf, bufsize);
1199         newbuf[bufsize] = '\0';
1200         buf = newbuf;
1201     }
1202     track = parse_memory(library, buf);
1203     free(buf);
1204     if (!track)
1205         return 0;
1206
1207     ass_msg(library, MSGL_INFO, "Added subtitle file: "
1208             "<memory> (%d styles, %d events)",
1209             track->n_styles, track->n_events);
1210     return track;
1211 }
1212
1213 static char *read_file_recode(ASS_Library *library, char *fname,
1214                               char *codepage, size_t *size)
1215 {
1216     char *buf;
1217     size_t bufsize;
1218
1219     buf = read_file(library, fname, &bufsize);
1220     if (!buf)
1221         return 0;
1222 #ifdef CONFIG_ICONV
1223     if (codepage) {
1224         char *tmpbuf = sub_recode(library, buf, bufsize, codepage);
1225         free(buf);
1226         buf = tmpbuf;
1227     }
1228     if (!buf)
1229         return 0;
1230 #endif
1231     *size = bufsize;
1232     return buf;
1233 }
1234
1235 /**
1236  * \brief Read subtitles from file.
1237  * \param library libass library object
1238  * \param fname file name
1239  * \param codepage recode buffer contents from given codepage
1240  * \return newly allocated track
1241 */
1242 ASS_Track *ass_read_file(ASS_Library *library, char *fname,
1243                          char *codepage)
1244 {
1245     char *buf;
1246     ASS_Track *track;
1247     size_t bufsize;
1248
1249     buf = read_file_recode(library, fname, codepage, &bufsize);
1250     if (!buf)
1251         return 0;
1252     track = parse_memory(library, buf);
1253     free(buf);
1254     if (!track)
1255         return 0;
1256
1257     track->name = strdup(fname);
1258
1259     ass_msg(library, MSGL_INFO,
1260             "Added subtitle file: '%s' (%d styles, %d events)",
1261             fname, track->n_styles, track->n_events);
1262
1263     return track;
1264 }
1265
1266 /**
1267  * \brief read styles from file into already initialized track
1268  */
1269 int ass_read_styles(ASS_Track *track, char *fname, char *codepage)
1270 {
1271     char *buf;
1272     ParserState old_state;
1273     size_t sz;
1274
1275     buf = read_file(track->library, fname, &sz);
1276     if (!buf)
1277         return 1;
1278 #ifdef CONFIG_ICONV
1279     if (codepage) {
1280         char *tmpbuf;
1281         tmpbuf = sub_recode(track->library, buf, sz, codepage);
1282         free(buf);
1283         buf = tmpbuf;
1284     }
1285     if (!buf)
1286         return 1;
1287 #endif
1288
1289     old_state = track->parser_priv->state;
1290     track->parser_priv->state = PST_STYLES;
1291     process_text(track, buf);
1292     free(buf);
1293     track->parser_priv->state = old_state;
1294
1295     return 0;
1296 }
1297
1298 long long ass_step_sub(ASS_Track *track, long long now, int movement)
1299 {
1300     int i;
1301     ASS_Event *best = NULL;
1302     long long target = now;
1303     int direction = (movement > 0 ? 1 : -1) * !!movement;
1304
1305     if (track->n_events == 0)
1306         return 0;
1307
1308     do {
1309         ASS_Event *closest = NULL;
1310         long long closest_time = now;
1311         for (i = 0; i < track->n_events; i++) {
1312             if (direction < 0) {
1313                 long long end =
1314                     track->events[i].Start + track->events[i].Duration;
1315                 if (end < target) {
1316                     if (!closest || end > closest_time) {
1317                         closest = &track->events[i];
1318                         closest_time = end;
1319                     }
1320                 }
1321             } else if (direction > 0) {
1322                 long long start = track->events[i].Start;
1323                 if (start > target) {
1324                     if (!closest || start < closest_time) {
1325                         closest = &track->events[i];
1326                         closest_time = start;
1327                     }
1328                 }
1329             } else {
1330                 long long start = track->events[i].Start;
1331                 if (start < target) {
1332                     if (!closest || start >= closest_time) {
1333                         closest = &track->events[i];
1334                         closest_time = start;
1335                     }
1336                 }
1337             }
1338         }
1339         target = closest_time + direction;
1340         movement -= direction;
1341         if (closest)
1342             best = closest;
1343     } while (movement);
1344
1345     return best ? best->Start - now : 0;
1346 }
1347
1348 ASS_Track *ass_new_track(ASS_Library *library)
1349 {
1350     ASS_Track *track = calloc(1, sizeof(ASS_Track));
1351     if (!track)
1352         return NULL;
1353     track->library = library;
1354     track->ScaledBorderAndShadow = 1;
1355     track->parser_priv = calloc(1, sizeof(ASS_ParserPriv));
1356     if (!track->parser_priv) {
1357         free(track);
1358         return NULL;
1359     }
1360     track->parser_priv->check_readorder = 1;
1361     return track;
1362 }
1363
1364 /**
1365  * \brief Prepare track for rendering
1366  */
1367 void ass_lazy_track_init(ASS_Library *lib, ASS_Track *track)
1368 {
1369     if (track->PlayResX && track->PlayResY)
1370         return;
1371     if (!track->PlayResX && !track->PlayResY) {
1372         ass_msg(lib, MSGL_WARN,
1373                "Neither PlayResX nor PlayResY defined. Assuming 384x288");
1374         track->PlayResX = 384;
1375         track->PlayResY = 288;
1376     } else {
1377         if (!track->PlayResY && track->PlayResX == 1280) {
1378             track->PlayResY = 1024;
1379             ass_msg(lib, MSGL_WARN,
1380                    "PlayResY undefined, setting to %d", track->PlayResY);
1381         } else if (!track->PlayResY) {
1382             track->PlayResY = track->PlayResX * 3 / 4;
1383             ass_msg(lib, MSGL_WARN,
1384                    "PlayResY undefined, setting to %d", track->PlayResY);
1385         } else if (!track->PlayResX && track->PlayResY == 1024) {
1386             track->PlayResX = 1280;
1387             ass_msg(lib, MSGL_WARN,
1388                    "PlayResX undefined, setting to %d", track->PlayResX);
1389         } else if (!track->PlayResX) {
1390             track->PlayResX = track->PlayResY * 4 / 3;
1391             ass_msg(lib, MSGL_WARN,
1392                    "PlayResX undefined, setting to %d", track->PlayResX);
1393         }
1394     }
1395 }