Mercurial > mplayer.hg
changeset 30200:48d020c5ceca
Update internal libass copy to commit 8db4a5
author | greg |
---|---|
date | Fri, 08 Jan 2010 18:35:44 +0000 |
parents | f9984b2fc1b2 |
children | 786d035899d2 |
files | Makefile libass/ass.c libass/ass.h libass/ass_bitmap.c libass/ass_bitmap.h libass/ass_cache.c libass/ass_cache.h libass/ass_cache_template.h libass/ass_drawing.c libass/ass_drawing.h libass/ass_font.c libass/ass_font.h libass/ass_fontconfig.c libass/ass_fontconfig.h libass/ass_library.c libass/ass_library.h libass/ass_parse.c libass/ass_parse.h libass/ass_render.c libass/ass_render.h libass/ass_strtod.c libass/ass_types.h libass/ass_utils.c libass/ass_utils.h |
diffstat | 24 files changed, 7522 insertions(+), 4537 deletions(-) [+] |
line wrap: on
line diff
--- a/Makefile Fri Jan 08 18:19:10 2010 +0000 +++ b/Makefile Fri Jan 08 18:35:44 2010 +0000 @@ -122,10 +122,13 @@ SRCS_COMMON-$(LIBASS_INTERNAL) += libass/ass.c \ libass/ass_bitmap.c \ libass/ass_cache.c \ + libass/ass_drawing.c \ libass/ass_font.c \ libass/ass_fontconfig.c \ libass/ass_library.c \ + libass/ass_parse.c \ libass/ass_render.c \ + libass/ass_strtod.c \ libass/ass_utils.c \ SRCS_COMMON-$(LIBAVCODEC) += av_opts.c \
--- a/libass/ass.c Fri Jan 08 18:19:10 2010 +0000 +++ b/libass/ass.c Fri Jan 08 18:35:44 2010 +0000 @@ -1,5 +1,3 @@ -// -*- c-basic-offset: 8; indent-tabs-mode: t -*- -// vim:ts=8:sw=8:noet:ai: /* * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com> * @@ -39,117 +37,137 @@ #include "ass.h" #include "ass_utils.h" #include "ass_library.h" -#include "mputils.h" - -typedef enum {PST_UNKNOWN = 0, PST_INFO, PST_STYLES, PST_EVENTS, PST_FONTS} parser_state_t; -struct parser_priv_s { - parser_state_t state; - char* fontname; - char* fontdata; - int fontdata_size; - int fontdata_used; +typedef enum { + PST_UNKNOWN = 0, + PST_INFO, + PST_STYLES, + PST_EVENTS, + PST_FONTS +} ParserState; + +struct parser_priv { + ParserState state; + char *fontname; + char *fontdata; + int fontdata_size; + int fontdata_used; }; #define ASS_STYLES_ALLOC 20 #define ASS_EVENTS_ALLOC 200 -void ass_free_track(ass_track_t* track) { - int i; +void ass_free_track(ASS_Track *track) +{ + int i; - if (track->parser_priv) { - if (track->parser_priv->fontname) - free(track->parser_priv->fontname); - if (track->parser_priv->fontdata) - free(track->parser_priv->fontdata); - free(track->parser_priv); - } - if (track->style_format) - free(track->style_format); - if (track->event_format) - free(track->event_format); - if (track->styles) { - for (i = 0; i < track->n_styles; ++i) - ass_free_style(track, i); - free(track->styles); - } - if (track->events) { - for (i = 0; i < track->n_events; ++i) - ass_free_event(track, i); - free(track->events); - } + if (track->parser_priv) { + if (track->parser_priv->fontname) + free(track->parser_priv->fontname); + if (track->parser_priv->fontdata) + free(track->parser_priv->fontdata); + free(track->parser_priv); + } + if (track->style_format) + free(track->style_format); + if (track->event_format) + free(track->event_format); + if (track->styles) { + for (i = 0; i < track->n_styles; ++i) + ass_free_style(track, i); + free(track->styles); + } + if (track->events) { + for (i = 0; i < track->n_events; ++i) + ass_free_event(track, i); + free(track->events); + } + free(track->name); + free(track); } /// \brief Allocate a new style struct /// \param track track /// \return style id -int ass_alloc_style(ass_track_t* track) { - int sid; +int ass_alloc_style(ASS_Track *track) +{ + int sid; - assert(track->n_styles <= track->max_styles); + assert(track->n_styles <= track->max_styles); - if (track->n_styles == track->max_styles) { - track->max_styles += ASS_STYLES_ALLOC; - track->styles = (ass_style_t*)realloc(track->styles, sizeof(ass_style_t)*track->max_styles); - } + if (track->n_styles == track->max_styles) { + track->max_styles += ASS_STYLES_ALLOC; + track->styles = + (ASS_Style *) realloc(track->styles, + sizeof(ASS_Style) * + track->max_styles); + } - sid = track->n_styles++; - memset(track->styles + sid, 0, sizeof(ass_style_t)); - return sid; + sid = track->n_styles++; + memset(track->styles + sid, 0, sizeof(ASS_Style)); + return sid; } /// \brief Allocate a new event struct /// \param track track /// \return event id -int ass_alloc_event(ass_track_t* track) { - int eid; +int ass_alloc_event(ASS_Track *track) +{ + int eid; - assert(track->n_events <= track->max_events); + assert(track->n_events <= track->max_events); - if (track->n_events == track->max_events) { - track->max_events += ASS_EVENTS_ALLOC; - track->events = (ass_event_t*)realloc(track->events, sizeof(ass_event_t)*track->max_events); - } + if (track->n_events == track->max_events) { + track->max_events += ASS_EVENTS_ALLOC; + track->events = + (ASS_Event *) realloc(track->events, + sizeof(ASS_Event) * + track->max_events); + } - eid = track->n_events++; - memset(track->events + eid, 0, sizeof(ass_event_t)); - return eid; + eid = track->n_events++; + memset(track->events + eid, 0, sizeof(ASS_Event)); + return eid; } -void ass_free_event(ass_track_t* track, int eid) { - ass_event_t* event = track->events + eid; - if (event->Name) - free(event->Name); - if (event->Effect) - free(event->Effect); - if (event->Text) - free(event->Text); - if (event->render_priv) - free(event->render_priv); +void ass_free_event(ASS_Track *track, int eid) +{ + ASS_Event *event = track->events + eid; + if (event->Name) + free(event->Name); + if (event->Effect) + free(event->Effect); + if (event->Text) + free(event->Text); + if (event->render_priv) + free(event->render_priv); } -void ass_free_style(ass_track_t* track, int sid) { - ass_style_t* style = track->styles + sid; - if (style->Name) - free(style->Name); - if (style->FontName) - free(style->FontName); +void ass_free_style(ASS_Track *track, int sid) +{ + ASS_Style *style = track->styles + sid; + if (style->Name) + free(style->Name); + if (style->FontName) + free(style->FontName); } // ============================================================================================== -static void skip_spaces(char** str) { - char* p = *str; - while ((*p==' ') || (*p=='\t')) - ++p; - *str = p; +static void skip_spaces(char **str) +{ + char *p = *str; + while ((*p == ' ') || (*p == '\t')) + ++p; + *str = p; } -static void rskip_spaces(char** str, char* limit) { - char* p = *str; - while ((p >= limit) && ((*p==' ') || (*p=='\t'))) - --p; - *str = p; +static void rskip_spaces(char **str, char *limit) +{ + char *p = *str; + while ((p >= limit) && ((*p == ' ') || (*p == '\t'))) + --p; + *str = p; } /** @@ -160,47 +178,55 @@ * Returnes 0 if no styles found => expects at least 1 style. * Parsing code always adds "Default" style in the end. */ -static int lookup_style(ass_track_t* track, char* name) { - int i; - if (*name == '*') ++name; // FIXME: what does '*' really mean ? - for (i = track->n_styles - 1; i >= 0; --i) { - // FIXME: mb strcasecmp ? - if (strcmp(track->styles[i].Name, name) == 0) - return i; - } - i = track->default_style; - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_NoStyleNamedXFoundUsingY, track, name, track->styles[i].Name); - return i; // use the first style +static int lookup_style(ASS_Track *track, char *name) +{ + int i; + if (*name == '*') + ++name; // FIXME: what does '*' really mean ? + for (i = track->n_styles - 1; i >= 0; --i) { + // FIXME: mb strcasecmp ? + if (strcmp(track->styles[i].Name, name) == 0) + return i; + } + i = track->default_style; + ass_msg(track->library, MSGL_WARN, + "[%p]: Warning: no style named '%s' found, using '%s'", + track, name, track->styles[i].Name); + return i; // use the first style } -static uint32_t string2color(char* p) { - uint32_t tmp; - (void)strtocolor(&p, &tmp); - return tmp; +static uint32_t string2color(ASS_Library *library, char *p) +{ + uint32_t tmp; + (void) strtocolor(library, &p, &tmp, 0); + return tmp; } -static long long string2timecode(char* p) { - unsigned h, m, s, ms; - long long tm; - int res = sscanf(p, "%1d:%2d:%2d.%2d", &h, &m, &s, &ms); - if (res < 4) { - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_BadTimestamp); - return 0; - } - tm = ((h * 60 + m) * 60 + s) * 1000 + ms * 10; - return tm; +static long long string2timecode(ASS_Library *library, char *p) +{ + unsigned h, m, s, ms; + long long tm; + int res = sscanf(p, "%1d:%2d:%2d.%2d", &h, &m, &s, &ms); + if (res < 4) { + ass_msg(library, MSGL_WARN, "Bad timestamp"); + return 0; + } + tm = ((h * 60 + m) * 60 + s) * 1000 + ms * 10; + return tm; } /** * \brief converts numpad-style align to align. */ -static int numpad2align(int val) { - int res, v; - v = (val - 1) / 3; // 0, 1 or 2 for vertical alignment - if (v != 0) v = 3 - v; - res = ((val - 1) % 3) + 1; // horizontal alignment - res += v*4; - return res; +static int numpad2align(int val) +{ + int res, v; + v = (val - 1) / 3; // 0, 1 or 2 for vertical alignment + if (v != 0) + v = 3 - v; + res = ((val - 1) % 3) + 1; // horizontal alignment + res += v * 4; + return res; } #define NEXT(str,token) \ @@ -210,51 +236,62 @@ #define ANYVAL(name,func) \ } else if (strcasecmp(tname, #name) == 0) { \ target->name = func(token); \ - mp_msg(MSGT_ASS, MSGL_DBG2, "%s = %s\n", #name, token); + ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token); #define STRVAL(name) \ } else if (strcasecmp(tname, #name) == 0) { \ if (target->name != NULL) free(target->name); \ target->name = strdup(token); \ - mp_msg(MSGT_ASS, MSGL_DBG2, "%s = %s\n", #name, token); + ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token); -#define COLORVAL(name) ANYVAL(name,string2color) +#define COLORVAL(name) \ + } else if (strcasecmp(tname, #name) == 0) { \ + target->name = string2color(track->library, token); \ + ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token); + #define INTVAL(name) ANYVAL(name,atoi) #define FPVAL(name) ANYVAL(name,atof) -#define TIMEVAL(name) ANYVAL(name,string2timecode) +#define TIMEVAL(name) \ + } else if (strcasecmp(tname, #name) == 0) { \ + target->name = string2timecode(track->library, token); \ + ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token); + #define STYLEVAL(name) \ } else if (strcasecmp(tname, #name) == 0) { \ target->name = lookup_style(track, token); \ - mp_msg(MSGT_ASS, MSGL_DBG2, "%s = %s\n", #name, token); + ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token); #define ALIAS(alias,name) \ if (strcasecmp(tname, #alias) == 0) {tname = #name;} -static char* next_token(char** str) { - char* p = *str; - char* start; - skip_spaces(&p); - if (*p == '\0') { - *str = p; - return 0; - } - start = p; // start of the token - for (; (*p != '\0') && (*p != ','); ++p) {} - if (*p == '\0') { - *str = p; // eos found, str will point to '\0' at exit - } else { - *p = '\0'; - *str = p + 1; // ',' found, str will point to the next char (beginning of the next token) - } - --p; // end of current token - rskip_spaces(&p, start); - if (p < start) - p = start; // empty token - else - ++p; // the first space character, or '\0' - *p = '\0'; - return start; +static char *next_token(char **str) +{ + char *p = *str; + char *start; + skip_spaces(&p); + if (*p == '\0') { + *str = p; + return 0; + } + start = p; // start of the token + for (; (*p != '\0') && (*p != ','); ++p) { + } + if (*p == '\0') { + *str = p; // eos found, str will point to '\0' at exit + } else { + *p = '\0'; + *str = p + 1; // ',' found, str will point to the next char (beginning of the next token) + } + --p; // end of current token + rskip_spaces(&p, start); + if (p < start) + p = start; // empty token + else + ++p; // the first space character, or '\0' + *p = '\0'; + return start; } + /** * \brief Parse the tail of Dialogue line * \param track track @@ -262,68 +299,62 @@ * \param str string to parse, zero-terminated * \param n_ignored number of format options to skip at the beginning */ -static int process_event_tail(ass_track_t* track, ass_event_t* event, char* str, int n_ignored) +static int process_event_tail(ASS_Track *track, ASS_Event *event, + char *str, int n_ignored) { - char* token; - char* tname; - char* p = str; - int i; - ass_event_t* target = event; + char *token; + char *tname; + char *p = str; + int i; + ASS_Event *target = event; - char* format; - char* q; // format scanning pointer + char *format = strdup(track->event_format); + char *q = format; // format scanning pointer - if (!track->event_format) { - track->event_format = strdup("Format: Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text"); - mp_msg(MSGT_ASS, MSGL_V, "Event format is broken, reseting to defaults.\n"); - } - - q = format = strdup(track->event_format); + if (track->n_styles == 0) { + // add "Default" style to the end + // will be used if track does not contain a default style (or even does not contain styles at all) + int sid = ass_alloc_style(track); + track->styles[sid].Name = strdup("Default"); + track->styles[sid].FontName = strdup("Arial"); + } - if (track->n_styles == 0) { - // add "Default" style to the end - // will be used if track does not contain a default style (or even does not contain styles at all) - int sid = ass_alloc_style(track); - track->styles[sid].Name = strdup("Default"); - track->styles[sid].FontName = strdup("Arial"); - } - - for (i = 0; i < n_ignored; ++i) { - NEXT(q, tname); - } + for (i = 0; i < n_ignored; ++i) { + NEXT(q, tname); + } - while (1) { - NEXT(q, tname); - if (strcasecmp(tname, "Text") == 0) { - char* last; - event->Text = strdup(p); - if (*event->Text != 0) { - last = event->Text + strlen(event->Text) - 1; - if (last >= event->Text && *last == '\r') - *last = 0; - } - mp_msg(MSGT_ASS, MSGL_DBG2, "Text = %s\n", event->Text); - event->Duration -= event->Start; - free(format); - return 0; // "Text" is always the last - } - NEXT(p, token); + while (1) { + NEXT(q, tname); + if (strcasecmp(tname, "Text") == 0) { + char *last; + event->Text = strdup(p); + if (*event->Text != 0) { + last = event->Text + strlen(event->Text) - 1; + if (last >= event->Text && *last == '\r') + *last = 0; + } + ass_msg(track->library, MSGL_DBG2, "Text = %s", event->Text); + event->Duration -= event->Start; + free(format); + return 0; // "Text" is always the last + } + NEXT(p, token); - ALIAS(End,Duration) // temporarily store end timecode in event->Duration - if (0) { // cool ;) - INTVAL(Layer) - STYLEVAL(Style) - STRVAL(Name) - STRVAL(Effect) - INTVAL(MarginL) - INTVAL(MarginR) - INTVAL(MarginV) - TIMEVAL(Start) - TIMEVAL(Duration) - } - } - free(format); - return 1; + ALIAS(End, Duration) // temporarily store end timecode in event->Duration + if (0) { // cool ;) + INTVAL(Layer) + STYLEVAL(Style) + STRVAL(Name) + STRVAL(Effect) + INTVAL(MarginL) + INTVAL(MarginR) + INTVAL(MarginV) + TIMEVAL(Start) + TIMEVAL(Duration) + } + } + free(format); + return 1; } /** @@ -331,73 +362,79 @@ * \param track track to apply overrides to * The format for overrides is [StyleName.]Field=Value */ -void process_force_style(ass_track_t* track) { - char **fs, *eq, *dt, *style, *tname, *token; - ass_style_t* target; - int sid; - char** list = track->library->style_overrides; +void ass_process_force_style(ASS_Track *track) +{ + char **fs, *eq, *dt, *style, *tname, *token; + ASS_Style *target; + int sid; + char **list = track->library->style_overrides; - if (!list) return; + if (!list) + return; - for (fs = list; *fs; ++fs) { - eq = strrchr(*fs, '='); - if (!eq) - continue; - *eq = '\0'; - token = eq + 1; + for (fs = list; *fs; ++fs) { + eq = strrchr(*fs, '='); + if (!eq) + continue; + *eq = '\0'; + token = eq + 1; - if(!strcasecmp(*fs, "PlayResX")) - track->PlayResX = atoi(token); - else if(!strcasecmp(*fs, "PlayResY")) - track->PlayResY = atoi(token); - else if(!strcasecmp(*fs, "Timer")) - track->Timer = atof(token); - else if(!strcasecmp(*fs, "WrapStyle")) - track->WrapStyle = atoi(token); - else if(!strcasecmp(*fs, "ScaledBorderAndShadow")) - track->ScaledBorderAndShadow = parse_bool(token); + if (!strcasecmp(*fs, "PlayResX")) + track->PlayResX = atoi(token); + else if (!strcasecmp(*fs, "PlayResY")) + track->PlayResY = atoi(token); + else if (!strcasecmp(*fs, "Timer")) + track->Timer = atof(token); + else if (!strcasecmp(*fs, "WrapStyle")) + track->WrapStyle = atoi(token); + else if (!strcasecmp(*fs, "ScaledBorderAndShadow")) + track->ScaledBorderAndShadow = parse_bool(token); + else if (!strcasecmp(*fs, "Kerning")) + track->Kerning = parse_bool(token); - dt = strrchr(*fs, '.'); - if (dt) { - *dt = '\0'; - style = *fs; - tname = dt + 1; - } else { - style = NULL; - tname = *fs; - } - for (sid = 0; sid < track->n_styles; ++sid) { - if (style == NULL || strcasecmp(track->styles[sid].Name, style) == 0) { - target = track->styles + sid; - if (0) { - STRVAL(FontName) - COLORVAL(PrimaryColour) - COLORVAL(SecondaryColour) - COLORVAL(OutlineColour) - COLORVAL(BackColour) - FPVAL(FontSize) - INTVAL(Bold) - INTVAL(Italic) - INTVAL(Underline) - INTVAL(StrikeOut) - FPVAL(Spacing) - INTVAL(Angle) - INTVAL(BorderStyle) - INTVAL(Alignment) - INTVAL(MarginL) - INTVAL(MarginR) - INTVAL(MarginV) - INTVAL(Encoding) - FPVAL(ScaleX) - FPVAL(ScaleY) - FPVAL(Outline) - FPVAL(Shadow) - } - } - } - *eq = '='; - if (dt) *dt = '.'; - } + dt = strrchr(*fs, '.'); + if (dt) { + *dt = '\0'; + style = *fs; + tname = dt + 1; + } else { + style = NULL; + tname = *fs; + } + for (sid = 0; sid < track->n_styles; ++sid) { + if (style == NULL + || strcasecmp(track->styles[sid].Name, style) == 0) { + target = track->styles + sid; + if (0) { + STRVAL(FontName) + COLORVAL(PrimaryColour) + COLORVAL(SecondaryColour) + COLORVAL(OutlineColour) + COLORVAL(BackColour) + FPVAL(FontSize) + INTVAL(Bold) + INTVAL(Italic) + INTVAL(Underline) + INTVAL(StrikeOut) + FPVAL(Spacing) + INTVAL(Angle) + INTVAL(BorderStyle) + INTVAL(Alignment) + INTVAL(MarginL) + INTVAL(MarginR) + INTVAL(MarginV) + INTVAL(Encoding) + FPVAL(ScaleX) + FPVAL(ScaleY) + FPVAL(Outline) + FPVAL(Shadow) + } + } + } + *eq = '='; + if (dt) + *dt = '.'; + } } /** @@ -406,257 +443,294 @@ * \param str string to parse, zero-terminated * Allocates a new style struct. */ -static int process_style(ass_track_t* track, char *str) +static int process_style(ASS_Track *track, char *str) { - char* token; - char* tname; - char* p = str; - char* format; - char* q; // format scanning pointer - int sid; - ass_style_t* style; - ass_style_t* target; + char *token; + char *tname; + char *p = str; + char *format; + char *q; // format scanning pointer + int sid; + ASS_Style *style; + ASS_Style *target; - if (!track->style_format) { - // no style format header - // probably an ancient script version - if (track->track_type == TRACK_TYPE_SSA) - track->style_format = strdup("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour," - "TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline," - "Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding"); - else - track->style_format = strdup("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour," - "OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut," - "ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow," - "Alignment, MarginL, MarginR, MarginV, Encoding"); - } - - q = format = strdup(track->style_format); + if (!track->style_format) { + // no style format header + // probably an ancient script version + if (track->track_type == TRACK_TYPE_SSA) + track->style_format = + strdup + ("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour," + "TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline," + "Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding"); + else + track->style_format = + strdup + ("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour," + "OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut," + "ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow," + "Alignment, MarginL, MarginR, MarginV, Encoding"); + } - mp_msg(MSGT_ASS, MSGL_V, "[%p] Style: %s\n", track, str); + q = format = strdup(track->style_format); - sid = ass_alloc_style(track); + ass_msg(track->library, MSGL_V, "[%p] Style: %s", track, str); + + sid = ass_alloc_style(track); - style = track->styles + sid; - target = style; -// fill style with some default values - style->ScaleX = 100.; - style->ScaleY = 100.; + style = track->styles + sid; + target = style; - while (1) { - NEXT(q, tname); - NEXT(p, token); + // fill style with some default values + style->ScaleX = 100.; + style->ScaleY = 100.; -// ALIAS(TertiaryColour,OutlineColour) // ignore TertiaryColour; it appears only in SSA, and is overridden by BackColour + while (1) { + NEXT(q, tname); + NEXT(p, token); - if (0) { // cool ;) - STRVAL(Name) - if ((strcmp(target->Name, "Default")==0) || (strcmp(target->Name, "*Default")==0)) - track->default_style = sid; - STRVAL(FontName) - COLORVAL(PrimaryColour) - COLORVAL(SecondaryColour) - COLORVAL(OutlineColour) // TertiaryColor - COLORVAL(BackColour) - // SSA uses BackColour for both outline and shadow - // this will destroy SSA's TertiaryColour, but i'm not going to use it anyway - if (track->track_type == TRACK_TYPE_SSA) - target->OutlineColour = target->BackColour; - FPVAL(FontSize) - INTVAL(Bold) - INTVAL(Italic) - INTVAL(Underline) - INTVAL(StrikeOut) - FPVAL(Spacing) - INTVAL(Angle) - INTVAL(BorderStyle) - INTVAL(Alignment) - if (track->track_type == TRACK_TYPE_ASS) - target->Alignment = numpad2align(target->Alignment); - INTVAL(MarginL) - INTVAL(MarginR) - INTVAL(MarginV) - INTVAL(Encoding) - FPVAL(ScaleX) - FPVAL(ScaleY) - FPVAL(Outline) - FPVAL(Shadow) - } - } - style->ScaleX /= 100.; - style->ScaleY /= 100.; - style->Bold = !!style->Bold; - style->Italic = !!style->Italic; - style->Underline = !!style->Underline; - if (!style->Name) - style->Name = strdup("Default"); - if (!style->FontName) - style->FontName = strdup("Arial"); - // skip '@' at the start of the font name - if (*style->FontName == '@') { - p = style->FontName; - style->FontName = strdup(p + 1); - free(p); - } - free(format); - return 0; + if (0) { // cool ;) + STRVAL(Name) + if ((strcmp(target->Name, "Default") == 0) + || (strcmp(target->Name, "*Default") == 0)) + track->default_style = sid; + STRVAL(FontName) + COLORVAL(PrimaryColour) + COLORVAL(SecondaryColour) + COLORVAL(OutlineColour) // TertiaryColor + COLORVAL(BackColour) + // SSA uses BackColour for both outline and shadow + // this will destroy SSA's TertiaryColour, but i'm not going to use it anyway + if (track->track_type == TRACK_TYPE_SSA) + target->OutlineColour = target->BackColour; + FPVAL(FontSize) + INTVAL(Bold) + INTVAL(Italic) + INTVAL(Underline) + INTVAL(StrikeOut) + FPVAL(Spacing) + INTVAL(Angle) + INTVAL(BorderStyle) + INTVAL(Alignment) + if (track->track_type == TRACK_TYPE_ASS) + target->Alignment = numpad2align(target->Alignment); + INTVAL(MarginL) + INTVAL(MarginR) + INTVAL(MarginV) + INTVAL(Encoding) + FPVAL(ScaleX) + FPVAL(ScaleY) + FPVAL(Outline) + FPVAL(Shadow) + } + } + style->ScaleX /= 100.; + style->ScaleY /= 100.; + style->Bold = !!style->Bold; + style->Italic = !!style->Italic; + style->Underline = !!style->Underline; + if (!style->Name) + style->Name = strdup("Default"); + if (!style->FontName) + style->FontName = strdup("Arial"); + // skip '@' at the start of the font name + if (*style->FontName == '@') { + p = style->FontName; + style->FontName = strdup(p + 1); + free(p); + } + free(format); + return 0; } -static int process_styles_line(ass_track_t* track, char *str) +static int process_styles_line(ASS_Track *track, char *str) { - if (!strncmp(str,"Format:", 7)) { - char* p = str + 7; - skip_spaces(&p); - track->style_format = strdup(p); - mp_msg(MSGT_ASS, MSGL_DBG2, "Style format: %s\n", track->style_format); - } else if (!strncmp(str,"Style:", 6)) { - char* p = str + 6; - skip_spaces(&p); - process_style(track, p); - } - return 0; + if (!strncmp(str, "Format:", 7)) { + char *p = str + 7; + skip_spaces(&p); + track->style_format = strdup(p); + ass_msg(track->library, MSGL_DBG2, "Style format: %s", + track->style_format); + } else if (!strncmp(str, "Style:", 6)) { + char *p = str + 6; + skip_spaces(&p); + process_style(track, p); + } + return 0; +} + +static int process_info_line(ASS_Track *track, char *str) +{ + if (!strncmp(str, "PlayResX:", 9)) { + track->PlayResX = atoi(str + 9); + } else if (!strncmp(str, "PlayResY:", 9)) { + track->PlayResY = atoi(str + 9); + } else if (!strncmp(str, "Timer:", 6)) { + track->Timer = atof(str + 6); + } else if (!strncmp(str, "WrapStyle:", 10)) { + track->WrapStyle = atoi(str + 10); + } else if (!strncmp(str, "ScaledBorderAndShadow:", 22)) { + track->ScaledBorderAndShadow = parse_bool(str + 22); + } else if (!strncmp(str, "Kerning:", 8)) { + track->Kerning = parse_bool(str + 8); + } + return 0; } -static int process_info_line(ass_track_t* track, char *str) +static void event_format_fallback(ASS_Track *track) { - if (!strncmp(str, "PlayResX:", 9)) { - track->PlayResX = atoi(str + 9); - } else if (!strncmp(str,"PlayResY:", 9)) { - track->PlayResY = atoi(str + 9); - } else if (!strncmp(str,"Timer:", 6)) { - track->Timer = atof(str + 6); - } else if (!strncmp(str,"WrapStyle:", 10)) { - track->WrapStyle = atoi(str + 10); - } else if (!strncmp(str, "ScaledBorderAndShadow:", 22)) { - track->ScaledBorderAndShadow = parse_bool(str + 22); - } - return 0; + track->parser_priv->state = PST_EVENTS; + if (track->track_type == TRACK_TYPE_SSA) + track->event_format = strdup("Format: Marked, Start, End, Style, " + "Name, MarginL, MarginR, MarginV, Effect, Text"); + else + track->event_format = strdup("Format: Layer, Start, End, Style, " + "Actor, MarginL, MarginR, MarginV, Effect, Text"); + ass_msg(track->library, MSGL_V, + "No event format found, using fallback"); } -static int process_events_line(ass_track_t* track, char *str) +static int process_events_line(ASS_Track *track, char *str) { - if (!strncmp(str, "Format:", 7)) { - char* p = str + 7; - skip_spaces(&p); - track->event_format = strdup(p); - mp_msg(MSGT_ASS, MSGL_DBG2, "Event format: %s\n", track->event_format); - } else if (!strncmp(str, "Dialogue:", 9)) { - // This should never be reached for embedded subtitles. - // They have slightly different format and are parsed in ass_process_chunk, - // called directly from demuxer - int eid; - ass_event_t* event; + if (!strncmp(str, "Format:", 7)) { + char *p = str + 7; + skip_spaces(&p); + track->event_format = strdup(p); + ass_msg(track->library, MSGL_DBG2, "Event format: %s", track->event_format); + } else if (!strncmp(str, "Dialogue:", 9)) { + // This should never be reached for embedded subtitles. + // They have slightly different format and are parsed in ass_process_chunk, + // called directly from demuxer + int eid; + ASS_Event *event; - str += 9; - skip_spaces(&str); + str += 9; + skip_spaces(&str); - eid = ass_alloc_event(track); - event = track->events + eid; + eid = ass_alloc_event(track); + event = track->events + eid; - process_event_tail(track, event, str, 0); - } else { - mp_msg(MSGT_ASS, MSGL_V, "Not understood: %s \n", str); - } - return 0; + // We can't parse events with event_format + if (!track->event_format) + event_format_fallback(track); + + process_event_tail(track, event, str, 0); + } else { + ass_msg(track->library, MSGL_V, "Not understood: '%s'", str); + } + return 0; } // Copied from mkvtoolnix -static unsigned char* decode_chars(unsigned char c1, unsigned char c2, - unsigned char c3, unsigned char c4, unsigned char* dst, int cnt) +static unsigned char *decode_chars(unsigned char c1, unsigned char c2, + unsigned char c3, unsigned char c4, + unsigned char *dst, int cnt) { - uint32_t value; - unsigned char bytes[3]; - int i; + uint32_t value; + unsigned char bytes[3]; + int i; - value = ((c1 - 33) << 18) + ((c2 - 33) << 12) + ((c3 - 33) << 6) + (c4 - 33); - bytes[2] = value & 0xff; - bytes[1] = (value & 0xff00) >> 8; - bytes[0] = (value & 0xff0000) >> 16; + value = + ((c1 - 33) << 18) + ((c2 - 33) << 12) + ((c3 - 33) << 6) + (c4 - + 33); + bytes[2] = value & 0xff; + bytes[1] = (value & 0xff00) >> 8; + bytes[0] = (value & 0xff0000) >> 16; - for (i = 0; i < cnt; ++i) - *dst++ = bytes[i]; - return dst; + for (i = 0; i < cnt; ++i) + *dst++ = bytes[i]; + return dst; } -static int decode_font(ass_track_t* track) +static int decode_font(ASS_Track *track) { - unsigned char* p; - unsigned char* q; - int i; - int size; // original size - int dsize; // decoded size - unsigned char* buf = 0; + unsigned char *p; + unsigned char *q; + int i; + int size; // original size + int dsize; // decoded size + unsigned char *buf = 0; - mp_msg(MSGT_ASS, MSGL_V, "font: %d bytes encoded data \n", track->parser_priv->fontdata_used); - size = track->parser_priv->fontdata_used; - if (size % 4 == 1) { - mp_msg(MSGT_ASS, MSGL_ERR, MSGTR_LIBASS_BadEncodedDataSize); - goto error_decode_font; - } - buf = malloc(size / 4 * 3 + 2); - q = buf; - for (i = 0, p = (unsigned char*)track->parser_priv->fontdata; i < size / 4; i++, p+=4) { - q = decode_chars(p[0], p[1], p[2], p[3], q, 3); - } - if (size % 4 == 2) { - q = decode_chars(p[0], p[1], 0, 0, q, 1); - } else if (size % 4 == 3) { - q = decode_chars(p[0], p[1], p[2], 0, q, 2); - } - dsize = q - buf; - assert(dsize <= size / 4 * 3 + 2); + ass_msg(track->library, MSGL_V, "Font: %d bytes encoded data", + track->parser_priv->fontdata_used); + size = track->parser_priv->fontdata_used; + if (size % 4 == 1) { + ass_msg(track->library, MSGL_ERR, "Bad encoded data size"); + goto error_decode_font; + } + buf = malloc(size / 4 * 3 + 2); + q = buf; + for (i = 0, p = (unsigned char *) track->parser_priv->fontdata; + i < size / 4; i++, p += 4) { + q = decode_chars(p[0], p[1], p[2], p[3], q, 3); + } + if (size % 4 == 2) { + q = decode_chars(p[0], p[1], 0, 0, q, 1); + } else if (size % 4 == 3) { + q = decode_chars(p[0], p[1], p[2], 0, q, 2); + } + dsize = q - buf; + assert(dsize <= size / 4 * 3 + 2); - if (track->library->extract_fonts) { - ass_add_font(track->library, track->parser_priv->fontname, (char*)buf, dsize); - buf = 0; - } + if (track->library->extract_fonts) { + ass_add_font(track->library, track->parser_priv->fontname, + (char *) buf, dsize); + buf = 0; + } -error_decode_font: - if (buf) free(buf); - free(track->parser_priv->fontname); - free(track->parser_priv->fontdata); - track->parser_priv->fontname = 0; - track->parser_priv->fontdata = 0; - track->parser_priv->fontdata_size = 0; - track->parser_priv->fontdata_used = 0; - return 0; + error_decode_font: + if (buf) + free(buf); + free(track->parser_priv->fontname); + free(track->parser_priv->fontdata); + track->parser_priv->fontname = 0; + track->parser_priv->fontdata = 0; + track->parser_priv->fontdata_size = 0; + track->parser_priv->fontdata_used = 0; + return 0; } -static int process_fonts_line(ass_track_t* track, char *str) +static int process_fonts_line(ASS_Track *track, char *str) { - int len; + int len; - if (!strncmp(str, "fontname:", 9)) { - char* p = str + 9; - skip_spaces(&p); - if (track->parser_priv->fontname) { - decode_font(track); - } - track->parser_priv->fontname = strdup(p); - mp_msg(MSGT_ASS, MSGL_V, "fontname: %s\n", track->parser_priv->fontname); - return 0; - } + if (!strncmp(str, "fontname:", 9)) { + char *p = str + 9; + skip_spaces(&p); + if (track->parser_priv->fontname) { + decode_font(track); + } + track->parser_priv->fontname = strdup(p); + ass_msg(track->library, MSGL_V, "Fontname: %s", + track->parser_priv->fontname); + return 0; + } - if (!track->parser_priv->fontname) { - mp_msg(MSGT_ASS, MSGL_V, "Not understood: %s \n", str); - return 0; - } + if (!track->parser_priv->fontname) { + ass_msg(track->library, MSGL_V, "Not understood: '%s'", str); + return 0; + } - len = strlen(str); - if (len > 80) { - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FontLineTooLong, len, str); - return 0; - } - if (track->parser_priv->fontdata_used + len > track->parser_priv->fontdata_size) { - track->parser_priv->fontdata_size += 100 * 1024; - track->parser_priv->fontdata = realloc(track->parser_priv->fontdata, track->parser_priv->fontdata_size); - } - memcpy(track->parser_priv->fontdata + track->parser_priv->fontdata_used, str, len); - track->parser_priv->fontdata_used += len; + len = strlen(str); + if (len > 80) { + ass_msg(track->library, MSGL_WARN, "Font line too long: %d, %s", + len, str); + return 0; + } + if (track->parser_priv->fontdata_used + len > + track->parser_priv->fontdata_size) { + track->parser_priv->fontdata_size += 100 * 1024; + track->parser_priv->fontdata = + realloc(track->parser_priv->fontdata, + track->parser_priv->fontdata_size); + } + memcpy(track->parser_priv->fontdata + track->parser_priv->fontdata_used, + str, len); + track->parser_priv->fontdata_used += len; - return 0; + return 0; } /** @@ -664,67 +738,72 @@ * \param track track * \param str string to parse, zero-terminated */ -static int process_line(ass_track_t* track, char *str) +static int process_line(ASS_Track *track, char *str) { - if (!strncasecmp(str, "[Script Info]", 13)) { - track->parser_priv->state = PST_INFO; - } else if (!strncasecmp(str, "[V4 Styles]", 11)) { - track->parser_priv->state = PST_STYLES; - track->track_type = TRACK_TYPE_SSA; - } else if (!strncasecmp(str, "[V4+ Styles]", 12)) { - track->parser_priv->state = PST_STYLES; - track->track_type = TRACK_TYPE_ASS; - } else if (!strncasecmp(str, "[Events]", 8)) { - track->parser_priv->state = PST_EVENTS; - } else if (!strncasecmp(str, "[Fonts]", 7)) { - track->parser_priv->state = PST_FONTS; - } else { - switch (track->parser_priv->state) { - case PST_INFO: - process_info_line(track, str); - break; - case PST_STYLES: - process_styles_line(track, str); - break; - case PST_EVENTS: - process_events_line(track, str); - break; - case PST_FONTS: - process_fonts_line(track, str); - break; - default: - break; - } - } + if (!strncasecmp(str, "[Script Info]", 13)) { + track->parser_priv->state = PST_INFO; + } else if (!strncasecmp(str, "[V4 Styles]", 11)) { + track->parser_priv->state = PST_STYLES; + track->track_type = TRACK_TYPE_SSA; + } else if (!strncasecmp(str, "[V4+ Styles]", 12)) { + track->parser_priv->state = PST_STYLES; + track->track_type = TRACK_TYPE_ASS; + } else if (!strncasecmp(str, "[Events]", 8)) { + track->parser_priv->state = PST_EVENTS; + } else if (!strncasecmp(str, "[Fonts]", 7)) { + track->parser_priv->state = PST_FONTS; + } else { + switch (track->parser_priv->state) { + case PST_INFO: + process_info_line(track, str); + break; + case PST_STYLES: + process_styles_line(track, str); + break; + case PST_EVENTS: + process_events_line(track, str); + break; + case PST_FONTS: + process_fonts_line(track, str); + break; + default: + break; + } + } - // there is no explicit end-of-font marker in ssa/ass - if ((track->parser_priv->state != PST_FONTS) && (track->parser_priv->fontname)) - decode_font(track); + // there is no explicit end-of-font marker in ssa/ass + if ((track->parser_priv->state != PST_FONTS) + && (track->parser_priv->fontname)) + decode_font(track); - return 0; + return 0; } -static int process_text(ass_track_t* track, char* str) +static int process_text(ASS_Track *track, char *str) { - char* p = str; - while(1) { - char* q; - while (1) { - if ((*p=='\r')||(*p=='\n')) ++p; - else if (p[0]=='\xef' && p[1]=='\xbb' && p[2]=='\xbf') p+=3; // U+FFFE (BOM) - else break; - } - for (q=p; ((*q!='\0')&&(*q!='\r')&&(*q!='\n')); ++q) {}; - if (q==p) - break; - if (*q != '\0') - *(q++) = '\0'; - process_line(track, p); - if (*q == '\0') - break; - p = q; - } - return 0; + char *p = str; + while (1) { + char *q; + while (1) { + if ((*p == '\r') || (*p == '\n')) + ++p; + else if (p[0] == '\xef' && p[1] == '\xbb' && p[2] == '\xbf') + p += 3; // U+FFFE (BOM) + else + break; + } + for (q = p; ((*q != '\0') && (*q != '\r') && (*q != '\n')); ++q) { + }; + if (q == p) + break; + if (*q != '\0') + *(q++) = '\0'; + process_line(track, p); + if (*q == '\0') + break; + p = q; + } + return 0; } /** @@ -733,16 +812,16 @@ * \param data string to parse * \param size length of data */ -void ass_process_data(ass_track_t* track, char* data, int size) +void ass_process_data(ASS_Track *track, char *data, int size) { - char* str = malloc(size + 1); + char *str = malloc(size + 1); - memcpy(str, data, size); - str[size] = '\0'; + memcpy(str, data, size); + str[size] = '\0'; - mp_msg(MSGT_ASS, MSGL_V, "event: %s\n", str); - process_text(track, str); - free(str); + ass_msg(track->library, MSGL_V, "Event: %s", str); + process_text(track, str); + free(str); } /** @@ -752,30 +831,25 @@ * \param size length of data CodecPrivate section contains [Stream Info] and [V4+ Styles] ([V4 Styles] for SSA) sections */ -void ass_process_codec_private(ass_track_t* track, char *data, int size) +void ass_process_codec_private(ASS_Track *track, char *data, int size) { - ass_process_data(track, data, size); + ass_process_data(track, data, size); - if (!track->event_format) { - // probably an mkv produced by ancient mkvtoolnix - // such files don't have [Events] and Format: headers - track->parser_priv->state = PST_EVENTS; - if (track->track_type == TRACK_TYPE_SSA) - track->event_format = strdup("Format: Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text"); - else - track->event_format = strdup("Format: Layer, Start, End, Style, Actor, MarginL, MarginR, MarginV, Effect, Text"); - } + // probably an mkv produced by ancient mkvtoolnix + // such files don't have [Events] and Format: headers + if (!track->event_format) + event_format_fallback(track); - process_force_style(track); + ass_process_force_style(track); } -static int check_duplicate_event(ass_track_t* track, int ReadOrder) +static int check_duplicate_event(ASS_Track *track, int ReadOrder) { - int i; - for (i = 0; i<track->n_events - 1; ++i) // ignoring last event, it is the one we are comparing with - if (track->events[i].ReadOrder == ReadOrder) - return 1; - return 0; + int i; + for (i = 0; i < track->n_events - 1; ++i) // ignoring last event, it is the one we are comparing with + if (track->events[i].ReadOrder == ReadOrder) + return 1; + return 0; } /** @@ -786,51 +860,53 @@ * \param timecode starting time of the event (milliseconds) * \param duration duration of the event (milliseconds) */ -void ass_process_chunk(ass_track_t* track, char *data, int size, long long timecode, long long duration) +void ass_process_chunk(ASS_Track *track, char *data, int size, + long long timecode, long long duration) { - char* str; - int eid; - char* p; - char* token; - ass_event_t* event; + char *str; + int eid; + char *p; + char *token; + ASS_Event *event; - if (!track->event_format) { - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_EventFormatHeaderMissing); - return; - } + if (!track->event_format) { + ass_msg(track->library, MSGL_WARN, "Event format header missing"); + return; + } - str = malloc(size + 1); - memcpy(str, data, size); - str[size] = '\0'; - mp_msg(MSGT_ASS, MSGL_V, "event at %" PRId64 ", +%" PRId64 ": %s \n", (int64_t)timecode, (int64_t)duration, str); + str = malloc(size + 1); + memcpy(str, data, size); + str[size] = '\0'; + ass_msg(track->library, MSGL_V, "Event at %" PRId64 ", +%" PRId64 ": %s", + (int64_t) timecode, (int64_t) duration, str); - eid = ass_alloc_event(track); - event = track->events + eid; - - p = str; + eid = ass_alloc_event(track); + event = track->events + eid; - do { - NEXT(p, token); - event->ReadOrder = atoi(token); - if (check_duplicate_event(track, event->ReadOrder)) - break; + p = str; - NEXT(p, token); - event->Layer = atoi(token); + do { + NEXT(p, token); + event->ReadOrder = atoi(token); + if (check_duplicate_event(track, event->ReadOrder)) + break; - process_event_tail(track, event, p, 3); + NEXT(p, token); + event->Layer = atoi(token); - event->Start = timecode; - event->Duration = duration; + process_event_tail(track, event, p, 3); + + event->Start = timecode; + event->Duration = duration; - free(str); - return; -// dump_events(tid); - } while (0); - // some error - ass_free_event(track, eid); - track->n_events--; - free(str); + free(str); + return; +// dump_events(tid); + } while (0); + // some error + ass_free_event(track, eid); + track->n_events--; + free(str); } #ifdef CONFIG_ICONV @@ -840,75 +916,78 @@ * \param size buffer size * \return a pointer to recoded buffer, caller is responsible for freeing it **/ -static char* sub_recode(char* data, size_t size, char* codepage) +static char *sub_recode(ASS_Library *library, char *data, size_t size, + char *codepage) { - static iconv_t icdsc = (iconv_t)(-1); - char* tocp = "UTF-8"; - char* outbuf; - assert(codepage); + iconv_t icdsc; + char *tocp = "UTF-8"; + char *outbuf; + assert(codepage); - { - const char* cp_tmp = codepage; + { + const char *cp_tmp = codepage; #ifdef CONFIG_ENCA - char enca_lang[3], enca_fallback[100]; - if (sscanf(codepage, "enca:%2s:%99s", enca_lang, enca_fallback) == 2 - || sscanf(codepage, "ENCA:%2s:%99s", enca_lang, enca_fallback) == 2) { - cp_tmp = guess_buffer_cp((unsigned char*)data, size, enca_lang, enca_fallback); - } + char enca_lang[3], enca_fallback[100]; + if (sscanf(codepage, "enca:%2s:%99s", enca_lang, enca_fallback) == 2 + || sscanf(codepage, "ENCA:%2s:%99s", enca_lang, + enca_fallback) == 2) { + cp_tmp = + ass_guess_buffer_cp(library, (unsigned char *) data, size, + enca_lang, enca_fallback); + } #endif - if ((icdsc = iconv_open (tocp, cp_tmp)) != (iconv_t)(-1)){ - mp_msg(MSGT_ASS,MSGL_V,"LIBSUB: opened iconv descriptor.\n"); - } else - mp_msg(MSGT_ASS,MSGL_ERR,MSGTR_LIBASS_ErrorOpeningIconvDescriptor); - } + if ((icdsc = iconv_open(tocp, cp_tmp)) != (iconv_t) (-1)) { + ass_msg(library, MSGL_V, "Opened iconv descriptor"); + } else + ass_msg(library, MSGL_ERR, "Error opening iconv descriptor"); + } - { - size_t osize = size; - size_t ileft = size; - size_t oleft = size - 1; - char* ip; - char* op; - size_t rc; - int clear = 0; - - outbuf = malloc(osize); - ip = data; - op = outbuf; + { + size_t osize = size; + size_t ileft = size; + size_t oleft = size - 1; + char *ip; + char *op; + size_t rc; + int clear = 0; - while (1) { - if (ileft) - rc = iconv(icdsc, &ip, &ileft, &op, &oleft); - else {// clear the conversion state and leave - clear = 1; - rc = iconv(icdsc, NULL, NULL, &op, &oleft); - } - if (rc == (size_t)(-1)) { - if (errno == E2BIG) { - size_t offset = op - outbuf; - outbuf = (char*)realloc(outbuf, osize + size); - op = outbuf + offset; - osize += size; - oleft += size; - } else { - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_ErrorRecodingFile); - return NULL; - } - } else - if (clear) - break; - } - outbuf[osize - oleft - 1] = 0; - } + outbuf = malloc(osize); + ip = data; + op = outbuf; - if (icdsc != (iconv_t)(-1)) { - (void)iconv_close(icdsc); - icdsc = (iconv_t)(-1); - mp_msg(MSGT_ASS,MSGL_V,"LIBSUB: closed iconv descriptor.\n"); - } + while (1) { + if (ileft) + rc = iconv(icdsc, &ip, &ileft, &op, &oleft); + else { // clear the conversion state and leave + clear = 1; + rc = iconv(icdsc, NULL, NULL, &op, &oleft); + } + if (rc == (size_t) (-1)) { + if (errno == E2BIG) { + size_t offset = op - outbuf; + outbuf = (char *) realloc(outbuf, osize + size); + op = outbuf + offset; + osize += size; + oleft += size; + } else { + ass_msg(library, MSGL_WARN, "Error recoding file"); + return NULL; + } + } else if (clear) + break; + } + outbuf[osize - oleft - 1] = 0; + } - return outbuf; + if (icdsc != (iconv_t) (-1)) { + (void) iconv_close(icdsc); + icdsc = (iconv_t) (-1); + ass_msg(library, MSGL_V, "Closed iconv descriptor"); + } + + return outbuf; } -#endif // ICONV +#endif // ICONV /** * \brief read file contents into newly allocated buffer @@ -916,86 +995,91 @@ * \param bufsize out: file size * \return pointer to file contents. Caller is responsible for its deallocation. */ -static char* read_file(char* fname, size_t *bufsize) +static char *read_file(ASS_Library *library, char *fname, size_t *bufsize) { - int res; - long sz; - long bytes_read; - char* buf; + int res; + long sz; + long bytes_read; + char *buf; - FILE* fp = fopen(fname, "rb"); - if (!fp) { - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FopenFailed, fname); - return 0; - } - res = fseek(fp, 0, SEEK_END); - if (res == -1) { - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FseekFailed, fname); - fclose(fp); - return 0; - } + FILE *fp = fopen(fname, "rb"); + if (!fp) { + ass_msg(library, MSGL_WARN, + "ass_read_file(%s): fopen failed", fname); + return 0; + } + res = fseek(fp, 0, SEEK_END); + if (res == -1) { + ass_msg(library, MSGL_WARN, + "ass_read_file(%s): fseek failed", fname); + fclose(fp); + return 0; + } - sz = ftell(fp); - rewind(fp); + sz = ftell(fp); + rewind(fp); - if (sz > 10*1024*1024) { - mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_RefusingToLoadSubtitlesLargerThan10M, fname); - fclose(fp); - return 0; - } + if (sz > 10 * 1024 * 1024) { + ass_msg(library, MSGL_INFO, + "ass_read_file(%s): Refusing to load subtitles " + "larger than 10MiB", fname); + fclose(fp); + return 0; + } - mp_msg(MSGT_ASS, MSGL_V, "file size: %ld\n", sz); + ass_msg(library, MSGL_V, "File size: %ld", sz); - buf = malloc(sz + 1); - assert(buf); - bytes_read = 0; - do { - res = fread(buf + bytes_read, 1, sz - bytes_read, fp); - if (res <= 0) { - mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_ReadFailed, errno, strerror(errno)); - fclose(fp); - free(buf); - return 0; - } - bytes_read += res; - } while (sz - bytes_read > 0); - buf[sz] = '\0'; - fclose(fp); + buf = malloc(sz + 1); + assert(buf); + bytes_read = 0; + do { + res = fread(buf + bytes_read, 1, sz - bytes_read, fp); + if (res <= 0) { + ass_msg(library, MSGL_INFO, "Read failed, %d: %s", errno, + strerror(errno)); + fclose(fp); + free(buf); + return 0; + } + bytes_read += res; + } while (sz - bytes_read > 0); + buf[sz] = '\0'; + fclose(fp); - if (bufsize) - *bufsize = sz; - return buf; + if (bufsize) + *bufsize = sz; + return buf; } /* * \param buf pointer to subtitle text in utf-8 */ -static ass_track_t* parse_memory(ass_library_t* library, char* buf) +static ASS_Track *parse_memory(ASS_Library *library, char *buf) { - ass_track_t* track; - int i; + ASS_Track *track; + int i; - track = ass_new_track(library); + track = ass_new_track(library); - // process header - process_text(track, buf); + // process header + process_text(track, buf); - // external SSA/ASS subs does not have ReadOrder field - for (i = 0; i < track->n_events; ++i) - track->events[i].ReadOrder = i; + // external SSA/ASS subs does not have ReadOrder field + for (i = 0; i < track->n_events; ++i) + track->events[i].ReadOrder = i; - // there is no explicit end-of-font marker in ssa/ass - if (track->parser_priv->fontname) - decode_font(track); + // there is no explicit end-of-font marker in ssa/ass + if (track->parser_priv->fontname) + decode_font(track); - if (track->track_type == TRACK_TYPE_UNKNOWN) { - ass_free_track(track); - return 0; - } + if (track->track_type == TRACK_TYPE_UNKNOWN) { + ass_free_track(track); + return 0; + } - process_force_style(track); + ass_process_force_style(track); - return track; + return track; } /** @@ -1006,51 +1090,56 @@ * \param codepage recode buffer contents from given codepage * \return newly allocated track */ -ass_track_t* ass_read_memory(ass_library_t* library, char* buf, size_t bufsize, char* codepage) +ASS_Track *ass_read_memory(ASS_Library *library, char *buf, + size_t bufsize, char *codepage) { - ass_track_t* track; - int need_free = 0; + ASS_Track *track; + int need_free = 0; - if (!buf) - return 0; + if (!buf) + return 0; #ifdef CONFIG_ICONV - if (codepage) - buf = sub_recode(buf, bufsize, codepage); - if (!buf) - return 0; - else - need_free = 1; + if (codepage) { + buf = sub_recode(library, buf, bufsize, codepage); + if (!buf) + return 0; + else + need_free = 1; + } #endif - track = parse_memory(library, buf); - if (need_free) - free(buf); - if (!track) - return 0; + track = parse_memory(library, buf); + if (need_free) + free(buf); + if (!track) + return 0; - mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_AddedSubtitleFileMemory, track->n_styles, track->n_events); - return track; + ass_msg(library, MSGL_INFO, "Added subtitle file: " + "<memory> (%d styles, %d events)", + track->n_styles, track->n_events); + return track; } -char* read_file_recode(char* fname, char* codepage, size_t* size) +static char *read_file_recode(ASS_Library *library, char *fname, + char *codepage, size_t *size) { - char* buf; - size_t bufsize; + char *buf; + size_t bufsize; - buf = read_file(fname, &bufsize); - if (!buf) - return 0; + buf = read_file(library, fname, &bufsize); + if (!buf) + return 0; #ifdef CONFIG_ICONV - if (codepage) { - char* tmpbuf = sub_recode(buf, bufsize, codepage); - free(buf); - buf = tmpbuf; - } - if (!buf) - return 0; + if (codepage) { + char *tmpbuf = sub_recode(library, buf, bufsize, codepage); + free(buf); + buf = tmpbuf; + } + if (!buf) + return 0; #endif - *size = bufsize; - return buf; + *size = bufsize; + return buf; } /** @@ -1060,83 +1149,98 @@ * \param codepage recode buffer contents from given codepage * \return newly allocated track */ -ass_track_t* ass_read_file(ass_library_t* library, char* fname, char* codepage) +ASS_Track *ass_read_file(ASS_Library *library, char *fname, + char *codepage) { - char* buf; - ass_track_t* track; - size_t bufsize; + char *buf; + ASS_Track *track; + size_t bufsize; - buf = read_file_recode(fname, codepage, &bufsize); - if (!buf) - return 0; - track = parse_memory(library, buf); - free(buf); - if (!track) - return 0; + buf = read_file_recode(library, fname, codepage, &bufsize); + if (!buf) + return 0; + track = parse_memory(library, buf); + free(buf); + if (!track) + return 0; - track->name = strdup(fname); + track->name = strdup(fname); - mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_AddedSubtitleFileFname, fname, track->n_styles, track->n_events); + ass_msg(library, MSGL_INFO, + "Added subtitle file: '%s' (%d styles, %d events)", + fname, track->n_styles, track->n_events); -// dump_events(forced_tid); - return track; + return track; } /** * \brief read styles from file into already initialized track */ -int ass_read_styles(ass_track_t* track, char* fname, char* codepage) +int ass_read_styles(ASS_Track *track, char *fname, char *codepage) { - char* buf; - parser_state_t old_state; - size_t sz; + char *buf; + ParserState old_state; + size_t sz; - buf = read_file(fname, &sz); - if (!buf) - return 1; + buf = read_file(track->library, fname, &sz); + if (!buf) + return 1; #ifdef CONFIG_ICONV - if (codepage) { - char* tmpbuf; - tmpbuf = sub_recode(buf, sz, codepage); - free(buf); - buf = tmpbuf; - } - if (!buf) - return 0; + if (codepage) { + char *tmpbuf; + tmpbuf = sub_recode(track->library, buf, sz, codepage); + free(buf); + buf = tmpbuf; + } + if (!buf) + return 0; #endif - old_state = track->parser_priv->state; - track->parser_priv->state = PST_STYLES; - process_text(track, buf); - track->parser_priv->state = old_state; + old_state = track->parser_priv->state; + track->parser_priv->state = PST_STYLES; + process_text(track, buf); + track->parser_priv->state = old_state; - return 0; + return 0; } -long long ass_step_sub(ass_track_t* track, long long now, int movement) { - int i; +long long ass_step_sub(ASS_Track *track, long long now, int movement) +{ + int i; - if (movement == 0) return 0; - if (track->n_events == 0) return 0; + if (movement == 0) + return 0; + if (track->n_events == 0) + return 0; - if (movement < 0) - for (i = 0; (i < track->n_events) && ((long long)(track->events[i].Start + track->events[i].Duration) <= now); ++i) {} - else - for (i = track->n_events - 1; (i >= 0) && ((long long)(track->events[i].Start) > now); --i) {} + if (movement < 0) + for (i = 0; + (i < track->n_events) + && + ((long long) (track->events[i].Start + + track->events[i].Duration) <= now); ++i) { + } else + for (i = track->n_events - 1; + (i >= 0) && ((long long) (track->events[i].Start) > now); + --i) { + } - // -1 and n_events are ok - assert(i >= -1); assert(i <= track->n_events); - i += movement; - if (i < 0) i = 0; - if (i >= track->n_events) i = track->n_events - 1; - return ((long long)track->events[i].Start) - now; + // -1 and n_events are ok + assert(i >= -1); + assert(i <= track->n_events); + i += movement; + if (i < 0) + i = 0; + if (i >= track->n_events) + i = track->n_events - 1; + return ((long long) track->events[i].Start) - now; } -ass_track_t* ass_new_track(ass_library_t* library) { - ass_track_t* track = calloc(1, sizeof(ass_track_t)); - track->library = library; - track->ScaledBorderAndShadow = 1; - track->parser_priv = calloc(1, sizeof(parser_priv_t)); - return track; +ASS_Track *ass_new_track(ASS_Library *library) +{ + ASS_Track *track = calloc(1, sizeof(ASS_Track)); + track->library = library; + track->ScaledBorderAndShadow = 1; + track->parser_priv = calloc(1, sizeof(ASS_ParserPriv)); + return track; } -
--- a/libass/ass.h Fri Jan 08 18:19:10 2010 +0000 +++ b/libass/ass.h Fri Jan 08 18:35:44 2010 +0000 @@ -1,5 +1,3 @@ -// -*- c-basic-offset: 8; indent-tabs-mode: t -*- -// vim:ts=8:sw=8:noet:ai: /* * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com> * @@ -24,136 +22,271 @@ #define LIBASS_ASS_H #include <stdio.h> +#include <stdarg.h> #include "ass_types.h" -/// Libass renderer object. Contents are private. -typedef struct ass_renderer_s ass_renderer_t; +#define LIBASS_VERSION 0x00908000 + +/* + * A linked list of images produced by an ass renderer. + * + * These images have to be rendered in-order for the correct screen + * composition. The libass renderer clips these bitmaps to the frame size. + * w/h can be zero, in this case the bitmap should not be rendered at all. + * The last bitmap row is not guaranteed to be padded up to stride size, + * e.g. in the worst case a bitmap has the size stride * (h - 1) + w. + */ +typedef struct ass_image { + int w, h; // Bitmap width/height + int stride; // Bitmap stride + unsigned char *bitmap; // 1bpp stride*h alpha buffer + // Note: the last row may not be padded to + // bitmap stride! + uint32_t color; // Bitmap color and alpha, RGBA + int dst_x, dst_y; // Bitmap placement inside the video frame + + struct ass_image *next; // Next image, or NULL +} ASS_Image; -/// a linked list of images produced by ass renderer -typedef struct ass_image_s { - int w, h; // bitmap width/height - int stride; // bitmap stride - unsigned char* bitmap; // 1bpp stride*h alpha buffer - // Actual bitmap size may be as low as - // stride * (h-1) + w - uint32_t color; // RGBA - int dst_x, dst_y; // bitmap placement inside the video frame +/* + * Hinting type. (see ass_set_hinting below) + * + * FreeType's native hinter is still buggy sometimes and it is recommended + * to use the light autohinter, ASS_HINTING_LIGHT, instead. For best + * compatibility with problematic fonts, disable hinting. + */ +typedef enum { + ASS_HINTING_NONE = 0, + ASS_HINTING_LIGHT, + ASS_HINTING_NORMAL, + ASS_HINTING_NATIVE +} ASS_Hinting; - struct ass_image_s* next; // linked list -} ass_image_t; +/** + * \brief Initialize the library. + * \return library handle or NULL if failed + */ +ASS_Library *ass_library_init(void); -/// Hinting type -typedef enum {ASS_HINTING_NONE = 0, - ASS_HINTING_LIGHT, - ASS_HINTING_NORMAL, - ASS_HINTING_NATIVE -} ass_hinting_t; +/** + * \brief Finalize the library + * \param priv library handle + */ +void ass_library_done(ASS_Library *priv); /** - * \brief initialize the library - * \return library handle or NULL if failed + * \brief Set private font directory. + * It is used for saving embedded fonts and also in font lookup. + * + * \param priv library handle + * \param fonts_dir private directory for font extraction */ -ass_library_t* ass_library_init(void); +void ass_set_fonts_dir(ASS_Library *priv, const char *fonts_dir); /** - * \brief finalize the library + * \brief Whether fonts should be extracted from track data. * \param priv library handle + * \param extract whether to extract fonts */ -void ass_library_done(ass_library_t*); +void ass_set_extract_fonts(ASS_Library *priv, int extract); /** - * \brief set private font directory - * It is used for saving embedded fonts and also in font lookup. + * \brief Register style overrides with a library instance. + * The overrides should have the form [Style.]Param=Value, e.g. + * SomeStyle.Font=Arial + * ScaledBorderAndShadow=yes + * + * \param priv library handle + * \param list NULL-terminated list of strings */ -void ass_set_fonts_dir(ass_library_t* priv, const char* fonts_dir); +void ass_set_style_overrides(ASS_Library *priv, char **list); -void ass_set_extract_fonts(ass_library_t* priv, int extract); - -void ass_set_style_overrides(ass_library_t* priv, char** list); +/** + * \brief Explicitly process style overrides for a track. + * \param track track handle + */ +void ass_process_force_style(ASS_Track *track); /** - * \brief initialize the renderer + * \brief Register a callback for debug/info messages. + * If a callback is registered, it is called for every message emitted by + * libass. The callback receives a format string and a list of arguments, + * to be used for the printf family of functions. Additionally, a log level + * from 0 (FATAL errors) to 7 (verbose DEBUG) is passed. Usually, level 5 + * should be used by applications. + * If no callback is set, all messages level < 5 are printed to stderr, + * prefixed with [ass]. + * + * \param priv library handle + * \param msg_cb pointer to callback function + * \param data additional data, will be passed to callback + */ +void ass_set_message_cb(ASS_Library *priv, void (*msg_cb) + (int level, const char *fmt, va_list args, void *data), + void *data); + +/** + * \brief Initialize the renderer. * \param priv library handle * \return renderer handle or NULL if failed */ -ass_renderer_t* ass_renderer_init(ass_library_t*); +ASS_Renderer *ass_renderer_init(ASS_Library *); /** - * \brief finalize the renderer + * \brief Finalize the renderer. * \param priv renderer handle */ -void ass_renderer_done(ass_renderer_t* priv); +void ass_renderer_done(ASS_Renderer *priv); + +/** + * \brief Set the frame size in pixels, including margins. + * \param priv renderer handle + * \param w width + * \param h height + */ +void ass_set_frame_size(ASS_Renderer *priv, int w, int h); -void ass_set_frame_size(ass_renderer_t* priv, int w, int h); -void ass_set_margins(ass_renderer_t* priv, int t, int b, int l, int r); -void ass_set_use_margins(ass_renderer_t* priv, int use); -void ass_set_aspect_ratio(ass_renderer_t* priv, double ar); -void ass_set_font_scale(ass_renderer_t* priv, double font_scale); -void ass_set_hinting(ass_renderer_t* priv, ass_hinting_t ht); -void ass_set_line_spacing(ass_renderer_t* priv, double line_spacing); +/** + * \brief Set frame margins. These values may be negative if pan-and-scan + * is used. + * \param priv renderer handle + * \param t top margin + * \param b bottom margin + * \param l left margin + * \param r right margin + */ +void ass_set_margins(ASS_Renderer *priv, int t, int b, int l, int r); /** - * \brief set font lookup defaults + * \brief Whether margins should be used for placing regular events. + * \param priv renderer handle + * \param use whether to use the margins + */ +void ass_set_use_margins(ASS_Renderer *priv, int use); + +/** + * \brief Set aspect ratio parameters. + * \param priv renderer handle + * \param dar display aspect ratio (DAR), prescaled for output PAR + * \param sar storage aspect ratio (SAR) */ -int ass_set_fonts(ass_renderer_t* priv, const char* default_font, const char* default_family); +void ass_set_aspect_ratio(ASS_Renderer *priv, double dar, double sar); + +/** + * \brief Set a fixed font scaling factor. + * \param priv renderer handle + * \param font_scale scaling factor, default is 1.0 + */ +void ass_set_font_scale(ASS_Renderer *priv, double font_scale); + +/** + * \brief Set font hinting method. + * \param priv renderer handle + * \param ht hinting method + */ +void ass_set_hinting(ASS_Renderer *priv, ASS_Hinting ht); + +/** + * \brief Set line spacing. Will not be scaled with frame size. + * \param priv renderer handle + * \param line_spacing line spacing in pixels + */ +void ass_set_line_spacing(ASS_Renderer *priv, double line_spacing); /** - * \brief set font lookup defaults, don't use fontconfig even if it is available + * \brief Set font lookup defaults. + * \param default_font path to default font to use. Must be supplied if + * fontconfig is disabled or unavailable. + * \param default_family fallback font family for fontconfig, or NULL + * \param fc whether to use fontconfig + * \param config path to fontconfig configuration file, or NULL. Only relevant + * if fontconfig is used. + * \param update whether fontconfig cache should be built/updated now. Only + * relevant if fontconfig is used. */ -int ass_set_fonts_nofc(ass_renderer_t* priv, const char* default_font, const char* default_family); +void ass_set_fonts(ASS_Renderer *priv, const char *default_font, + const char *default_family, int fc, const char *config, + int update); /** - * \brief render a frame, producing a list of ass_image_t - * \param priv library + * \brief Update/build font cache. This needs to be called if it was + * disabled when ass_set_fonts was set. + * + * \param priv renderer handle + * \return success + */ +int ass_fonts_update(ASS_Renderer *priv); + +/** + * \brief Set hard cache limits. Do not set, or set to zero, for reasonable + * defaults. + * + * \param priv renderer handle + * \param glyph_max maximum number of cached glyphs + * \param bitmap_max_size maximum bitmap cache size (in MB) + */ +void ass_set_cache_limits(ASS_Renderer *priv, int glyph_max, + int bitmap_max_size); + +/** + * \brief Render a frame, producing a list of ASS_Image. + * \param priv renderer handle * \param track subtitle track * \param now video timestamp in milliseconds + * \param detect_change will be set to 1 if a change occured compared + * to the last invocation */ -ass_image_t* ass_render_frame(ass_renderer_t *priv, ass_track_t* track, long long now, int* detect_change); +ASS_Image *ass_render_frame(ASS_Renderer *priv, ASS_Track *track, + long long now, int *detect_change); -// The following functions operate on track objects and do not need an ass_renderer // +/* + * The following functions operate on track objects and do not need + * an ass_renderer + */ /** - * \brief allocate a new empty track object + * \brief Allocate a new empty track object. + * \param library handle * \return pointer to empty track */ -ass_track_t* ass_new_track(ass_library_t*); +ASS_Track *ass_new_track(ASS_Library *); /** - * \brief deallocate track and all its child objects (styles and events) + * \brief Deallocate track and all its child objects (styles and events). * \param track track to deallocate */ -void ass_free_track(ass_track_t* track); +void ass_free_track(ASS_Track *track); /** - * \brief allocate new style + * \brief Allocate new style. * \param track track * \return newly allocated style id */ -int ass_alloc_style(ass_track_t* track); +int ass_alloc_style(ASS_Track *track); /** - * \brief allocate new event + * \brief Allocate new event. * \param track track * \return newly allocated event id */ -int ass_alloc_event(ass_track_t* track); +int ass_alloc_event(ASS_Track *track); /** - * \brief delete a style + * \brief Delete a style. * \param track track * \param sid style id * Deallocates style data. Does not modify track->n_styles. */ -void ass_free_style(ass_track_t* track, int sid); +void ass_free_style(ASS_Track *track, int sid); /** - * \brief delete an event + * \brief Delete an event. * \param track track * \param eid event id * Deallocates event data. Does not modify track->n_events. */ -void ass_free_event(ass_track_t* track, int eid); +void ass_free_event(ASS_Track *track, int eid); /** * \brief Parse a chunk of subtitle stream data. @@ -161,71 +294,81 @@ * \param data string to parse * \param size length of data */ -void ass_process_data(ass_track_t* track, char* data, int size); +void ass_process_data(ASS_Track *track, char *data, int size); /** - * \brief Parse Codec Private section of subtitle stream + * \brief Parse Codec Private section of subtitle stream. * \param track target track * \param data string to parse * \param size length of data */ -void ass_process_codec_private(ass_track_t* track, char *data, int size); +void ass_process_codec_private(ASS_Track *track, char *data, int size); /** - * \brief Parse a chunk of subtitle stream data. In Matroska, this contains exactly 1 event (or a commentary). + * \brief Parse a chunk of subtitle stream data. In Matroska, + * this contains exactly 1 event (or a commentary). * \param track track * \param data string to parse * \param size length of data * \param timecode starting time of the event (milliseconds) * \param duration duration of the event (milliseconds) -*/ -void ass_process_chunk(ass_track_t* track, char *data, int size, long long timecode, long long duration); - -char* read_file_recode(char* fname, char* codepage, size_t* size); + */ +void ass_process_chunk(ASS_Track *track, char *data, int size, + long long timecode, long long duration); /** * \brief Read subtitles from file. + * \param library library handle * \param fname file name + * \param codepage encoding (iconv format) * \return newly allocated track */ -ass_track_t* ass_read_file(ass_library_t* library, char* fname, char* codepage); +ASS_Track *ass_read_file(ASS_Library *library, char *fname, + char *codepage); /** * \brief Read subtitles from memory. - * \param library libass library object + * \param library library handle * \param buf pointer to subtitles text * \param bufsize size of buffer - * \param codepage recode buffer contents from given codepage + * \param codepage encoding (iconv format) * \return newly allocated track */ -ass_track_t* ass_read_memory(ass_library_t* library, char* buf, size_t bufsize, char* codepage); +ASS_Track *ass_read_memory(ASS_Library *library, char *buf, + size_t bufsize, char *codepage); /** - * \brief read styles from file into already initialized track + * \brief Read styles from file into already initialized track. + * \param fname file name + * \param codepage encoding (iconv format) * \return 0 on success */ -int ass_read_styles(ass_track_t* track, char* fname, char* codepage); +int ass_read_styles(ASS_Track *track, char *fname, char *codepage); /** * \brief Add a memory font. + * \param library library handle * \param name attachment name * \param data binary font data * \param data_size data size */ -void ass_add_font(ass_library_t* library, char* name, char* data, int data_size); +void ass_add_font(ASS_Library *library, char *name, char *data, + int data_size); /** - * \brief Remove all fonts stored in ass_library object + * \brief Remove all fonts stored in an ass_library object. + * \param library library handle */ -void ass_clear_fonts(ass_library_t* library); +void ass_clear_fonts(ASS_Library *library); /** - * \brief Calculates timeshift from now to the start of some other subtitle event, depending on movement parameter + * \brief Calculates timeshift from now to the start of some other subtitle + * event, depending on movement parameter. * \param track subtitle track - * \param now current time, ms + * \param now current time in milliseconds * \param movement how many events to skip from the one currently displayed * +2 means "the one after the next", -1 means "previous" - * \return timeshift, ms + * \return timeshift in milliseconds */ -long long ass_step_sub(ass_track_t* track, long long now, int movement); +long long ass_step_sub(ASS_Track *track, long long now, int movement); #endif /* LIBASS_ASS_H */
--- a/libass/ass_bitmap.c Fri Jan 08 18:19:10 2010 +0000 +++ b/libass/ass_bitmap.c Fri Jan 08 18:35:44 2010 +0000 @@ -1,5 +1,3 @@ -// -*- c-basic-offset: 8; indent-tabs-mode: t -*- -// vim:ts=8:sw=8:noet:ai: /* * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com> * @@ -27,311 +25,513 @@ #include <ft2build.h> #include FT_GLYPH_H -#include "mputils.h" +#include "ass_utils.h" #include "ass_bitmap.h" -struct ass_synth_priv_s { - int tmp_w, tmp_h; - unsigned short* tmp; +struct ass_synth_priv { + int tmp_w, tmp_h; + unsigned short *tmp; - int g_r; - int g_w; + int g_r; + int g_w; - unsigned *g; - unsigned *gt2; + unsigned *g; + unsigned *gt2; - double radius; + double radius; }; static const unsigned int maxcolor = 255; static const unsigned base = 256; -static int generate_tables(ass_synth_priv_t* priv, double radius) +static int generate_tables(ASS_SynthPriv *priv, double radius) { - double A = log(1.0/base)/(radius*radius*2); - int mx, i; - double volume_diff, volume_factor = 0; - unsigned volume; + double A = log(1.0 / base) / (radius * radius * 2); + int mx, i; + double volume_diff, volume_factor = 0; + unsigned volume; - if (priv->radius == radius) - return 0; - else - priv->radius = radius; + if (priv->radius == radius) + return 0; + else + priv->radius = radius; - priv->g_r = ceil(radius); - priv->g_w = 2*priv->g_r+1; + priv->g_r = ceil(radius); + priv->g_w = 2 * priv->g_r + 1; - if (priv->g_r) { - priv->g = realloc(priv->g, priv->g_w * sizeof(unsigned)); - priv->gt2 = realloc(priv->gt2, 256 * priv->g_w * sizeof(unsigned)); - if (priv->g==NULL || priv->gt2==NULL) { - return -1; - } - } + if (priv->g_r) { + priv->g = realloc(priv->g, priv->g_w * sizeof(unsigned)); + priv->gt2 = realloc(priv->gt2, 256 * priv->g_w * sizeof(unsigned)); + if (priv->g == NULL || priv->gt2 == NULL) { + return -1; + } + } - if (priv->g_r) { - // gaussian curve with volume = 256 - for (volume_diff=10000000; volume_diff>0.0000001; volume_diff*=0.5){ - volume_factor+= volume_diff; - volume=0; - for (i = 0; i<priv->g_w; ++i) { - priv->g[i] = (unsigned)(exp(A * (i-priv->g_r)*(i-priv->g_r)) * volume_factor + .5); - volume+= priv->g[i]; - } - if(volume>256) volume_factor-= volume_diff; - } - volume=0; - for (i = 0; i<priv->g_w; ++i) { - priv->g[i] = (unsigned)(exp(A * (i-priv->g_r)*(i-priv->g_r)) * volume_factor + .5); - volume+= priv->g[i]; - } + if (priv->g_r) { + // gaussian curve with volume = 256 + for (volume_diff = 10000000; volume_diff > 0.0000001; + volume_diff *= 0.5) { + volume_factor += volume_diff; + volume = 0; + for (i = 0; i < priv->g_w; ++i) { + priv->g[i] = + (unsigned) (exp(A * (i - priv->g_r) * (i - priv->g_r)) * + volume_factor + .5); + volume += priv->g[i]; + } + if (volume > 256) + volume_factor -= volume_diff; + } + volume = 0; + for (i = 0; i < priv->g_w; ++i) { + priv->g[i] = + (unsigned) (exp(A * (i - priv->g_r) * (i - priv->g_r)) * + volume_factor + .5); + volume += priv->g[i]; + } - // gauss table: - for(mx=0;mx<priv->g_w;mx++){ - for(i=0;i<256;i++){ - priv->gt2[mx+i*priv->g_w] = i*priv->g[mx]; - } - } - } + // gauss table: + for (mx = 0; mx < priv->g_w; mx++) { + for (i = 0; i < 256; i++) { + priv->gt2[mx + i * priv->g_w] = i * priv->g[mx]; + } + } + } - return 0; + return 0; } -static void resize_tmp(ass_synth_priv_t* priv, int w, int h) +static void resize_tmp(ASS_SynthPriv *priv, int w, int h) { - if (priv->tmp_w >= w && priv->tmp_h >= h) - return; - if (priv->tmp_w == 0) - priv->tmp_w = 64; - if (priv->tmp_h == 0) - priv->tmp_h = 64; - while (priv->tmp_w < w) priv->tmp_w *= 2; - while (priv->tmp_h < h) priv->tmp_h *= 2; - if (priv->tmp) - free(priv->tmp); - priv->tmp = malloc((priv->tmp_w + 1) * priv->tmp_h * sizeof(short)); + if (priv->tmp_w >= w && priv->tmp_h >= h) + return; + if (priv->tmp_w == 0) + priv->tmp_w = 64; + if (priv->tmp_h == 0) + priv->tmp_h = 64; + while (priv->tmp_w < w) + priv->tmp_w *= 2; + while (priv->tmp_h < h) + priv->tmp_h *= 2; + if (priv->tmp) + free(priv->tmp); + priv->tmp = malloc((priv->tmp_w + 1) * priv->tmp_h * sizeof(short)); } -ass_synth_priv_t* ass_synth_init(double radius) +ASS_SynthPriv *ass_synth_init(double radius) { - ass_synth_priv_t* priv = calloc(1, sizeof(ass_synth_priv_t)); - generate_tables(priv, radius); - return priv; + ASS_SynthPriv *priv = calloc(1, sizeof(ASS_SynthPriv)); + generate_tables(priv, radius); + return priv; } -void ass_synth_done(ass_synth_priv_t* priv) +void ass_synth_done(ASS_SynthPriv *priv) { - if (priv->tmp) - free(priv->tmp); - if (priv->g) - free(priv->g); - if (priv->gt2) - free(priv->gt2); - free(priv); + if (priv->tmp) + free(priv->tmp); + if (priv->g) + free(priv->g); + if (priv->gt2) + free(priv->gt2); + free(priv); } -static bitmap_t* alloc_bitmap(int w, int h) +static Bitmap *alloc_bitmap(int w, int h) { - bitmap_t* bm; - bm = calloc(1, sizeof(bitmap_t)); - bm->buffer = malloc(w*h); - bm->w = w; - bm->h = h; - bm->left = bm->top = 0; - return bm; + Bitmap *bm; + bm = calloc(1, sizeof(Bitmap)); + bm->buffer = malloc(w * h); + bm->w = w; + bm->h = h; + bm->left = bm->top = 0; + return bm; } -void ass_free_bitmap(bitmap_t* bm) +void ass_free_bitmap(Bitmap *bm) { - if (bm) { - if (bm->buffer) free(bm->buffer); - free(bm); - } + if (bm) { + if (bm->buffer) + free(bm->buffer); + free(bm); + } } -static bitmap_t* copy_bitmap(const bitmap_t* src) +static Bitmap *copy_bitmap(const Bitmap *src) { - bitmap_t* dst = alloc_bitmap(src->w, src->h); - dst->left = src->left; - dst->top = src->top; - memcpy(dst->buffer, src->buffer, src->w * src->h); - return dst; + Bitmap *dst = alloc_bitmap(src->w, src->h); + dst->left = src->left; + dst->top = src->top; + memcpy(dst->buffer, src->buffer, src->w * src->h); + return dst; } -static int check_glyph_area(FT_Glyph glyph) +static int check_glyph_area(ASS_Library *library, FT_Glyph glyph) { - FT_BBox bbox; - long long dx, dy; - FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_TRUNCATE, &bbox); - dx = bbox.xMax - bbox.xMin; - dy = bbox.yMax - bbox.yMin; - if (dx * dy > 8000000) { - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_GlyphBBoxTooLarge, (int)dx, (int)dy); - return 1; - } else - return 0; + FT_BBox bbox; + long long dx, dy; + FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_TRUNCATE, &bbox); + dx = bbox.xMax - bbox.xMin; + dy = bbox.yMax - bbox.yMin; + if (dx * dy > 8000000) { + ass_msg(library, MSGL_WARN, "Glyph bounding box too large: %dx%dpx", + (int) dx, (int) dy); + return 1; + } else + return 0; } -static bitmap_t* glyph_to_bitmap_internal(FT_Glyph glyph, int bord) +static Bitmap *glyph_to_bitmap_internal(ASS_Library *library, + FT_Glyph glyph, int bord) { - FT_BitmapGlyph bg; - FT_Bitmap* bit; - bitmap_t* bm; - int w, h; - unsigned char* src; - unsigned char* dst; - int i; - int error; + FT_BitmapGlyph bg; + FT_Bitmap *bit; + Bitmap *bm; + int w, h; + unsigned char *src; + unsigned char *dst; + int i; + int error; - if (check_glyph_area(glyph)) - return 0; - error = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 0); - if (error) { - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FT_Glyph_To_BitmapError, error); - return 0; - } + if (check_glyph_area(library, glyph)) + return 0; + error = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 0); + if (error) { + ass_msg(library, MSGL_WARN, "FT_Glyph_To_Bitmap error %d", + error); + return 0; + } - bg = (FT_BitmapGlyph)glyph; - bit = &(bg->bitmap); - if (bit->pixel_mode != FT_PIXEL_MODE_GRAY) { - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_UnsupportedPixelMode, (int)(bit->pixel_mode)); - FT_Done_Glyph(glyph); - return 0; - } + bg = (FT_BitmapGlyph) glyph; + bit = &(bg->bitmap); + if (bit->pixel_mode != FT_PIXEL_MODE_GRAY) { + ass_msg(library, MSGL_WARN, "Unsupported pixel mode: %d", + (int) (bit->pixel_mode)); + FT_Done_Glyph(glyph); + return 0; + } - w = bit->width; - h = bit->rows; - bm = alloc_bitmap(w + 2*bord, h + 2*bord); - memset(bm->buffer, 0, bm->w * bm->h); - bm->left = bg->left - bord; - bm->top = - bg->top - bord; + w = bit->width; + h = bit->rows; + bm = alloc_bitmap(w + 2 * bord, h + 2 * bord); + memset(bm->buffer, 0, bm->w * bm->h); + bm->left = bg->left - bord; + bm->top = -bg->top - bord; - src = bit->buffer; - dst = bm->buffer + bord + bm->w * bord; - for (i = 0; i < h; ++i) { - memcpy(dst, src, w); - src += bit->pitch; - dst += bm->w; - } + src = bit->buffer; + dst = bm->buffer + bord + bm->w * bord; + for (i = 0; i < h; ++i) { + memcpy(dst, src, w); + src += bit->pitch; + dst += bm->w; + } - FT_Done_Glyph(glyph); - return bm; + FT_Done_Glyph(glyph); + return bm; } /** - * \brief fix outline bitmap and generate shadow bitmap - * Two things are done here: - * 1. Glyph bitmap is subtracted from outline bitmap. This way looks much better in some cases. - * 2. Shadow bitmap is created as a sum of glyph and outline bitmaps. + * \brief fix outline bitmap + * + * The glyph bitmap is subtracted from outline bitmap. This way looks much + * better in some cases. */ -static bitmap_t* fix_outline_and_shadow(bitmap_t* bm_g, bitmap_t* bm_o) +static void fix_outline(Bitmap *bm_g, Bitmap *bm_o) +{ + int x, y; + const int l = bm_o->left > bm_g->left ? bm_o->left : bm_g->left; + const int t = bm_o->top > bm_g->top ? bm_o->top : bm_g->top; + const int r = + bm_o->left + bm_o->w < + bm_g->left + bm_g->w ? bm_o->left + bm_o->w : bm_g->left + bm_g->w; + const int b = + bm_o->top + bm_o->h < + bm_g->top + bm_g->h ? bm_o->top + bm_o->h : bm_g->top + bm_g->h; + + unsigned char *g = + bm_g->buffer + (t - bm_g->top) * bm_g->w + (l - bm_g->left); + unsigned char *o = + bm_o->buffer + (t - bm_o->top) * bm_o->w + (l - bm_o->left); + + for (y = 0; y < b - t; ++y) { + for (x = 0; x < r - l; ++x) { + unsigned char c_g, c_o; + c_g = g[x]; + c_o = o[x]; + o[x] = (c_o > c_g) ? c_o - (c_g / 2) : 0; + } + g += bm_g->w; + o += bm_o->w; + } +} + +/** + * \brief Shift a bitmap by the fraction of a pixel in x and y direction + * expressed in 26.6 fixed point + */ +static void shift_bitmap(unsigned char *buf, int w, int h, int shift_x, + int shift_y) { - int x, y; - const int l = bm_o->left > bm_g->left ? bm_o->left : bm_g->left; - const int t = bm_o->top > bm_g->top ? bm_o->top : bm_g->top; - const int r = bm_o->left + bm_o->w < bm_g->left + bm_g->w ? bm_o->left + bm_o->w : bm_g->left + bm_g->w; - const int b = bm_o->top + bm_o->h < bm_g->top + bm_g->h ? bm_o->top + bm_o->h : bm_g->top + bm_g->h; + int x, y, b; + + // Shift in x direction + if (shift_x > 0) { + for (y = 0; y < h; y++) { + for (x = w - 1; x > 0; x--) { + b = (buf[x + y * w - 1] * shift_x) >> 6; + buf[x + y * w - 1] -= b; + buf[x + y * w] += b; + } + } + } else if (shift_x < 0) { + shift_x = -shift_x; + for (y = 0; y < h; y++) { + for (x = 0; x < w - 1; x++) { + b = (buf[x + y * w + 1] * shift_x) >> 6; + buf[x + y * w + 1] -= b; + buf[x + y * w] += b; + } + } + } - bitmap_t* bm_s = copy_bitmap(bm_o); + // Shift in y direction + if (shift_y > 0) { + for (x = 0; x < w; x++) { + for (y = h - 1; y > 0; y--) { + b = (buf[x + (y - 1) * w] * shift_y) >> 6; + buf[x + (y - 1) * w] -= b; + buf[x + y * w] += b; + } + } + } else if (shift_y < 0) { + shift_y = -shift_y; + for (x = 0; x < w; x++) { + for (y = 0; y < h - 1; y++) { + b = (buf[x + (y + 1) * w] * shift_y) >> 6; + buf[x + (y + 1) * w] -= b; + buf[x + y * w] += b; + } + } + } +} + +/* + * Gaussian blur. An fast pure C implementation from MPlayer. + */ +static void ass_gauss_blur(unsigned char *buffer, unsigned short *tmp2, + int width, int height, int stride, int *m2, + int r, int mwidth) +{ + + int x, y; + + unsigned char *s = buffer; + unsigned short *t = tmp2 + 1; + for (y = 0; y < height; y++) { + memset(t - 1, 0, (width + 1) * sizeof(short)); - unsigned char* g = bm_g->buffer + (t - bm_g->top) * bm_g->w + (l - bm_g->left); - unsigned char* o = bm_o->buffer + (t - bm_o->top) * bm_o->w + (l - bm_o->left); - unsigned char* s = bm_s->buffer + (t - bm_s->top) * bm_s->w + (l - bm_s->left); + for (x = 0; x < r; x++) { + const int src = s[x]; + if (src) { + register unsigned short *dstp = t + x - r; + int mx; + unsigned *m3 = (unsigned *) (m2 + src * mwidth); + for (mx = r - x; mx < mwidth; mx++) { + dstp[mx] += m3[mx]; + } + } + } + + for (; x < width - r; x++) { + const int src = s[x]; + if (src) { + register unsigned short *dstp = t + x - r; + int mx; + unsigned *m3 = (unsigned *) (m2 + src * mwidth); + for (mx = 0; mx < mwidth; mx++) { + dstp[mx] += m3[mx]; + } + } + } + + for (; x < width; x++) { + const int src = s[x]; + if (src) { + register unsigned short *dstp = t + x - r; + int mx; + const int x2 = r + width - x; + unsigned *m3 = (unsigned *) (m2 + src * mwidth); + for (mx = 0; mx < x2; mx++) { + dstp[mx] += m3[mx]; + } + } + } + + s += stride; + t += width + 1; + } + + t = tmp2; + for (x = 0; x < width; x++) { + for (y = 0; y < r; y++) { + unsigned short *srcp = t + y * (width + 1) + 1; + int src = *srcp; + if (src) { + register unsigned short *dstp = srcp - 1 + width + 1; + const int src2 = (src + 128) >> 8; + unsigned *m3 = (unsigned *) (m2 + src2 * mwidth); - for (y = 0; y < b - t; ++y) { - for (x = 0; x < r - l; ++x) { - unsigned char c_g, c_o; - c_g = g[x]; - c_o = o[x]; - o[x] = (c_o > c_g) ? c_o - (c_g/2) : 0; - s[x] = (c_o < 0xFF - c_g) ? c_o + c_g : 0xFF; - } - g += bm_g->w; - o += bm_o->w; - s += bm_s->w; - } + int mx; + *srcp = 128; + for (mx = r - 1; mx < mwidth; mx++) { + *dstp += m3[mx]; + dstp += width + 1; + } + } + } + for (; y < height - r; y++) { + unsigned short *srcp = t + y * (width + 1) + 1; + int src = *srcp; + if (src) { + register unsigned short *dstp = srcp - 1 - r * (width + 1); + const int src2 = (src + 128) >> 8; + unsigned *m3 = (unsigned *) (m2 + src2 * mwidth); - assert(bm_s); - return bm_s; + int mx; + *srcp = 128; + for (mx = 0; mx < mwidth; mx++) { + *dstp += m3[mx]; + dstp += width + 1; + } + } + } + for (; y < height; y++) { + unsigned short *srcp = t + y * (width + 1) + 1; + int src = *srcp; + if (src) { + const int y2 = r + height - y; + register unsigned short *dstp = srcp - 1 - r * (width + 1); + const int src2 = (src + 128) >> 8; + unsigned *m3 = (unsigned *) (m2 + src2 * mwidth); + + int mx; + *srcp = 128; + for (mx = 0; mx < y2; mx++) { + *dstp += m3[mx]; + dstp += width + 1; + } + } + } + t++; + } + + t = tmp2; + s = buffer; + for (y = 0; y < height; y++) { + for (x = 0; x < width; x++) { + s[x] = t[x] >> 8; + } + s += stride; + t += width + 1; + } } /** * \brief Blur with [[1,2,1]. [2,4,2], [1,2,1]] kernel * This blur is the same as the one employed by vsfilter. */ -static void be_blur(unsigned char *buf, int w, int h) { - unsigned int x, y; - unsigned int old_sum, new_sum; +static void be_blur(unsigned char *buf, int w, int h) +{ + unsigned int x, y; + unsigned int old_sum, new_sum; - for (y=0; y<h; y++) { - old_sum = 2 * buf[y*w]; - for (x=0; x<w-1; x++) { - new_sum = buf[y*w+x] + buf[y*w+x+1]; - buf[y*w+x] = (old_sum + new_sum) >> 2; - old_sum = new_sum; - } - } + for (y = 0; y < h; y++) { + old_sum = 2 * buf[y * w]; + for (x = 0; x < w - 1; x++) { + new_sum = buf[y * w + x] + buf[y * w + x + 1]; + buf[y * w + x] = (old_sum + new_sum) >> 2; + old_sum = new_sum; + } + } - for (x=0; x<w; x++) { - old_sum = 2 * buf[x]; - for (y=0; y<h-1; y++) { - new_sum = buf[y*w+x] + buf[(y+1)*w+x]; - buf[y*w+x] = (old_sum + new_sum) >> 2; - old_sum = new_sum; - } - } + for (x = 0; x < w; x++) { + old_sum = 2 * buf[x]; + for (y = 0; y < h - 1; y++) { + new_sum = buf[y * w + x] + buf[(y + 1) * w + x]; + buf[y * w + x] = (old_sum + new_sum) >> 2; + old_sum = new_sum; + } + } } -int glyph_to_bitmap(ass_synth_priv_t* priv_blur, - FT_Glyph glyph, FT_Glyph outline_glyph, bitmap_t** bm_g, - bitmap_t** bm_o, bitmap_t** bm_s, int be, double blur_radius) +int glyph_to_bitmap(ASS_Library *library, ASS_SynthPriv *priv_blur, + FT_Glyph glyph, FT_Glyph outline_glyph, + Bitmap **bm_g, Bitmap **bm_o, Bitmap **bm_s, + int be, double blur_radius, FT_Vector shadow_offset, + int border_style) { - int bord = be ? (be/4+1) : 0; - blur_radius *= 2; - bord = (blur_radius > 0.0) ? blur_radius : bord; + blur_radius *= 2; + int bbord = be > 0 ? sqrt(2 * be) : 0; + int gbord = blur_radius > 0.0 ? blur_radius + 1 : 0; + int bord = FFMAX(bbord, gbord); + if (bord == 0 && (shadow_offset.x || shadow_offset.y)) + bord = 1; - assert(bm_g && bm_o && bm_s); + assert(bm_g && bm_o && bm_s); - *bm_g = *bm_o = *bm_s = 0; + *bm_g = *bm_o = *bm_s = 0; - if (glyph) - *bm_g = glyph_to_bitmap_internal(glyph, bord); - if (!*bm_g) - return 1; + if (glyph) + *bm_g = glyph_to_bitmap_internal(library, glyph, bord); + if (!*bm_g) + return 1; + + if (outline_glyph) { + *bm_o = glyph_to_bitmap_internal(library, outline_glyph, bord); + if (!*bm_o) { + return 1; + } + } - if (outline_glyph) { - *bm_o = glyph_to_bitmap_internal(outline_glyph, bord); - if (!*bm_o) { - ass_free_bitmap(*bm_g); - return 1; - } - } - if (*bm_o) - resize_tmp(priv_blur, (*bm_o)->w, (*bm_o)->h); - resize_tmp(priv_blur, (*bm_g)->w, (*bm_g)->h); + // Apply box blur (multiple passes, if requested) + while (be--) { + if (*bm_o) + be_blur((*bm_o)->buffer, (*bm_o)->w, (*bm_o)->h); + else + be_blur((*bm_g)->buffer, (*bm_g)->w, (*bm_g)->h); + } - if (be) { - while (be--) { - if (*bm_o) - be_blur((*bm_o)->buffer, (*bm_o)->w, (*bm_o)->h); - else - be_blur((*bm_g)->buffer, (*bm_g)->w, (*bm_g)->h); - } - } else { - if (blur_radius > 0.0) { - generate_tables(priv_blur, blur_radius); - if (*bm_o) - blur((*bm_o)->buffer, priv_blur->tmp, (*bm_o)->w, (*bm_o)->h, (*bm_o)->w, (int*)priv_blur->gt2, priv_blur->g_r, priv_blur->g_w); - else - blur((*bm_g)->buffer, priv_blur->tmp, (*bm_g)->w, (*bm_g)->h, (*bm_g)->w, (int*)priv_blur->gt2, priv_blur->g_r, priv_blur->g_w); - } - } - if (*bm_o) - *bm_s = fix_outline_and_shadow(*bm_g, *bm_o); - else - *bm_s = copy_bitmap(*bm_g); + // Apply gaussian blur + if (blur_radius > 0.0) { + if (*bm_o) + resize_tmp(priv_blur, (*bm_o)->w, (*bm_o)->h); + else + resize_tmp(priv_blur, (*bm_g)->w, (*bm_g)->h); + generate_tables(priv_blur, blur_radius); + if (*bm_o) + ass_gauss_blur((*bm_o)->buffer, priv_blur->tmp, + (*bm_o)->w, (*bm_o)->h, (*bm_o)->w, + (int *) priv_blur->gt2, priv_blur->g_r, + priv_blur->g_w); + else + ass_gauss_blur((*bm_g)->buffer, priv_blur->tmp, + (*bm_g)->w, (*bm_g)->h, (*bm_g)->w, + (int *) priv_blur->gt2, priv_blur->g_r, + priv_blur->g_w); + } - assert(bm_s); - return 0; + // Create shadow and fix outline as needed + if (*bm_o && border_style != 3) { + *bm_s = copy_bitmap(*bm_o); + fix_outline(*bm_g, *bm_o); + } else if (*bm_o) { + *bm_s = copy_bitmap(*bm_o); + } else + *bm_s = copy_bitmap(*bm_g); + + assert(bm_s); + + shift_bitmap((*bm_s)->buffer, (*bm_s)->w,(*bm_s)->h, + shadow_offset.x, shadow_offset.y); + + return 0; } -
--- a/libass/ass_bitmap.h Fri Jan 08 18:19:10 2010 +0000 +++ b/libass/ass_bitmap.h Fri Jan 08 18:35:44 2010 +0000 @@ -1,5 +1,3 @@ -// -*- c-basic-offset: 8; indent-tabs-mode: t -*- -// vim:ts=8:sw=8:noet:ai: /* * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com> * @@ -26,16 +24,18 @@ #include <ft2build.h> #include FT_GLYPH_H -typedef struct ass_synth_priv_s ass_synth_priv_t; +#include "ass.h" -ass_synth_priv_t* ass_synth_init(double); -void ass_synth_done(ass_synth_priv_t* priv); +typedef struct ass_synth_priv ASS_SynthPriv; -typedef struct bitmap_s { - int left, top; - int w, h; // width, height - unsigned char* buffer; // w x h buffer -} bitmap_t; +ASS_SynthPriv *ass_synth_init(double); +void ass_synth_done(ASS_SynthPriv *priv); + +typedef struct { + int left, top; + int w, h; // width, height + unsigned char *buffer; // w x h buffer +} Bitmap; /** * \brief perform glyph rendering @@ -46,8 +46,12 @@ * \param bm_g out: pointer to the bitmap of glyph shadow is returned here * \param be 1 = produces blurred bitmaps, 0 = normal bitmaps */ -int glyph_to_bitmap(ass_synth_priv_t* priv_blur, FT_Glyph glyph, FT_Glyph outline_glyph, bitmap_t** bm_g, bitmap_t** bm_o, bitmap_t** bm_s, int be, double blur_radius); +int glyph_to_bitmap(ASS_Library *library, ASS_SynthPriv *priv_blur, + FT_Glyph glyph, FT_Glyph outline_glyph, + Bitmap **bm_g, Bitmap **bm_o, Bitmap **bm_s, + int be, double blur_radius, FT_Vector shadow_offset, + int border_style); -void ass_free_bitmap(bitmap_t* bm); +void ass_free_bitmap(Bitmap *bm); -#endif /* LIBASS_BITMAP_H */ +#endif /* LIBASS_BITMAP_H */
--- a/libass/ass_cache.c Fri Jan 08 18:19:10 2010 +0000 +++ b/libass/ass_cache.c Fri Jan 08 18:35:44 2010 +0000 @@ -1,5 +1,3 @@ -// -*- c-basic-offset: 8; indent-tabs-mode: t -*- -// vim:ts=8:sw=8:noet:ai: /* * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com> * @@ -29,225 +27,209 @@ #include <assert.h> -#include "mputils.h" +#include "ass_utils.h" #include "ass.h" #include "ass_fontconfig.h" #include "ass_font.h" #include "ass_bitmap.h" #include "ass_cache.h" - -typedef struct hashmap_item_s { - void* key; - void* value; - struct hashmap_item_s* next; -} hashmap_item_t; -typedef hashmap_item_t* hashmap_item_p; - -struct hashmap_s { - int nbuckets; - size_t key_size, value_size; - hashmap_item_p* root; - hashmap_item_dtor_t item_dtor; // a destructor for hashmap key/value pairs - hashmap_key_compare_t key_compare; - hashmap_hash_t hash; - // stats - int hit_count; - int miss_count; - int count; -}; +static unsigned hashmap_hash(void *buf, size_t len) +{ + return fnv_32a_buf(buf, len, FNV1_32A_INIT); +} -#define FNV1_32A_INIT (unsigned)0x811c9dc5 - -static inline unsigned fnv_32a_buf(void* buf, size_t len, unsigned hval) +static int hashmap_key_compare(void *a, void *b, size_t size) { - unsigned char *bp = buf; - unsigned char *be = bp + len; - while (bp < be) { - hval ^= (unsigned)*bp++; - hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24); - } - return hval; + return memcmp(a, b, size) == 0; } -static inline unsigned fnv_32a_str(char* str, unsigned hval) + +static void hashmap_item_dtor(void *key, size_t key_size, void *value, + size_t value_size) { - unsigned char* s = (unsigned char*)str; - while (*s) { - hval ^= (unsigned)*s++; - hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24); - } - return hval; + free(key); + free(value); } -static unsigned hashmap_hash(void* buf, size_t len) -{ - return fnv_32a_buf(buf, len, FNV1_32A_INIT); -} - -static int hashmap_key_compare(void* a, void* b, size_t size) +Hashmap *hashmap_init(ASS_Library *library, size_t key_size, + size_t value_size, int nbuckets, + HashmapItemDtor item_dtor, + HashmapKeyCompare key_compare, + HashmapHash hash) { - return memcmp(a, b, size) == 0; -} - -static void hashmap_item_dtor(void* key, size_t key_size, void* value, size_t value_size) -{ - free(key); - free(value); + Hashmap *map = calloc(1, sizeof(Hashmap)); + map->library = library; + map->nbuckets = nbuckets; + map->key_size = key_size; + map->value_size = value_size; + map->root = calloc(nbuckets, sizeof(hashmap_item_p)); + map->item_dtor = item_dtor ? item_dtor : hashmap_item_dtor; + map->key_compare = key_compare ? key_compare : hashmap_key_compare; + map->hash = hash ? hash : hashmap_hash; + return map; } -hashmap_t* hashmap_init(size_t key_size, size_t value_size, int nbuckets, - hashmap_item_dtor_t item_dtor, hashmap_key_compare_t key_compare, - hashmap_hash_t hash) -{ - hashmap_t* map = calloc(1, sizeof(hashmap_t)); - map->nbuckets = nbuckets; - map->key_size = key_size; - map->value_size = value_size; - map->root = calloc(nbuckets, sizeof(hashmap_item_p)); - map->item_dtor = item_dtor ? item_dtor : hashmap_item_dtor; - map->key_compare = key_compare ? key_compare : hashmap_key_compare; - map->hash = hash ? hash : hashmap_hash; - return map; -} - -void hashmap_done(hashmap_t* map) +void hashmap_done(Hashmap *map) { - int i; - // print stats - if (map->count > 0 || map->hit_count + map->miss_count > 0) - mp_msg(MSGT_ASS, MSGL_V, "cache statistics: \n total accesses: %d\n hits: %d\n misses: %d\n object count: %d\n", - map->hit_count + map->miss_count, map->hit_count, map->miss_count, map->count); + int i; + // print stats + if (map->count > 0 || map->hit_count + map->miss_count > 0) + ass_msg(map->library, MSGL_V, + "cache statistics: \n total accesses: %d\n hits: %d\n " + "misses: %d\n object count: %d", + map->hit_count + map->miss_count, map->hit_count, + map->miss_count, map->count); - for (i = 0; i < map->nbuckets; ++i) { - hashmap_item_t* item = map->root[i]; - while (item) { - hashmap_item_t* next = item->next; - map->item_dtor(item->key, map->key_size, item->value, map->value_size); - free(item); - item = next; - } - } - free(map->root); - free(map); + for (i = 0; i < map->nbuckets; ++i) { + HashmapItem *item = map->root[i]; + while (item) { + HashmapItem *next = item->next; + map->item_dtor(item->key, map->key_size, item->value, + map->value_size); + free(item); + item = next; + } + } + free(map->root); + free(map); } // does nothing if key already exists -void* hashmap_insert(hashmap_t* map, void* key, void* value) +void *hashmap_insert(Hashmap *map, void *key, void *value) { - unsigned hash = map->hash(key, map->key_size); - hashmap_item_t** next = map->root + (hash % map->nbuckets); - while (*next) { - if (map->key_compare(key, (*next)->key, map->key_size)) - return (*next)->value; - next = &((*next)->next); - assert(next); - } - (*next) = malloc(sizeof(hashmap_item_t)); - (*next)->key = malloc(map->key_size); - (*next)->value = malloc(map->value_size); - memcpy((*next)->key, key, map->key_size); - memcpy((*next)->value, value, map->value_size); - (*next)->next = 0; + unsigned hash = map->hash(key, map->key_size); + HashmapItem **next = map->root + (hash % map->nbuckets); + while (*next) { + if (map->key_compare(key, (*next)->key, map->key_size)) + return (*next)->value; + next = &((*next)->next); + assert(next); + } + (*next) = malloc(sizeof(HashmapItem)); + (*next)->key = malloc(map->key_size); + (*next)->value = malloc(map->value_size); + memcpy((*next)->key, key, map->key_size); + memcpy((*next)->value, value, map->value_size); + (*next)->next = 0; - map->count ++; - return (*next)->value; + map->count++; + return (*next)->value; } -void* hashmap_find(hashmap_t* map, void* key) +void *hashmap_find(Hashmap *map, void *key) { - unsigned hash = map->hash(key, map->key_size); - hashmap_item_t* item = map->root[hash % map->nbuckets]; - while (item) { - if (map->key_compare(key, item->key, map->key_size)) { - map->hit_count++; - return item->value; - } - item = item->next; - } - map->miss_count++; - return 0; + unsigned hash = map->hash(key, map->key_size); + HashmapItem *item = map->root[hash % map->nbuckets]; + while (item) { + if (map->key_compare(key, item->key, map->key_size)) { + map->hit_count++; + return item->value; + } + item = item->next; + } + map->miss_count++; + return 0; } //--------------------------------- // font cache -hashmap_t* font_cache; - -static unsigned font_desc_hash(void* buf, size_t len) +static unsigned font_desc_hash(void *buf, size_t len) { - ass_font_desc_t* desc = buf; - unsigned hval; - hval = fnv_32a_str(desc->family, FNV1_32A_INIT); - hval = fnv_32a_buf(&desc->bold, sizeof(desc->bold), hval); - hval = fnv_32a_buf(&desc->italic, sizeof(desc->italic), hval); - return hval; + ASS_FontDesc *desc = buf; + unsigned hval; + hval = fnv_32a_str(desc->family, FNV1_32A_INIT); + hval = fnv_32a_buf(&desc->bold, sizeof(desc->bold), hval); + hval = fnv_32a_buf(&desc->italic, sizeof(desc->italic), hval); + return hval; } -static int font_compare(void* key1, void* key2, size_t key_size) { - ass_font_desc_t* a = key1; - ass_font_desc_t* b = key2; - if (strcmp(a->family, b->family) != 0) - return 0; - if (a->bold != b->bold) - return 0; - if (a->italic != b->italic) - return 0; - if (a->treat_family_as_pattern != b->treat_family_as_pattern) - return 0; - return 1; +static int font_compare(void *key1, void *key2, size_t key_size) +{ + ASS_FontDesc *a = key1; + ASS_FontDesc *b = key2; + if (strcmp(a->family, b->family) != 0) + return 0; + if (a->bold != b->bold) + return 0; + if (a->italic != b->italic) + return 0; + if (a->treat_family_as_pattern != b->treat_family_as_pattern) + return 0; + return 1; } -static void font_hash_dtor(void* key, size_t key_size, void* value, size_t value_size) +static void font_hash_dtor(void *key, size_t key_size, void *value, + size_t value_size) { - ass_font_free(value); - free(key); + ass_font_free(value); + free(key); } -ass_font_t* ass_font_cache_find(ass_font_desc_t* desc) +ASS_Font *ass_font_cache_find(Hashmap *font_cache, + ASS_FontDesc *desc) { - return hashmap_find(font_cache, desc); + return hashmap_find(font_cache, desc); } /** * \brief Add a face struct to cache. * \param font font struct */ -void* ass_font_cache_add(ass_font_t* font) +void *ass_font_cache_add(Hashmap *font_cache, ASS_Font *font) { - return hashmap_insert(font_cache, &(font->desc), font); + return hashmap_insert(font_cache, &(font->desc), font); } -void ass_font_cache_init(void) +Hashmap *ass_font_cache_init(ASS_Library *library) { - font_cache = hashmap_init(sizeof(ass_font_desc_t), - sizeof(ass_font_t), - 1000, - font_hash_dtor, font_compare, font_desc_hash); + Hashmap *font_cache; + font_cache = hashmap_init(library, sizeof(ASS_FontDesc), + sizeof(ASS_Font), + 1000, + font_hash_dtor, font_compare, font_desc_hash); + return font_cache; } -void ass_font_cache_done(void) +void ass_font_cache_done(Hashmap *font_cache) { - hashmap_done(font_cache); + hashmap_done(font_cache); } + +// Create hash/compare functions for bitmap and glyph +#define CREATE_HASH_FUNCTIONS +#include "ass_cache_template.h" +#define CREATE_COMPARISON_FUNCTIONS +#include "ass_cache_template.h" + //--------------------------------- // bitmap cache -hashmap_t* bitmap_cache; - -static void bitmap_hash_dtor(void* key, size_t key_size, void* value, size_t value_size) +static void bitmap_hash_dtor(void *key, size_t key_size, void *value, + size_t value_size) { - bitmap_hash_val_t* v = value; - if (v->bm) ass_free_bitmap(v->bm); - if (v->bm_o) ass_free_bitmap(v->bm_o); - if (v->bm_s) ass_free_bitmap(v->bm_s); - free(key); - free(value); + BitmapHashValue *v = value; + if (v->bm) + ass_free_bitmap(v->bm); + if (v->bm_o) + ass_free_bitmap(v->bm_o); + if (v->bm_s) + ass_free_bitmap(v->bm_s); + free(key); + free(value); } -void* cache_add_bitmap(bitmap_hash_key_t* key, bitmap_hash_val_t* val) +void *cache_add_bitmap(Hashmap *bitmap_cache, BitmapHashKey *key, + BitmapHashValue *val) { - return hashmap_insert(bitmap_cache, key, val); + // Note: this is only an approximation + if (val->bm_o) + bitmap_cache->cache_size += val->bm_o->w * val->bm_o->h * 3; + else if (val->bm) + bitmap_cache->cache_size += val->bm->w * val->bm->h * 3; + + return hashmap_insert(bitmap_cache, key, val); } /** @@ -255,47 +237,56 @@ * \param key hash key * \return requested hash val or 0 if not found */ -bitmap_hash_val_t* cache_find_bitmap(bitmap_hash_key_t* key) +BitmapHashValue *cache_find_bitmap(Hashmap *bitmap_cache, + BitmapHashKey *key) { - return hashmap_find(bitmap_cache, key); + return hashmap_find(bitmap_cache, key); } -void ass_bitmap_cache_init(void) +Hashmap *ass_bitmap_cache_init(ASS_Library *library) { - bitmap_cache = hashmap_init(sizeof(bitmap_hash_key_t), - sizeof(bitmap_hash_val_t), - 0xFFFF + 13, - bitmap_hash_dtor, NULL, NULL); + Hashmap *bitmap_cache; + bitmap_cache = hashmap_init(library, + sizeof(BitmapHashKey), + sizeof(BitmapHashValue), + 0xFFFF + 13, + bitmap_hash_dtor, bitmap_compare, + bitmap_hash); + return bitmap_cache; } -void ass_bitmap_cache_done(void) +void ass_bitmap_cache_done(Hashmap *bitmap_cache) { - hashmap_done(bitmap_cache); + hashmap_done(bitmap_cache); } -void ass_bitmap_cache_reset(void) +Hashmap *ass_bitmap_cache_reset(Hashmap *bitmap_cache) { - ass_bitmap_cache_done(); - ass_bitmap_cache_init(); + ASS_Library *lib = bitmap_cache->library; + + ass_bitmap_cache_done(bitmap_cache); + return ass_bitmap_cache_init(lib); } //--------------------------------- // glyph cache -hashmap_t* glyph_cache; - -static void glyph_hash_dtor(void* key, size_t key_size, void* value, size_t value_size) +static void glyph_hash_dtor(void *key, size_t key_size, void *value, + size_t value_size) { - glyph_hash_val_t* v = value; - if (v->glyph) FT_Done_Glyph(v->glyph); - if (v->outline_glyph) FT_Done_Glyph(v->outline_glyph); - free(key); - free(value); + GlyphHashValue *v = value; + if (v->glyph) + FT_Done_Glyph(v->glyph); + if (v->outline_glyph) + FT_Done_Glyph(v->outline_glyph); + free(key); + free(value); } -void* cache_add_glyph(glyph_hash_key_t* key, glyph_hash_val_t* val) +void *cache_add_glyph(Hashmap *glyph_cache, GlyphHashKey *key, + GlyphHashValue *val) { - return hashmap_insert(glyph_cache, key, val); + return hashmap_insert(glyph_cache, key, val); } /** @@ -303,48 +294,54 @@ * \param key hash key * \return requested hash val or 0 if not found */ -glyph_hash_val_t* cache_find_glyph(glyph_hash_key_t* key) +GlyphHashValue *cache_find_glyph(Hashmap *glyph_cache, + GlyphHashKey *key) { - return hashmap_find(glyph_cache, key); + return hashmap_find(glyph_cache, key); } -void ass_glyph_cache_init(void) +Hashmap *ass_glyph_cache_init(ASS_Library *library) { - glyph_cache = hashmap_init(sizeof(glyph_hash_key_t), - sizeof(glyph_hash_val_t), - 0xFFFF + 13, - glyph_hash_dtor, NULL, NULL); + Hashmap *glyph_cache; + glyph_cache = hashmap_init(library, sizeof(GlyphHashKey), + sizeof(GlyphHashValue), + 0xFFFF + 13, + glyph_hash_dtor, glyph_compare, glyph_hash); + return glyph_cache; } -void ass_glyph_cache_done(void) +void ass_glyph_cache_done(Hashmap *glyph_cache) { - hashmap_done(glyph_cache); + hashmap_done(glyph_cache); } -void ass_glyph_cache_reset(void) +Hashmap *ass_glyph_cache_reset(Hashmap *glyph_cache) { - ass_glyph_cache_done(); - ass_glyph_cache_init(); + ASS_Library *lib = glyph_cache->library; + + ass_glyph_cache_done(glyph_cache); + return ass_glyph_cache_init(lib); } //--------------------------------- // composite cache -hashmap_t* composite_cache; - -static void composite_hash_dtor(void* key, size_t key_size, void* value, size_t value_size) +static void composite_hash_dtor(void *key, size_t key_size, void *value, + size_t value_size) { - composite_hash_val_t* v = value; - free(v->a); - free(v->b); - free(key); - free(value); + CompositeHashValue *v = value; + free(v->a); + free(v->b); + free(key); + free(value); } -void* cache_add_composite(composite_hash_key_t* key, composite_hash_val_t* val) +void *cache_add_composite(Hashmap *composite_cache, + CompositeHashKey *key, + CompositeHashValue *val) { - return hashmap_insert(composite_cache, key, val); + return hashmap_insert(composite_cache, key, val); } /** @@ -352,27 +349,32 @@ * \param key hash key * \return requested hash val or 0 if not found */ -composite_hash_val_t* cache_find_composite(composite_hash_key_t* key) +CompositeHashValue *cache_find_composite(Hashmap *composite_cache, + CompositeHashKey *key) { - return hashmap_find(composite_cache, key); + return hashmap_find(composite_cache, key); } -void ass_composite_cache_init(void) +Hashmap *ass_composite_cache_init(ASS_Library *library) { - composite_cache = hashmap_init(sizeof(composite_hash_key_t), - sizeof(composite_hash_val_t), - 0xFFFF + 13, - composite_hash_dtor, NULL, NULL); + Hashmap *composite_cache; + composite_cache = hashmap_init(library, sizeof(CompositeHashKey), + sizeof(CompositeHashValue), + 0xFFFF + 13, + composite_hash_dtor, composite_compare, + composite_hash); + return composite_cache; } -void ass_composite_cache_done(void) +void ass_composite_cache_done(Hashmap *composite_cache) { - hashmap_done(composite_cache); + hashmap_done(composite_cache); } -void ass_composite_cache_reset(void) +Hashmap *ass_composite_cache_reset(Hashmap *composite_cache) { - ass_composite_cache_done(); - ass_composite_cache_init(); + ASS_Library *lib = composite_cache->library; + + ass_composite_cache_done(composite_cache); + return ass_composite_cache_init(lib); } -
--- a/libass/ass_cache.h Fri Jan 08 18:19:10 2010 +0000 +++ b/libass/ass_cache.h Fri Jan 08 18:35:44 2010 +0000 @@ -1,5 +1,3 @@ -// -*- c-basic-offset: 8; indent-tabs-mode: t -*- -// vim:ts=8:sw=8:noet:ai: /* * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com> * @@ -27,99 +25,95 @@ #include "ass_font.h" #include "ass_bitmap.h" -void ass_font_cache_init(void); -ass_font_t* ass_font_cache_find(ass_font_desc_t* desc); -void* ass_font_cache_add(ass_font_t* font); -void ass_font_cache_done(void); +typedef void (*HashmapItemDtor) (void *key, size_t key_size, + void *value, size_t value_size); +typedef int (*HashmapKeyCompare) (void *key1, void *key2, + size_t key_size); +typedef unsigned (*HashmapHash) (void *key, size_t key_size); +typedef struct hashmap_item { + void *key; + void *value; + struct hashmap_item *next; +} HashmapItem; +typedef HashmapItem *hashmap_item_p; -// describes a bitmap; bitmaps with equivalents structs are considered identical -typedef struct bitmap_hash_key_s { - char bitmap; // bool : true = bitmap, false = outline - ass_font_t* font; - double size; // font size - uint32_t ch; // character code - unsigned outline; // border width, 16.16 fixed point value - int bold, italic; - char be; // blur edges - double blur; // gaussian blur +typedef struct { + int nbuckets; + size_t key_size, value_size; + hashmap_item_p *root; + HashmapItemDtor item_dtor; // a destructor for hashmap key/value pairs + HashmapKeyCompare key_compare; + HashmapHash hash; + size_t cache_size; + // stats + int hit_count; + int miss_count; + int count; + ASS_Library *library; +} Hashmap; - unsigned scale_x, scale_y; // 16.16 - int frx, fry, frz; // signed 16.16 - int shift_x, shift_y; // shift vector that was added to glyph before applying rotation - // = 0, if frx = fry = frx = 0 - // = (glyph base point) - (rotation origin), otherwise +Hashmap *hashmap_init(ASS_Library *library, size_t key_size, + size_t value_size, int nbuckets, + HashmapItemDtor item_dtor, + HashmapKeyCompare key_compare, + HashmapHash hash); +void hashmap_done(Hashmap *map); +void *hashmap_insert(Hashmap *map, void *key, void *value); +void *hashmap_find(Hashmap *map, void *key); - FT_Vector advance; // subpixel shift vector -} bitmap_hash_key_t; +Hashmap *ass_font_cache_init(ASS_Library *library); +ASS_Font *ass_font_cache_find(Hashmap *, ASS_FontDesc *desc); +void *ass_font_cache_add(Hashmap *, ASS_Font *font); +void ass_font_cache_done(Hashmap *); -typedef struct bitmap_hash_val_s { - bitmap_t* bm; // the actual bitmaps - bitmap_t* bm_o; - bitmap_t* bm_s; -} bitmap_hash_val_t; +// Create definitions for bitmap_hash_key and glyph_hash_key +#define CREATE_STRUCT_DEFINITIONS +#include "ass_cache_template.h" -void ass_bitmap_cache_init(void); -void* cache_add_bitmap(bitmap_hash_key_t* key, bitmap_hash_val_t* val); -bitmap_hash_val_t* cache_find_bitmap(bitmap_hash_key_t* key); -void ass_bitmap_cache_reset(void); -void ass_bitmap_cache_done(void); +typedef struct { + Bitmap *bm; // the actual bitmaps + Bitmap *bm_o; + Bitmap *bm_s; +} BitmapHashValue; + +Hashmap *ass_bitmap_cache_init(ASS_Library *library); +void *cache_add_bitmap(Hashmap *, BitmapHashKey *key, + BitmapHashValue *val); +BitmapHashValue *cache_find_bitmap(Hashmap *bitmap_cache, + BitmapHashKey *key); +Hashmap *ass_bitmap_cache_reset(Hashmap *bitmap_cache); +void ass_bitmap_cache_done(Hashmap *bitmap_cache); -// Cache for composited bitmaps -typedef struct composite_hash_key_s { - int aw, ah, bw, bh; - int ax, ay, bx, by; - bitmap_hash_key_t a; - bitmap_hash_key_t b; -} composite_hash_key_t; +typedef struct { + unsigned char *a; + unsigned char *b; +} CompositeHashValue; -typedef struct composite_hash_val_s { - unsigned char* a; - unsigned char* b; -} composite_hash_val_t; - -void ass_composite_cache_init(void); -void* cache_add_composite(composite_hash_key_t* key, composite_hash_val_t* val); -composite_hash_val_t* cache_find_composite(composite_hash_key_t* key); -void ass_composite_cache_reset(void); -void ass_composite_cache_done(void); +Hashmap *ass_composite_cache_init(ASS_Library *library); +void *cache_add_composite(Hashmap *, CompositeHashKey *key, + CompositeHashValue *val); +CompositeHashValue *cache_find_composite(Hashmap *composite_cache, + CompositeHashKey *key); +Hashmap *ass_composite_cache_reset(Hashmap *composite_cache); +void ass_composite_cache_done(Hashmap *composite_cache); -// describes an outline glyph -typedef struct glyph_hash_key_s { - ass_font_t* font; - double size; // font size - uint32_t ch; // character code - int bold, italic; - unsigned scale_x, scale_y; // 16.16 - FT_Vector advance; // subpixel shift vector - unsigned outline; // border width, 16.16 -} glyph_hash_key_t; - -typedef struct glyph_hash_val_s { - FT_Glyph glyph; - FT_Glyph outline_glyph; - FT_BBox bbox_scaled; // bbox after scaling, but before rotation - FT_Vector advance; // 26.6, advance distance to the next bitmap in line -} glyph_hash_val_t; +typedef struct { + FT_Glyph glyph; + FT_Glyph outline_glyph; + FT_BBox bbox_scaled; // bbox after scaling, but before rotation + FT_Vector advance; // 26.6, advance distance to the next bitmap in line + int asc, desc; // ascender/descender of a drawing +} GlyphHashValue; -void ass_glyph_cache_init(void); -void* cache_add_glyph(glyph_hash_key_t* key, glyph_hash_val_t* val); -glyph_hash_val_t* cache_find_glyph(glyph_hash_key_t* key); -void ass_glyph_cache_reset(void); -void ass_glyph_cache_done(void); +Hashmap *ass_glyph_cache_init(ASS_Library *library); +void *cache_add_glyph(Hashmap *, GlyphHashKey *key, + GlyphHashValue *val); +GlyphHashValue *cache_find_glyph(Hashmap *glyph_cache, + GlyphHashKey *key); +Hashmap *ass_glyph_cache_reset(Hashmap *glyph_cache); +void ass_glyph_cache_done(Hashmap *glyph_cache); -typedef struct hashmap_s hashmap_t; -typedef void (*hashmap_item_dtor_t)(void* key, size_t key_size, void* value, size_t value_size); -typedef int (*hashmap_key_compare_t)(void* key1, void* key2, size_t key_size); -typedef unsigned (*hashmap_hash_t)(void* key, size_t key_size); - -hashmap_t* hashmap_init(size_t key_size, size_t value_size, int nbuckets, - hashmap_item_dtor_t item_dtor, hashmap_key_compare_t key_compare, - hashmap_hash_t hash); -void hashmap_done(hashmap_t* map); -void* hashmap_insert(hashmap_t* map, void* key, void* value); -void* hashmap_find(hashmap_t* map, void* key); - -#endif /* LIBASS_CACHE_H */ +#endif /* LIBASS_CACHE_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libass/ass_cache_template.h Fri Jan 08 18:35:44 2010 +0000 @@ -0,0 +1,122 @@ +#ifdef CREATE_STRUCT_DEFINITIONS +#undef CREATE_STRUCT_DEFINITIONS +#define START(funcname, structname) \ + typedef struct structname { +#define GENERIC(type, member) \ + type member; +#define FTVECTOR(member) \ + FT_Vector member; +#define BITMAPHASHKEY(member) \ + BitmapHashKey member; +#define END(typedefnamename) \ + } typedefnamename; + +#elif defined(CREATE_COMPARISON_FUNCTIONS) +#undef CREATE_COMPARISON_FUNCTIONS +#define START(funcname, structname) \ + static int funcname##_compare(void *key1, void *key2, size_t key_size) \ + { \ + struct structname *a = key1; \ + struct structname *b = key2; \ + return // conditions follow +#define GENERIC(type, member) \ + a->member == b->member && +#define FTVECTOR(member) \ + a->member.x == b->member.x && a->member.y == b->member.y && +#define BITMAPHASHKEY(member) \ + bitmap_compare(&a->member, &b->member, sizeof(a->member)) && +#define END(typedefname) \ + 1; \ + } + +#elif defined(CREATE_HASH_FUNCTIONS) +#undef CREATE_HASH_FUNCTIONS +#define START(funcname, structname) \ + static unsigned funcname##_hash(void *buf, size_t len) \ + { \ + struct structname *p = buf; \ + unsigned hval = FNV1_32A_INIT; +#define GENERIC(type, member) \ + hval = fnv_32a_buf(&p->member, sizeof(p->member), hval); +#define FTVECTOR(member) GENERIC(, member.x); GENERIC(, member.y); +#define BITMAPHASHKEY(member) { \ + unsigned temp = bitmap_hash(&p->member, sizeof(p->member)); \ + hval = fnv_32a_buf(&temp, sizeof(temp), hval); \ + } +#define END(typedefname) \ + return hval; \ + } + +#else +#error missing defines +#endif + + + +// describes a bitmap; bitmaps with equivalents structs are considered identical +START(bitmap, bitmap_hash_key) + GENERIC(char, bitmap) // bool : true = bitmap, false = outline + GENERIC(ASS_Font *, font) + GENERIC(double, size) // font size + GENERIC(uint32_t, ch) // character code + FTVECTOR(outline) // border width, 16.16 fixed point value + GENERIC(int, bold) + GENERIC(int, italic) + GENERIC(char, be) // blur edges + GENERIC(double, blur) // gaussian blur + GENERIC(unsigned, scale_x) // 16.16 + GENERIC(unsigned, scale_y) // 16.16 + GENERIC(int, frx) // signed 16.16 + GENERIC(int, fry) // signed 16.16 + GENERIC(int, frz) // signed 16.16 + GENERIC(int, fax) // signed 16.16 + GENERIC(int, fay) // signed 16.16 + // shift vector that was added to glyph before applying rotation + // = 0, if frx = fry = frx = 0 + // = (glyph base point) - (rotation origin), otherwise + GENERIC(int, shift_x) + GENERIC(int, shift_y) + FTVECTOR(advance) // subpixel shift vector + FTVECTOR(shadow_offset) // shadow subpixel shift + GENERIC(unsigned, drawing_hash) // hashcode of a drawing + GENERIC(unsigned, flags) // glyph decoration + GENERIC(unsigned, border_style) +END(BitmapHashKey) + +// describes an outline glyph +START(glyph, glyph_hash_key) + GENERIC(ASS_Font *, font) + GENERIC(double, size) // font size + GENERIC(uint32_t, ch) // character code + GENERIC(int, bold) + GENERIC(int, italic) + GENERIC(unsigned, scale_x) // 16.16 + GENERIC(unsigned, scale_y) // 16.16 + FTVECTOR(outline) // border width, 16.16 + GENERIC(unsigned, drawing_hash) // hashcode of a drawing + GENERIC(unsigned, flags) // glyph decoration flags + GENERIC(unsigned, border_style) +END(GlyphHashKey) + +// Cache for composited bitmaps +START(composite, composite_hash_key) + GENERIC(int, aw) + GENERIC(int, ah) + GENERIC(int, bw) + GENERIC(int, bh) + GENERIC(int, ax) + GENERIC(int, ay) + GENERIC(int, bx) + GENERIC(int, by) + GENERIC(int, as) + GENERIC(int, bs) + GENERIC(unsigned char *, a) + GENERIC(unsigned char *, b) +END(CompositeHashKey) + + +#undef START +#undef GENERIC +#undef FTVECTOR +#undef BITMAPHASHKEY +#undef END
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libass/ass_drawing.c Fri Jan 08 18:35:44 2010 +0000 @@ -0,0 +1,495 @@ +/* + * Copyright (C) 2009 Grigori Goronzy <greg@geekmind.org> + * + * This file is part of libass. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <ft2build.h> +#include FT_GLYPH_H +#include FT_OUTLINE_H +#include FT_BBOX_H +#include <math.h> + +#include "ass_utils.h" +#include "ass_font.h" +#include "ass_drawing.h" + +#define CURVE_ACCURACY 64.0 +#define GLYPH_INITIAL_POINTS 100 +#define GLYPH_INITIAL_CONTOURS 5 + +/* + * \brief Get and prepare a FreeType glyph + */ +static void drawing_make_glyph(ASS_Drawing *drawing, void *fontconfig_priv, + ASS_Font *font, ASS_Hinting hint) +{ + FT_OutlineGlyph glyph; + + // This is hacky... + glyph = (FT_OutlineGlyph) ass_font_get_glyph(fontconfig_priv, font, + (uint32_t) ' ', hint, 0); + if (glyph) { + FT_Outline_Done(drawing->ftlibrary, &glyph->outline); + FT_Outline_New(drawing->ftlibrary, GLYPH_INITIAL_POINTS, + GLYPH_INITIAL_CONTOURS, &glyph->outline); + + glyph->outline.n_contours = 0; + glyph->outline.n_points = 0; + glyph->root.advance.x = glyph->root.advance.y = 0; + } + drawing->glyph = glyph; +} + +/* + * \brief Add a single point to a contour. + */ +static inline void drawing_add_point(ASS_Drawing *drawing, + FT_Vector *point) +{ + FT_Outline *ol = &drawing->glyph->outline; + + if (ol->n_points >= drawing->max_points) { + drawing->max_points *= 2; + ol->points = realloc(ol->points, sizeof(FT_Vector) * + drawing->max_points); + ol->tags = realloc(ol->tags, drawing->max_points); + } + + ol->points[ol->n_points].x = point->x; + ol->points[ol->n_points].y = point->y; + ol->tags[ol->n_points] = 1; + ol->n_points++; +} + +/* + * \brief Close a contour and check glyph size overflow. + */ +static inline void drawing_close_shape(ASS_Drawing *drawing) +{ + FT_Outline *ol = &drawing->glyph->outline; + + if (ol->n_contours >= drawing->max_contours) { + drawing->max_contours *= 2; + ol->contours = realloc(ol->contours, sizeof(short) * + drawing->max_contours); + } + + if (ol->n_points) { + ol->contours[ol->n_contours] = ol->n_points - 1; + ol->n_contours++; + } +} + +/* + * \brief Prepare drawing for parsing. This just sets a few parameters. + */ +static void drawing_prepare(ASS_Drawing *drawing) +{ + // Scaling parameters + drawing->point_scale_x = drawing->scale_x * + 64.0 / (1 << (drawing->scale - 1)); + drawing->point_scale_y = drawing->scale_y * + 64.0 / (1 << (drawing->scale - 1)); +} + +/* + * \brief Finish a drawing. This only sets the horizontal advance according + * to the glyph's bbox at the moment. + */ +static void drawing_finish(ASS_Drawing *drawing, int raw_mode) +{ + int i, offset; + FT_BBox bbox; + FT_Outline *ol = &drawing->glyph->outline; + + // Close the last contour + drawing_close_shape(drawing); + +#if 0 + // Dump points + for (i = 0; i < ol->n_points; i++) { + printf("point (%d, %d)\n", (int) ol->points[i].x, + (int) ol->points[i].y); + } + + // Dump contours + for (i = 0; i < ol->n_contours; i++) + printf("contour %d\n", ol->contours[i]); +#endif + + ass_msg(drawing->library, MSGL_V, + "Parsed drawing with %d points and %d contours", ol->n_points, + ol->n_contours); + + if (raw_mode) + return; + + FT_Outline_Get_CBox(&drawing->glyph->outline, &bbox); + drawing->glyph->root.advance.x = d6_to_d16(bbox.xMax - bbox.xMin); + + drawing->desc = double_to_d6(-drawing->pbo * drawing->scale_y); + drawing->asc = bbox.yMax - bbox.yMin + drawing->desc; + + // Place it onto the baseline + offset = (bbox.yMax - bbox.yMin) + double_to_d6(-drawing->pbo * + drawing->scale_y); + for (i = 0; i < ol->n_points; i++) + ol->points[i].y += offset; +} + +/* + * \brief Check whether a number of items on the list is available + */ +static int token_check_values(ASS_DrawingToken *token, int i, int type) +{ + int j; + for (j = 0; j < i; j++) { + if (!token || token->type != type) return 0; + token = token->next; + } + + return 1; +} + +/* + * \brief Tokenize a drawing string into a list of ASS_DrawingToken + * This also expands points for closing b-splines + */ +static ASS_DrawingToken *drawing_tokenize(char *str) +{ + char *p = str; + int i, val, type = -1, is_set = 0; + FT_Vector point = {0, 0}; + + ASS_DrawingToken *root = NULL, *tail = NULL, *spline_start = NULL; + + while (*p) { + if (*p == 'c' && spline_start) { + // Close b-splines: add the first three points of the b-spline + // back to the end + if (token_check_values(spline_start->next, 2, TOKEN_B_SPLINE)) { + for (i = 0; i < 3; i++) { + tail->next = calloc(1, sizeof(ASS_DrawingToken)); + tail->next->prev = tail; + tail = tail->next; + tail->type = TOKEN_B_SPLINE; + tail->point = spline_start->point; + spline_start = spline_start->next; + } + spline_start = NULL; + } + } else if (!is_set && mystrtoi(&p, &val)) { + point.x = val; + is_set = 1; + p--; + } else if (is_set == 1 && mystrtoi(&p, &val)) { + point.y = val; + is_set = 2; + p--; + } else if (*p == 'm') + type = TOKEN_MOVE; + else if (*p == 'n') + type = TOKEN_MOVE_NC; + else if (*p == 'l') + type = TOKEN_LINE; + else if (*p == 'b') + type = TOKEN_CUBIC_BEZIER; + else if (*p == 'q') + type = TOKEN_CONIC_BEZIER; + else if (*p == 's') + type = TOKEN_B_SPLINE; + // We're simply ignoring TOKEN_EXTEND_B_SPLINE here. + // This is not harmful at all, since it can be ommitted with + // similar result (the spline is extended anyway). + + if (type != -1 && is_set == 2) { + if (root) { + tail->next = calloc(1, sizeof(ASS_DrawingToken)); + tail->next->prev = tail; + tail = tail->next; + } else + root = tail = calloc(1, sizeof(ASS_DrawingToken)); + tail->type = type; + tail->point = point; + is_set = 0; + if (type == TOKEN_B_SPLINE && !spline_start) + spline_start = tail->prev; + } + p++; + } + +#if 0 + // Check tokens + ASS_DrawingToken *t = root; + while(t) { + printf("token %d point (%d, %d)\n", t->type, t->point.x, t->point.y); + t = t->next; + } +#endif + + return root; +} + +/* + * \brief Free a list of tokens + */ +static void drawing_free_tokens(ASS_DrawingToken *token) +{ + while (token) { + ASS_DrawingToken *at = token; + token = token->next; + free(at); + } +} + +/* + * \brief Translate and scale a point coordinate according to baseline + * offset and scale. + */ +static inline void translate_point(ASS_Drawing *drawing, FT_Vector *point) +{ + point->x = drawing->point_scale_x * point->x; + point->y = drawing->point_scale_y * -point->y; +} + +/* + * \brief Evaluate a curve into lines + * This curve evaluator is also used in VSFilter (RTS.cpp); it's a simple + * implementation of the De Casteljau algorithm. + */ +static void drawing_evaluate_curve(ASS_Drawing *drawing, + ASS_DrawingToken *token, char spline, + int started) +{ + double cx3, cx2, cx1, cx0, cy3, cy2, cy1, cy0; + double t, h, max_accel, max_accel1, max_accel2; + FT_Vector cur = {0, 0}; + + cur = token->point; + translate_point(drawing, &cur); + int x0 = cur.x; + int y0 = cur.y; + token = token->next; + cur = token->point; + translate_point(drawing, &cur); + int x1 = cur.x; + int y1 = cur.y; + token = token->next; + cur = token->point; + translate_point(drawing, &cur); + int x2 = cur.x; + int y2 = cur.y; + token = token->next; + cur = token->point; + translate_point(drawing, &cur); + int x3 = cur.x; + int y3 = cur.y; + + if (spline) { + // 1 [-1 +3 -3 +1] + // - * [+3 -6 +3 0] + // 6 [-3 0 +3 0] + // [+1 +4 +1 0] + + double div6 = 1.0/6.0; + + cx3 = div6*(- x0+3*x1-3*x2+x3); + cx2 = div6*( 3*x0-6*x1+3*x2); + cx1 = div6*(-3*x0 +3*x2); + cx0 = div6*( x0+4*x1+1*x2); + + cy3 = div6*(- y0+3*y1-3*y2+y3); + cy2 = div6*( 3*y0-6*y1+3*y2); + cy1 = div6*(-3*y0 +3*y2); + cy0 = div6*( y0+4*y1+1*y2); + } else { + // [-1 +3 -3 +1] + // [+3 -6 +3 0] + // [-3 +3 0 0] + // [+1 0 0 0] + + cx3 = - x0+3*x1-3*x2+x3; + cx2 = 3*x0-6*x1+3*x2; + cx1 = -3*x0+3*x1; + cx0 = x0; + + cy3 = - y0+3*y1-3*y2+y3; + cy2 = 3*y0-6*y1+3*y2; + cy1 = -3*y0+3*y1; + cy0 = y0; + } + + max_accel1 = fabs(2 * cy2) + fabs(6 * cy3); + max_accel2 = fabs(2 * cx2) + fabs(6 * cx3); + + max_accel = FFMAX(max_accel1, max_accel2); + h = 1.0; + + if (max_accel > CURVE_ACCURACY) + h = sqrt(CURVE_ACCURACY / max_accel); + + if (!started) { + cur.x = cx0; + cur.y = cy0; + drawing_add_point(drawing, &cur); + } + + for (t = 0; t < 1.0; t += h) { + cur.x = cx0 + t * (cx1 + t * (cx2 + t * cx3)); + cur.y = cy0 + t * (cy1 + t * (cy2 + t * cy3)); + drawing_add_point(drawing, &cur); + } + + cur.x = cx0 + cx1 + cx2 + cx3; + cur.y = cy0 + cy1 + cy2 + cy3; + drawing_add_point(drawing, &cur); +} + +/* + * \brief Create and initialize a new drawing and return it + */ +ASS_Drawing *ass_drawing_new(void *fontconfig_priv, ASS_Font *font, + ASS_Hinting hint, FT_Library lib) +{ + ASS_Drawing *drawing; + + drawing = calloc(1, sizeof(*drawing)); + drawing->text = calloc(1, DRAWING_INITIAL_SIZE); + drawing->size = DRAWING_INITIAL_SIZE; + + drawing->ftlibrary = lib; + if (font) { + drawing->library = font->library; + drawing_make_glyph(drawing, fontconfig_priv, font, hint); + } + + drawing->scale_x = 1.; + drawing->scale_y = 1.; + drawing->max_contours = GLYPH_INITIAL_CONTOURS; + drawing->max_points = GLYPH_INITIAL_POINTS; + + return drawing; +} + +/* + * \brief Free a drawing + */ +void ass_drawing_free(ASS_Drawing* drawing) +{ + if (drawing) { + if (drawing->glyph) + FT_Done_Glyph((FT_Glyph) drawing->glyph); + free(drawing->text); + } + free(drawing); +} + +/* + * \brief Add one ASCII character to the drawing text buffer + */ +void ass_drawing_add_char(ASS_Drawing* drawing, char symbol) +{ + drawing->text[drawing->i++] = symbol; + drawing->text[drawing->i] = 0; + + if (drawing->i + 1 >= drawing->size) { + drawing->size *= 2; + drawing->text = realloc(drawing->text, drawing->size); + } +} + +/* + * \brief Create a hashcode for the drawing + * XXX: To avoid collisions a better hash algorithm might be useful. + */ +void ass_drawing_hash(ASS_Drawing* drawing) +{ + drawing->hash = fnv_32a_str(drawing->text, FNV1_32A_INIT); +} + +/* + * \brief Convert token list to outline. Calls the line and curve evaluators. + */ +FT_OutlineGlyph *ass_drawing_parse(ASS_Drawing *drawing, int raw_mode) +{ + int started = 0; + ASS_DrawingToken *token; + FT_Vector pen = {0, 0}; + + if (!drawing->glyph) + return NULL; + + drawing->tokens = drawing_tokenize(drawing->text); + drawing_prepare(drawing); + + token = drawing->tokens; + while (token) { + // Draw something according to current command + switch (token->type) { + case TOKEN_MOVE_NC: + pen = token->point; + translate_point(drawing, &pen); + token = token->next; + break; + case TOKEN_MOVE: + pen = token->point; + translate_point(drawing, &pen); + if (started) { + drawing_close_shape(drawing); + started = 0; + } + token = token->next; + break; + case TOKEN_LINE: { + FT_Vector to; + to = token->point; + translate_point(drawing, &to); + if (!started) drawing_add_point(drawing, &pen); + drawing_add_point(drawing, &to); + started = 1; + token = token->next; + break; + } + case TOKEN_CUBIC_BEZIER: + if (token_check_values(token, 3, TOKEN_CUBIC_BEZIER) && + token->prev) { + drawing_evaluate_curve(drawing, token->prev, 0, started); + token = token->next; + token = token->next; + token = token->next; + started = 1; + } else + token = token->next; + break; + case TOKEN_B_SPLINE: + if (token_check_values(token, 3, TOKEN_B_SPLINE) && + token->prev) { + drawing_evaluate_curve(drawing, token->prev, 1, started); + token = token->next; + started = 1; + } else + token = token->next; + break; + default: + token = token->next; + break; + } + } + + drawing_finish(drawing, raw_mode); + drawing_free_tokens(drawing->tokens); + return &drawing->glyph; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libass/ass_drawing.h Fri Jan 08 18:35:44 2010 +0000 @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2009 Grigori Goronzy <greg@geekmind.org> + * + * This file is part of libass. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef LIBASS_DRAWING_H +#define LIBASS_DRAWING_H + +#include <ft2build.h> +#include FT_GLYPH_H + +#include "ass.h" + +#define DRAWING_INITIAL_SIZE 256 + +typedef enum { + TOKEN_MOVE, + TOKEN_MOVE_NC, + TOKEN_LINE, + TOKEN_CUBIC_BEZIER, + TOKEN_CONIC_BEZIER, + TOKEN_B_SPLINE, + TOKEN_EXTEND_SPLINE, + TOKEN_CLOSE +} ASS_TokenType; + +typedef struct ass_drawing_token { + ASS_TokenType type; + FT_Vector point; + struct ass_drawing_token *next; + struct ass_drawing_token *prev; +} ASS_DrawingToken; + +typedef struct { + char *text; // drawing string + int i; // text index + int scale; // scale (1-64) for subpixel accuracy + double pbo; // drawing will be shifted in y direction by this amount + double scale_x; // FontScaleX + double scale_y; // FontScaleY + int asc; // ascender + int desc; // descender + FT_OutlineGlyph glyph; // the "fake" glyph created for later rendering + int hash; // hash value (for caching) + + // private + FT_Library ftlibrary; // FT library instance, needed for font ops + ASS_Library *library; + int size; // current buffer size + ASS_DrawingToken *tokens; // tokenized drawing + int max_points; // current maximum size + int max_contours; + double point_scale_x; + double point_scale_y; +} ASS_Drawing; + +ASS_Drawing *ass_drawing_new(void *fontconfig_priv, ASS_Font *font, + ASS_Hinting hint, FT_Library lib); +void ass_drawing_free(ASS_Drawing* drawing); +void ass_drawing_add_char(ASS_Drawing* drawing, char symbol); +void ass_drawing_hash(ASS_Drawing* drawing); +FT_OutlineGlyph *ass_drawing_parse(ASS_Drawing *drawing, int raw_mode); + +#endif /* LIBASS_DRAWING_H */
--- a/libass/ass_font.c Fri Jan 08 18:19:10 2010 +0000 +++ b/libass/ass_font.c Fri Jan 08 18:35:44 2010 +0000 @@ -1,5 +1,3 @@ -// -*- c-basic-offset: 8; indent-tabs-mode: t -*- -// vim:ts=8:sw=8:noet:ai: /* * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com> * @@ -28,6 +26,7 @@ #include FT_SYNTHESIS_H #include FT_GLYPH_H #include FT_TRUETYPE_TABLES_H +#include FT_OUTLINE_H #include "ass.h" #include "ass_library.h" @@ -36,210 +35,224 @@ #include "ass_cache.h" #include "ass_fontconfig.h" #include "ass_utils.h" -#include "mputils.h" /** * Select Microfost Unicode CharMap, if the font has one. * Otherwise, let FreeType decide. */ -static void charmap_magic(FT_Face face) +static void charmap_magic(ASS_Library *library, FT_Face face) { - int i; - for (i = 0; i < face->num_charmaps; ++i) { - FT_CharMap cmap = face->charmaps[i]; - unsigned pid = cmap->platform_id; - unsigned eid = cmap->encoding_id; - if (pid == 3 /*microsoft*/ && (eid == 1 /*unicode bmp*/ || eid == 10 /*full unicode*/)) { - FT_Set_Charmap(face, cmap); - return; - } - } + int i; + for (i = 0; i < face->num_charmaps; ++i) { + FT_CharMap cmap = face->charmaps[i]; + unsigned pid = cmap->platform_id; + unsigned eid = cmap->encoding_id; + if (pid == 3 /*microsoft */ + && (eid == 1 /*unicode bmp */ + || eid == 10 /*full unicode */ )) { + FT_Set_Charmap(face, cmap); + return; + } + } - if (!face->charmap) { - if (face->num_charmaps == 0) { - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_NoCharmaps); - return; - } - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_NoCharmapAutodetected); - FT_Set_Charmap(face, face->charmaps[0]); - return; - } + if (!face->charmap) { + if (face->num_charmaps == 0) { + ass_msg(library, MSGL_WARN, "Font face with no charmaps"); + return; + } + ass_msg(library, MSGL_WARN, + "No charmap autodetected, trying the first one"); + FT_Set_Charmap(face, face->charmaps[0]); + return; + } } -static void update_transform(ass_font_t* font) +static void update_transform(ASS_Font *font) { - int i; - FT_Matrix m; - m.xx = double_to_d16(font->scale_x); - m.yy = double_to_d16(font->scale_y); - m.xy = m.yx = 0; - for (i = 0; i < font->n_faces; ++i) - FT_Set_Transform(font->faces[i], &m, &font->v); + int i; + FT_Matrix m; + m.xx = double_to_d16(font->scale_x); + m.yy = double_to_d16(font->scale_y); + m.xy = m.yx = 0; + for (i = 0; i < font->n_faces; ++i) + FT_Set_Transform(font->faces[i], &m, &font->v); } /** * \brief find a memory font by name */ -static int find_font(ass_library_t* library, char* name) +static int find_font(ASS_Library *library, char *name) { - int i; - for (i = 0; i < library->num_fontdata; ++i) - if (strcasecmp(name, library->fontdata[i].name) == 0) - return i; - return -1; + int i; + for (i = 0; i < library->num_fontdata; ++i) + if (strcasecmp(name, library->fontdata[i].name) == 0) + return i; + return -1; } static void face_set_size(FT_Face face, double size); static void buggy_font_workaround(FT_Face face) { - // Some fonts have zero Ascender/Descender fields in 'hhea' table. - // In this case, get the information from 'os2' table or, as - // a last resort, from face.bbox. - if (face->ascender + face->descender == 0 || face->height == 0) { - TT_OS2 *os2 = FT_Get_Sfnt_Table(face, ft_sfnt_os2); - if (os2) { - face->ascender = os2->sTypoAscender; - face->descender = os2->sTypoDescender; - face->height = face->ascender - face->descender; - } else { - face->ascender = face->bbox.yMax; - face->descender = face->bbox.yMin; - face->height = face->ascender - face->descender; - } - } + // Some fonts have zero Ascender/Descender fields in 'hhea' table. + // In this case, get the information from 'os2' table or, as + // a last resort, from face.bbox. + if (face->ascender + face->descender == 0 || face->height == 0) { + TT_OS2 *os2 = FT_Get_Sfnt_Table(face, ft_sfnt_os2); + if (os2) { + face->ascender = os2->sTypoAscender; + face->descender = os2->sTypoDescender; + face->height = face->ascender - face->descender; + } else { + face->ascender = face->bbox.yMax; + face->descender = face->bbox.yMin; + face->height = face->ascender - face->descender; + } + } } /** - * \brief Select a face with the given charcode and add it to ass_font_t + * \brief Select a face with the given charcode and add it to ASS_Font * \return index of the new face in font->faces, -1 if failed */ -static int add_face(void* fc_priv, ass_font_t* font, uint32_t ch) +static int add_face(void *fc_priv, ASS_Font *font, uint32_t ch) { - char* path; - int index; - FT_Face face; - int error; - int mem_idx; + char *path; + int index; + FT_Face face; + int error; + int mem_idx; - if (font->n_faces == ASS_FONT_MAX_FACES) - return -1; + if (font->n_faces == ASS_FONT_MAX_FACES) + return -1; - path = fontconfig_select(fc_priv, font->desc.family, font->desc.treat_family_as_pattern, font->desc.bold, - font->desc.italic, &index, ch); - if (!path) - return -1; + path = + fontconfig_select(font->library, fc_priv, font->desc.family, + font->desc.treat_family_as_pattern, + font->desc.bold, font->desc.italic, &index, ch); + if (!path) + return -1; - mem_idx = find_font(font->library, path); - if (mem_idx >= 0) { - error = FT_New_Memory_Face(font->ftlibrary, (unsigned char*)font->library->fontdata[mem_idx].data, - font->library->fontdata[mem_idx].size, 0, &face); - if (error) { - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_ErrorOpeningMemoryFont, path); - return -1; - } - } else { - error = FT_New_Face(font->ftlibrary, path, index, &face); - if (error) { - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_ErrorOpeningFont, path, index); - return -1; - } - } - charmap_magic(face); - buggy_font_workaround(face); + mem_idx = find_font(font->library, path); + if (mem_idx >= 0) { + error = + FT_New_Memory_Face(font->ftlibrary, + (unsigned char *) font->library-> + fontdata[mem_idx].data, + font->library->fontdata[mem_idx].size, 0, + &face); + if (error) { + ass_msg(font->library, MSGL_WARN, + "Error opening memory font: '%s'", path); + free(path); + return -1; + } + } else { + error = FT_New_Face(font->ftlibrary, path, index, &face); + if (error) { + ass_msg(font->library, MSGL_WARN, + "Error opening font: '%s', %d", path, index); + free(path); + return -1; + } + } + charmap_magic(font->library, face); + buggy_font_workaround(face); - font->faces[font->n_faces++] = face; - update_transform(font); - face_set_size(face, font->size); - return font->n_faces - 1; + font->faces[font->n_faces++] = face; + update_transform(font); + face_set_size(face, font->size); + free(path); + return font->n_faces - 1; } /** - * \brief Create a new ass_font_t according to "desc" argument + * \brief Create a new ASS_Font according to "desc" argument */ -ass_font_t* ass_font_new(ass_library_t* library, FT_Library ftlibrary, void* fc_priv, ass_font_desc_t* desc) +ASS_Font *ass_font_new(void *font_cache, ASS_Library *library, + FT_Library ftlibrary, void *fc_priv, + ASS_FontDesc *desc) { - int error; - ass_font_t* fontp; - ass_font_t font; + int error; + ASS_Font *fontp; + ASS_Font font; - fontp = ass_font_cache_find(desc); - if (fontp) - return fontp; + fontp = ass_font_cache_find((Hashmap *) font_cache, desc); + if (fontp) + return fontp; - font.library = library; - font.ftlibrary = ftlibrary; - font.n_faces = 0; - font.desc.family = strdup(desc->family); - font.desc.treat_family_as_pattern = desc->treat_family_as_pattern; - font.desc.bold = desc->bold; - font.desc.italic = desc->italic; + font.library = library; + font.ftlibrary = ftlibrary; + font.n_faces = 0; + font.desc.family = strdup(desc->family); + font.desc.treat_family_as_pattern = desc->treat_family_as_pattern; + font.desc.bold = desc->bold; + font.desc.italic = desc->italic; - font.scale_x = font.scale_y = 1.; - font.v.x = font.v.y = 0; - font.size = 0.; + font.scale_x = font.scale_y = 1.; + font.v.x = font.v.y = 0; + font.size = 0.; - error = add_face(fc_priv, &font, 0); - if (error == -1) { - free(font.desc.family); - return 0; - } else - return ass_font_cache_add(&font); + error = add_face(fc_priv, &font, 0); + if (error == -1) { + free(font.desc.family); + return 0; + } else + return ass_font_cache_add((Hashmap *) font_cache, &font); } /** * \brief Set font transformation matrix and shift vector **/ -void ass_font_set_transform(ass_font_t* font, double scale_x, double scale_y, FT_Vector* v) +void ass_font_set_transform(ASS_Font *font, double scale_x, + double scale_y, FT_Vector *v) { - font->scale_x = scale_x; - font->scale_y = scale_y; - font->v.x = v->x; - font->v.y = v->y; - update_transform(font); + font->scale_x = scale_x; + font->scale_y = scale_y; + if (v) { + font->v.x = v->x; + font->v.y = v->y; + } + update_transform(font); } static void face_set_size(FT_Face face, double size) { -#if (FREETYPE_MAJOR > 2) || ((FREETYPE_MAJOR == 2) && (FREETYPE_MINOR > 1)) - TT_HoriHeader *hori = FT_Get_Sfnt_Table(face, ft_sfnt_hhea); - TT_OS2 *os2 = FT_Get_Sfnt_Table(face, ft_sfnt_os2); - double mscale = 1.; - FT_Size_RequestRec rq; - FT_Size_Metrics *m = &face->size->metrics; - // VSFilter uses metrics from TrueType OS/2 table - // The idea was borrowed from asa (http://asa.diac24.net) - if (hori && os2) { - int hori_height = hori->Ascender - hori->Descender; - int os2_height = os2->usWinAscent + os2->usWinDescent; - if (hori_height && os2_height) - mscale = (double)hori_height / os2_height; - } - memset(&rq, 0, sizeof(rq)); - rq.type = FT_SIZE_REQUEST_TYPE_REAL_DIM; - rq.width = 0; - rq.height = double_to_d6(size * mscale); - rq.horiResolution = rq.vertResolution = 0; - FT_Request_Size(face, &rq); - m->ascender /= mscale; - m->descender /= mscale; - m->height /= mscale; -#else - FT_Set_Char_Size(face, 0, double_to_d6(size), 0, 0); -#endif + TT_HoriHeader *hori = FT_Get_Sfnt_Table(face, ft_sfnt_hhea); + TT_OS2 *os2 = FT_Get_Sfnt_Table(face, ft_sfnt_os2); + double mscale = 1.; + FT_Size_RequestRec rq; + FT_Size_Metrics *m = &face->size->metrics; + // VSFilter uses metrics from TrueType OS/2 table + // The idea was borrowed from asa (http://asa.diac24.net) + if (hori && os2) { + int hori_height = hori->Ascender - hori->Descender; + int os2_height = os2->usWinAscent + os2->usWinDescent; + if (hori_height && os2_height) + mscale = (double) hori_height / os2_height; + } + memset(&rq, 0, sizeof(rq)); + rq.type = FT_SIZE_REQUEST_TYPE_REAL_DIM; + rq.width = 0; + rq.height = double_to_d6(size * mscale); + rq.horiResolution = rq.vertResolution = 0; + FT_Request_Size(face, &rq); + m->ascender /= mscale; + m->descender /= mscale; + m->height /= mscale; } /** * \brief Set font size **/ -void ass_font_set_size(ass_font_t* font, double size) +void ass_font_set_size(ASS_Font *font, double size) { - int i; - if (font->size != size) { - font->size = size; - for (i = 0; i < font->n_faces; ++i) - face_set_size(font->faces[i], size); - } + int i; + if (font->size != size) { + font->size = size; + for (i = 0; i < font->n_faces; ++i) + face_set_size(font->faces[i], size); + } } /** @@ -247,125 +260,273 @@ * \param ch character code * The values are extracted from the font face that provides glyphs for the given character **/ -void ass_font_get_asc_desc(ass_font_t* font, uint32_t ch, int* asc, int* desc) +void ass_font_get_asc_desc(ASS_Font *font, uint32_t ch, int *asc, + int *desc) +{ + int i; + for (i = 0; i < font->n_faces; ++i) { + FT_Face face = font->faces[i]; + TT_OS2 *os2 = FT_Get_Sfnt_Table(face, ft_sfnt_os2); + if (FT_Get_Char_Index(face, ch)) { + int y_scale = face->size->metrics.y_scale; + if (os2) { + *asc = FT_MulFix(os2->usWinAscent, y_scale); + *desc = FT_MulFix(os2->usWinDescent, y_scale); + } else { + *asc = FT_MulFix(face->ascender, y_scale); + *desc = FT_MulFix(-face->descender, y_scale); + } + return; + } + } + + *asc = *desc = 0; +} + +/* + * Strike a glyph with a horizontal line; it's possible to underline it + * and/or strike through it. For the line's position and size, truetype + * tables are consulted. Obviously this relies on the data in the tables + * being accurate. + * + */ +static int ass_strike_outline_glyph(FT_Face face, ASS_Font *font, + FT_Glyph glyph, int under, int through) { - int i; - for (i = 0; i < font->n_faces; ++i) { - FT_Face face = font->faces[i]; - if (FT_Get_Char_Index(face, ch)) { - *asc = face->size->metrics.ascender; - *desc = - face->size->metrics.descender; - return; - } - } + TT_OS2 *os2 = FT_Get_Sfnt_Table(face, ft_sfnt_os2); + TT_Postscript *ps = FT_Get_Sfnt_Table(face, ft_sfnt_post); + FT_Outline *ol = &((FT_OutlineGlyph) glyph)->outline; + int bear, advance, y_scale, i, dir; + + if (!under && !through) + return 0; + + // Grow outline + i = (under ? 4 : 0) + (through ? 4 : 0); + ol->points = realloc(ol->points, sizeof(FT_Vector) * + (ol->n_points + i)); + ol->tags = realloc(ol->tags, ol->n_points + i); + i = !!under + !!through; + ol->contours = realloc(ol->contours, sizeof(short) * + (ol->n_contours + i)); + + // If the bearing is negative, the glyph starts left of the current + // pen position + bear = FFMIN(face->glyph->metrics.horiBearingX, 0); + // We're adding half a pixel to avoid small gaps + advance = d16_to_d6(glyph->advance.x) + 32; + y_scale = face->size->metrics.y_scale; + + // Reverse drawing direction for non-truetype fonts + dir = FT_Outline_Get_Orientation(ol); + + // Add points to the outline + if (under && ps) { + int pos, size; + pos = FT_MulFix(ps->underlinePosition, y_scale * font->scale_y); + size = FT_MulFix(ps->underlineThickness, + y_scale * font->scale_y / 2); + + if (pos > 0 || size <= 0) + return 1; + + FT_Vector points[4] = { + {.x = bear, .y = pos + size}, + {.x = advance, .y = pos + size}, + {.x = advance, .y = pos - size}, + {.x = bear, .y = pos - size}, + }; - *asc = *desc = 0; + if (dir == FT_ORIENTATION_TRUETYPE) { + for (i = 0; i < 4; i++) { + ol->points[ol->n_points] = points[i]; + ol->tags[ol->n_points++] = 1; + } + } else { + for (i = 3; i >= 0; i--) { + ol->points[ol->n_points] = points[i]; + ol->tags[ol->n_points++] = 1; + } + } + + ol->contours[ol->n_contours++] = ol->n_points - 1; + } + + if (through && os2) { + int pos, size; + pos = FT_MulFix(os2->yStrikeoutPosition, y_scale * font->scale_y); + size = FT_MulFix(os2->yStrikeoutSize, y_scale * font->scale_y / 2); + + if (pos < 0 || size <= 0) + return 1; + + FT_Vector points[4] = { + {.x = bear, .y = pos + size}, + {.x = advance, .y = pos + size}, + {.x = advance, .y = pos - size}, + {.x = bear, .y = pos - size}, + }; + + if (dir == FT_ORIENTATION_TRUETYPE) { + for (i = 0; i < 4; i++) { + ol->points[ol->n_points] = points[i]; + ol->tags[ol->n_points++] = 1; + } + } else { + for (i = 3; i >= 0; i--) { + ol->points[ol->n_points] = points[i]; + ol->tags[ol->n_points++] = 1; + } + } + + ol->contours[ol->n_contours++] = ol->n_points - 1; + } + + return 0; +} + +/** + * Slightly embold a glyph without touching its metrics + */ +static void ass_glyph_embolden(FT_GlyphSlot slot) +{ + int str; + + if (slot->format != FT_GLYPH_FORMAT_OUTLINE) + return; + + str = FT_MulFix(slot->face->units_per_EM, + slot->face->size->metrics.y_scale) / 64; + + FT_Outline_Embolden(&slot->outline, str); } /** * \brief Get a glyph * \param ch character code **/ -FT_Glyph ass_font_get_glyph(void* fontconfig_priv, ass_font_t* font, uint32_t ch, ass_hinting_t hinting) +FT_Glyph ass_font_get_glyph(void *fontconfig_priv, ASS_Font *font, + uint32_t ch, ASS_Hinting hinting, int deco) { - int error; - int index = 0; - int i; - FT_Glyph glyph; - FT_Face face = 0; - int flags = 0; + int error; + int index = 0; + int i; + FT_Glyph glyph; + FT_Face face = 0; + int flags = 0; - if (ch < 0x20) - return 0; - if (font->n_faces == 0) - return 0; + if (ch < 0x20) + return 0; + // Handle NBSP like a regular space when rendering the glyph + if (ch == 0xa0) + ch = ' '; + if (font->n_faces == 0) + return 0; - for (i = 0; i < font->n_faces; ++i) { - face = font->faces[i]; - index = FT_Get_Char_Index(face, ch); - if (index) - break; - } + for (i = 0; i < font->n_faces; ++i) { + face = font->faces[i]; + index = FT_Get_Char_Index(face, ch); + if (index) + break; + } #ifdef CONFIG_FONTCONFIG - if (index == 0) { - int face_idx; - mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_GlyphNotFoundReselectingFont, - ch, font->desc.family, font->desc.bold, font->desc.italic); - face_idx = add_face(fontconfig_priv, font, ch); - if (face_idx >= 0) { - face = font->faces[face_idx]; - index = FT_Get_Char_Index(face, ch); - if (index == 0) { - mp_msg(MSGT_ASS, MSGL_ERR, MSGTR_LIBASS_GlyphNotFound, - ch, font->desc.family, font->desc.bold, font->desc.italic); - } - } - } + if (index == 0) { + int face_idx; + ass_msg(font->library, MSGL_INFO, + "Glyph 0x%X not found, selecting one more " + "font for (%s, %d, %d)", ch, font->desc.family, + font->desc.bold, font->desc.italic); + face_idx = add_face(fontconfig_priv, font, ch); + if (face_idx >= 0) { + face = font->faces[face_idx]; + index = FT_Get_Char_Index(face, ch); + if (index == 0) { + ass_msg(font->library, MSGL_ERR, + "Glyph 0x%X not found in font for (%s, %d, %d)", + ch, font->desc.family, font->desc.bold, + font->desc.italic); + } + } + } #endif - switch (hinting) { - case ASS_HINTING_NONE: flags = FT_LOAD_NO_HINTING; break; - case ASS_HINTING_LIGHT: flags = FT_LOAD_FORCE_AUTOHINT | FT_LOAD_TARGET_LIGHT; break; - case ASS_HINTING_NORMAL: flags = FT_LOAD_FORCE_AUTOHINT; break; - case ASS_HINTING_NATIVE: flags = 0; break; - } - - error = FT_Load_Glyph(face, index, FT_LOAD_NO_BITMAP | flags); - if (error) { - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_ErrorLoadingGlyph); - return 0; - } + switch (hinting) { + case ASS_HINTING_NONE: + flags = FT_LOAD_NO_HINTING; + break; + case ASS_HINTING_LIGHT: + flags = FT_LOAD_FORCE_AUTOHINT | FT_LOAD_TARGET_LIGHT; + break; + case ASS_HINTING_NORMAL: + flags = FT_LOAD_FORCE_AUTOHINT; + break; + case ASS_HINTING_NATIVE: + flags = 0; + break; + } -#if (FREETYPE_MAJOR > 2) || \ - ((FREETYPE_MAJOR == 2) && (FREETYPE_MINOR >= 2)) || \ - ((FREETYPE_MAJOR == 2) && (FREETYPE_MINOR == 1) && (FREETYPE_PATCH >= 10)) -// FreeType >= 2.1.10 required - if (!(face->style_flags & FT_STYLE_FLAG_ITALIC) && - (font->desc.italic > 55)) { - FT_GlyphSlot_Oblique(face->glyph); - } -#endif - error = FT_Get_Glyph(face->glyph, &glyph); - if (error) { - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_ErrorLoadingGlyph); - return 0; - } + error = FT_Load_Glyph(face, index, FT_LOAD_NO_BITMAP | flags); + if (error) { + ass_msg(font->library, MSGL_WARN, "Error loading glyph, index %d", + index); + return 0; + } + if (!(face->style_flags & FT_STYLE_FLAG_ITALIC) && + (font->desc.italic > 55)) { + FT_GlyphSlot_Oblique(face->glyph); + } - return glyph; + if (!(face->style_flags & FT_STYLE_FLAG_BOLD) && + (font->desc.bold > 80)) { + ass_glyph_embolden(face->glyph); + } + error = FT_Get_Glyph(face->glyph, &glyph); + if (error) { + ass_msg(font->library, MSGL_WARN, "Error loading glyph, index %d", + index); + return 0; + } + + ass_strike_outline_glyph(face, font, glyph, deco & DECO_UNDERLINE, + deco & DECO_STRIKETHROUGH); + + return glyph; } /** * \brief Get kerning for the pair of glyphs. **/ -FT_Vector ass_font_get_kerning(ass_font_t* font, uint32_t c1, uint32_t c2) +FT_Vector ass_font_get_kerning(ASS_Font *font, uint32_t c1, uint32_t c2) { - FT_Vector v = {0, 0}; - int i; + FT_Vector v = { 0, 0 }; + int i; - for (i = 0; i < font->n_faces; ++i) { - FT_Face face = font->faces[i]; - int i1 = FT_Get_Char_Index(face, c1); - int i2 = FT_Get_Char_Index(face, c2); - if (i1 && i2) { - if (FT_HAS_KERNING(face)) - FT_Get_Kerning(face, i1, i2, FT_KERNING_DEFAULT, &v); - return v; - } - if (i1 || i2) // these glyphs are from different font faces, no kerning information - return v; - } - return v; + for (i = 0; i < font->n_faces; ++i) { + FT_Face face = font->faces[i]; + int i1 = FT_Get_Char_Index(face, c1); + int i2 = FT_Get_Char_Index(face, c2); + if (i1 && i2) { + if (FT_HAS_KERNING(face)) + FT_Get_Kerning(face, i1, i2, FT_KERNING_DEFAULT, &v); + return v; + } + if (i1 || i2) // these glyphs are from different font faces, no kerning information + return v; + } + return v; } /** - * \brief Deallocate ass_font_t + * \brief Deallocate ASS_Font **/ -void ass_font_free(ass_font_t* font) +void ass_font_free(ASS_Font *font) { - int i; - for (i = 0; i < font->n_faces; ++i) - if (font->faces[i]) FT_Done_Face(font->faces[i]); - if (font->desc.family) free(font->desc.family); - free(font); + int i; + for (i = 0; i < font->n_faces; ++i) + if (font->faces[i]) + FT_Done_Face(font->faces[i]); + if (font->desc.family) + free(font->desc.family); + free(font); }
--- a/libass/ass_font.h Fri Jan 08 18:19:10 2010 +0000 +++ b/libass/ass_font.h Fri Jan 08 18:35:44 2010 +0000 @@ -1,5 +1,3 @@ -// -*- c-basic-offset: 8; indent-tabs-mode: t -*- -// vim:ts=8:sw=8:noet:ai: /* * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com> * @@ -29,32 +27,40 @@ #include "ass.h" #include "ass_types.h" -typedef struct ass_font_desc_s { - char* family; - unsigned bold; - unsigned italic; - int treat_family_as_pattern; -} ass_font_desc_t; +#define ASS_FONT_MAX_FACES 10 +#define DECO_UNDERLINE 1 +#define DECO_STRIKETHROUGH 2 -#define ASS_FONT_MAX_FACES 10 +typedef struct { + char *family; + unsigned bold; + unsigned italic; + int treat_family_as_pattern; +} ASS_FontDesc; -typedef struct ass_font_s { - ass_font_desc_t desc; - ass_library_t* library; - FT_Library ftlibrary; - FT_Face faces[ASS_FONT_MAX_FACES]; - int n_faces; - double scale_x, scale_y; // current transform - FT_Vector v; // current shift - double size; -} ass_font_t; +typedef struct { + ASS_FontDesc desc; + ASS_Library *library; + FT_Library ftlibrary; + FT_Face faces[ASS_FONT_MAX_FACES]; + int n_faces; + double scale_x, scale_y; // current transform + FT_Vector v; // current shift + double size; +} ASS_Font; -ass_font_t* ass_font_new(ass_library_t* library, FT_Library ftlibrary, void* fc_priv, ass_font_desc_t* desc); -void ass_font_set_transform(ass_font_t* font, double scale_x, double scale_y, FT_Vector* v); -void ass_font_set_size(ass_font_t* font, double size); -void ass_font_get_asc_desc(ass_font_t* font, uint32_t ch, int* asc, int* desc); -FT_Glyph ass_font_get_glyph(void* fontconfig_priv, ass_font_t* font, uint32_t ch, ass_hinting_t hinting); -FT_Vector ass_font_get_kerning(ass_font_t* font, uint32_t c1, uint32_t c2); -void ass_font_free(ass_font_t* font); +// FIXME: passing the hashmap via a void pointer is very ugly. +ASS_Font *ass_font_new(void *font_cache, ASS_Library *library, + FT_Library ftlibrary, void *fc_priv, + ASS_FontDesc *desc); +void ass_font_set_transform(ASS_Font *font, double scale_x, + double scale_y, FT_Vector *v); +void ass_font_set_size(ASS_Font *font, double size); +void ass_font_get_asc_desc(ASS_Font *font, uint32_t ch, int *asc, + int *desc); +FT_Glyph ass_font_get_glyph(void *fontconfig_priv, ASS_Font *font, + uint32_t ch, ASS_Hinting hinting, int flags); +FT_Vector ass_font_get_kerning(ASS_Font *font, uint32_t c1, uint32_t c2); +void ass_font_free(ASS_Font *font); -#endif /* LIBASS_FONT_H */ +#endif /* LIBASS_FONT_H */
--- a/libass/ass_fontconfig.c Fri Jan 08 18:19:10 2010 +0000 +++ b/libass/ass_fontconfig.c Fri Jan 08 18:35:44 2010 +0000 @@ -1,5 +1,3 @@ -// -*- c-basic-offset: 8; indent-tabs-mode: t -*- -// vim:ts=8:sw=8:noet:ai: /* * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com> * @@ -32,7 +30,7 @@ #include <ft2build.h> #include FT_FREETYPE_H -#include "mputils.h" +#include "ass_utils.h" #include "ass.h" #include "ass_library.h" #include "ass_fontconfig.h" @@ -42,26 +40,17 @@ #include <fontconfig/fcfreetype.h> #endif -struct fc_instance_s { +struct fc_instance { #ifdef CONFIG_FONTCONFIG - FcConfig* config; + FcConfig *config; #endif - char* family_default; - char* path_default; - int index_default; + char *family_default; + char *path_default; + int index_default; }; #ifdef CONFIG_FONTCONFIG -// 4yo fontconfig does not have these. -// They are only needed for debug output, anyway. -#ifndef FC_FULLNAME -#define FC_FULLNAME "fullname" -#endif -#ifndef FC_EMBOLDEN -#define FC_EMBOLDEN "embolden" -#endif - /** * \brief Low-level font selection. * \param priv private data @@ -73,156 +62,162 @@ * \param code: the character that should be present in the font, can be 0 * \return font file path */ -static char* _select_font(fc_instance_t* priv, const char* family, int treat_family_as_pattern, - unsigned bold, unsigned italic, int* index, uint32_t code) +static char *_select_font(ASS_Library *library, FCInstance *priv, + const char *family, int treat_family_as_pattern, + unsigned bold, unsigned italic, int *index, + uint32_t code) { - FcBool rc; - FcResult result; - FcPattern *pat = NULL, *rpat = NULL; - int r_index, r_slant, r_weight; - FcChar8 *r_family, *r_style, *r_file, *r_fullname; - FcBool r_outline, r_embolden; - FcCharSet* r_charset; - FcFontSet* fset = NULL; - int curf; - char* retval = NULL; - int family_cnt; + FcBool rc; + FcResult result; + FcPattern *pat = NULL, *rpat = NULL; + int r_index, r_slant, r_weight; + FcChar8 *r_family, *r_style, *r_file, *r_fullname; + FcBool r_outline, r_embolden; + FcCharSet *r_charset; + FcFontSet *fset = NULL; + int curf; + char *retval = NULL; + int family_cnt = 0; - *index = 0; + *index = 0; - if (treat_family_as_pattern) - pat = FcNameParse((const FcChar8*)family); - else - pat = FcPatternCreate(); + if (treat_family_as_pattern) + pat = FcNameParse((const FcChar8 *) family); + else + pat = FcPatternCreate(); - if (!pat) - goto error; + if (!pat) + goto error; - if (!treat_family_as_pattern) { - FcPatternAddString(pat, FC_FAMILY, (const FcChar8*)family); + if (!treat_family_as_pattern) { + FcPatternAddString(pat, FC_FAMILY, (const FcChar8 *) family); - // In SSA/ASS fonts are sometimes referenced by their "full name", - // which is usually a concatenation of family name and font - // style (ex. Ottawa Bold). Full name is available from - // FontConfig pattern element FC_FULLNAME, but it is never - // used for font matching. - // Therefore, I'm removing words from the end of the name one - // by one, and adding shortened names to the pattern. It seems - // that the first value (full name in this case) has - // precedence in matching. - // An alternative approach could be to reimplement FcFontSort - // using FC_FULLNAME instead of FC_FAMILY. - family_cnt = 1; - { - char* s = strdup(family); - char* p = s + strlen(s); - while (--p > s) - if (*p == ' ' || *p == '-') { - *p = '\0'; - FcPatternAddString(pat, FC_FAMILY, (const FcChar8*)s); - ++ family_cnt; - } - free(s); - } - } - FcPatternAddBool(pat, FC_OUTLINE, FcTrue); - FcPatternAddInteger(pat, FC_SLANT, italic); - FcPatternAddInteger(pat, FC_WEIGHT, bold); + // In SSA/ASS fonts are sometimes referenced by their "full name", + // which is usually a concatenation of family name and font + // style (ex. Ottawa Bold). Full name is available from + // FontConfig pattern element FC_FULLNAME, but it is never + // used for font matching. + // Therefore, I'm removing words from the end of the name one + // by one, and adding shortened names to the pattern. It seems + // that the first value (full name in this case) has + // precedence in matching. + // An alternative approach could be to reimplement FcFontSort + // using FC_FULLNAME instead of FC_FAMILY. + family_cnt = 1; + { + char *s = strdup(family); + char *p = s + strlen(s); + while (--p > s) + if (*p == ' ' || *p == '-') { + *p = '\0'; + FcPatternAddString(pat, FC_FAMILY, (const FcChar8 *) s); + ++family_cnt; + } + free(s); + } + } + FcPatternAddBool(pat, FC_OUTLINE, FcTrue); + FcPatternAddInteger(pat, FC_SLANT, italic); + FcPatternAddInteger(pat, FC_WEIGHT, bold); - FcDefaultSubstitute(pat); + FcDefaultSubstitute(pat); - rc = FcConfigSubstitute(priv->config, pat, FcMatchPattern); - if (!rc) - goto error; + rc = FcConfigSubstitute(priv->config, pat, FcMatchPattern); + if (!rc) + goto error; - fset = FcFontSort(priv->config, pat, FcTrue, NULL, &result); - if (!fset) - goto error; + fset = FcFontSort(priv->config, pat, FcTrue, NULL, &result); + if (!fset) + goto error; - for (curf = 0; curf < fset->nfont; ++curf) { - FcPattern* curp = fset->fonts[curf]; + for (curf = 0; curf < fset->nfont; ++curf) { + FcPattern *curp = fset->fonts[curf]; - result = FcPatternGetBool(curp, FC_OUTLINE, 0, &r_outline); - if (result != FcResultMatch) - continue; - if (r_outline != FcTrue) - continue; - if (!code) - break; - result = FcPatternGetCharSet(curp, FC_CHARSET, 0, &r_charset); - if (result != FcResultMatch) - continue; - if (FcCharSetHasChar(r_charset, code)) - break; - } + result = FcPatternGetBool(curp, FC_OUTLINE, 0, &r_outline); + if (result != FcResultMatch) + continue; + if (r_outline != FcTrue) + continue; + if (!code) + break; + result = FcPatternGetCharSet(curp, FC_CHARSET, 0, &r_charset); + if (result != FcResultMatch) + continue; + if (FcCharSetHasChar(r_charset, code)) + break; + } - if (curf >= fset->nfont) - goto error; + if (curf >= fset->nfont) + goto error; -#if (FC_VERSION >= 20297) - if (!treat_family_as_pattern) { - // Remove all extra family names from original pattern. - // After this, FcFontRenderPrepare will select the most relevant family - // name in case there are more than one of them. - for (; family_cnt > 1; --family_cnt) - FcPatternRemove(pat, FC_FAMILY, family_cnt - 1); - } -#endif + if (!treat_family_as_pattern) { + // Remove all extra family names from original pattern. + // After this, FcFontRenderPrepare will select the most relevant family + // name in case there are more than one of them. + for (; family_cnt > 1; --family_cnt) + FcPatternRemove(pat, FC_FAMILY, family_cnt - 1); + } + + rpat = FcFontRenderPrepare(priv->config, pat, fset->fonts[curf]); + if (!rpat) + goto error; - rpat = FcFontRenderPrepare(priv->config, pat, fset->fonts[curf]); - if (!rpat) - goto error; + result = FcPatternGetInteger(rpat, FC_INDEX, 0, &r_index); + if (result != FcResultMatch) + goto error; + *index = r_index; - result = FcPatternGetInteger(rpat, FC_INDEX, 0, &r_index); - if (result != FcResultMatch) - goto error; - *index = r_index; + result = FcPatternGetString(rpat, FC_FILE, 0, &r_file); + if (result != FcResultMatch) + goto error; + retval = strdup((const char *) r_file); - result = FcPatternGetString(rpat, FC_FILE, 0, &r_file); - if (result != FcResultMatch) - goto error; - retval = strdup((const char*)r_file); + result = FcPatternGetString(rpat, FC_FAMILY, 0, &r_family); + if (result != FcResultMatch) + r_family = NULL; - result = FcPatternGetString(rpat, FC_FAMILY, 0, &r_family); - if (result != FcResultMatch) - r_family = NULL; - - result = FcPatternGetString(rpat, FC_FULLNAME, 0, &r_fullname); - if (result != FcResultMatch) - r_fullname = NULL; + result = FcPatternGetString(rpat, FC_FULLNAME, 0, &r_fullname); + if (result != FcResultMatch) + r_fullname = NULL; - if (!treat_family_as_pattern && - !(r_family && strcasecmp((const char*)r_family, family) == 0) && - !(r_fullname && strcasecmp((const char*)r_fullname, family) == 0)) - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_SelectedFontFamilyIsNotTheRequestedOne, - (const char*)(r_fullname ? r_fullname : r_family), family); + if (!treat_family_as_pattern && + !(r_family && strcasecmp((const char *) r_family, family) == 0) && + !(r_fullname && strcasecmp((const char *) r_fullname, family) == 0)) + ass_msg(library, MSGL_WARN, + "fontconfig: Selected font is not the requested one: " + "'%s' != '%s'", + (const char *) (r_fullname ? r_fullname : r_family), family); - result = FcPatternGetString(rpat, FC_STYLE, 0, &r_style); - if (result != FcResultMatch) - r_style = NULL; + result = FcPatternGetString(rpat, FC_STYLE, 0, &r_style); + if (result != FcResultMatch) + r_style = NULL; + + result = FcPatternGetInteger(rpat, FC_SLANT, 0, &r_slant); + if (result != FcResultMatch) + r_slant = 0; - result = FcPatternGetInteger(rpat, FC_SLANT, 0, &r_slant); - if (result != FcResultMatch) - r_slant = 0; + result = FcPatternGetInteger(rpat, FC_WEIGHT, 0, &r_weight); + if (result != FcResultMatch) + r_weight = 0; - result = FcPatternGetInteger(rpat, FC_WEIGHT, 0, &r_weight); - if (result != FcResultMatch) - r_weight = 0; + result = FcPatternGetBool(rpat, FC_EMBOLDEN, 0, &r_embolden); + if (result != FcResultMatch) + r_embolden = 0; - result = FcPatternGetBool(rpat, FC_EMBOLDEN, 0, &r_embolden); - if (result != FcResultMatch) - r_embolden = 0; + ass_msg(library, MSGL_V, + "Font info: family '%s', style '%s', fullname '%s'," + " slant %d, weight %d%s", (const char *) r_family, + (const char *) r_style, (const char *) r_fullname, r_slant, + r_weight, r_embolden ? ", embolden" : ""); - mp_msg(MSGT_ASS, MSGL_V, "[ass] Font info: family '%s', style '%s', fullname '%s'," - " slant %d, weight %d%s\n", - (const char*)r_family, (const char*)r_style, (const char*)r_fullname, - r_slant, r_weight, r_embolden ? ", embolden" : ""); - - error: - if (pat) FcPatternDestroy(pat); - if (rpat) FcPatternDestroy(rpat); - if (fset) FcFontSetDestroy(fset); - return retval; + error: + if (pat) + FcPatternDestroy(pat); + if (rpat) + FcPatternDestroy(rpat); + if (fset) + FcFontSetDestroy(fset); + return retval; } /** @@ -236,78 +231,52 @@ * \param code: the character that should be present in the font, can be 0 * \return font file path */ -char* fontconfig_select(fc_instance_t* priv, const char* family, int treat_family_as_pattern, - unsigned bold, unsigned italic, int* index, uint32_t code) +char *fontconfig_select(ASS_Library *library, FCInstance *priv, + const char *family, int treat_family_as_pattern, + unsigned bold, unsigned italic, int *index, + uint32_t code) { - char* res = 0; - if (!priv->config) { - *index = priv->index_default; - return priv->path_default; - } - if (family && *family) - res = _select_font(priv, family, treat_family_as_pattern, bold, italic, index, code); - if (!res && priv->family_default) { - res = _select_font(priv, priv->family_default, 0, bold, italic, index, code); - if (res) - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_UsingDefaultFontFamily, - family, bold, italic, res, *index); - } - if (!res && priv->path_default) { - res = priv->path_default; - *index = priv->index_default; - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_UsingDefaultFont, - family, bold, italic, res, *index); - } - if (!res) { - res = _select_font(priv, "Arial", 0, bold, italic, index, code); - if (res) - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_UsingArialFontFamily, - family, bold, italic, res, *index); - } - if (res) - mp_msg(MSGT_ASS, MSGL_V, "fontconfig_select: (%s, %d, %d) -> %s, %d\n", - family, bold, italic, res, *index); - return res; + char *res = 0; + if (!priv->config) { + *index = priv->index_default; + res = priv->path_default ? strdup(priv->path_default) : 0; + return res; + } + if (family && *family) + res = + _select_font(library, priv, family, treat_family_as_pattern, + bold, italic, index, code); + if (!res && priv->family_default) { + res = + _select_font(library, priv, priv->family_default, 0, bold, + italic, index, code); + if (res) + ass_msg(library, MSGL_WARN, "fontconfig_select: Using default " + "font family: (%s, %d, %d) -> %s, %d", + family, bold, italic, res, *index); + } + if (!res && priv->path_default) { + res = strdup(priv->path_default); + *index = priv->index_default; + ass_msg(library, MSGL_WARN, "fontconfig_select: Using default font: " + "(%s, %d, %d) -> %s, %d", family, bold, italic, + res, *index); + } + if (!res) { + res = _select_font(library, priv, "Arial", 0, bold, italic, + index, code); + if (res) + ass_msg(library, MSGL_WARN, "fontconfig_select: Using 'Arial' " + "font family: (%s, %d, %d) -> %s, %d", family, bold, + italic, res, *index); + } + if (res) + ass_msg(library, MSGL_V, + "fontconfig_select: (%s, %d, %d) -> %s, %d", family, bold, + italic, res, *index); + return res; } -#if (FC_VERSION < 20402) -static char* validate_fname(char* name) -{ - char* fname; - char* p; - char* q; - unsigned code; - int sz = strlen(name); - - q = fname = malloc(sz + 1); - p = name; - while (*p) { - code = utf8_get_char(&p); - if (code == 0) - break; - if ( (code > 0x7F) || - (code == '\\') || - (code == '/') || - (code == ':') || - (code == '*') || - (code == '?') || - (code == '<') || - (code == '>') || - (code == '|') || - (code == 0)) - { - *q++ = '_'; - } else { - *q++ = code; - } - if (p - name > sz) - break; - } - *q = 0; - return fname; -} -#endif - /** * \brief Process memory font. * \param priv private data @@ -317,87 +286,55 @@ * With FontConfig >= 2.4.2, builds a font pattern in memory via FT_New_Memory_Face/FcFreeTypeQueryFace. * With older FontConfig versions, save the font to ~/.mplayer/fonts. */ -static void process_fontdata(fc_instance_t* priv, ass_library_t* library, FT_Library ftlibrary, int idx) +static void process_fontdata(FCInstance *priv, ASS_Library *library, + FT_Library ftlibrary, int idx) { - int rc; - const char* name = library->fontdata[idx].name; - const char* data = library->fontdata[idx].data; - int data_size = library->fontdata[idx].size; - -#if (FC_VERSION < 20402) - struct stat st; - char* fname; - const char* fonts_dir = library->fonts_dir; - char buf[1000]; - FILE* fp = NULL; + int rc; + const char *name = library->fontdata[idx].name; + const char *data = library->fontdata[idx].data; + int data_size = library->fontdata[idx].size; - if (!fonts_dir) - return; - rc = stat(fonts_dir, &st); - if (rc) { - int res; -#ifndef __MINGW32__ - res = mkdir(fonts_dir, 0700); -#else - res = mkdir(fonts_dir); -#endif - if (res) { - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FailedToCreateDirectory, fonts_dir); - } - } else if (!S_ISDIR(st.st_mode)) { - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_NotADirectory, fonts_dir); - } + FT_Face face; + FcPattern *pattern; + FcFontSet *fset; + FcBool res; + int face_index, num_faces = 1; - fname = validate_fname((char*)name); - - snprintf(buf, 1000, "%s/%s", fonts_dir, fname); - free(fname); - - fp = fopen(buf, "wb"); - if (!fp) return; + for (face_index = 0; face_index < num_faces; ++face_index) { + rc = FT_New_Memory_Face(ftlibrary, (unsigned char *) data, + data_size, face_index, &face); + if (rc) { + ass_msg(library, MSGL_WARN, "Error opening memory font: %s", + name); + return; + } + num_faces = face->num_faces; - fwrite(data, data_size, 1, fp); - fclose(fp); - -#else // (FC_VERSION >= 20402) - FT_Face face; - FcPattern* pattern; - FcFontSet* fset; - FcBool res; - int face_index, num_faces = 1; - - for (face_index = 0; face_index < num_faces; ++face_index) { - rc = FT_New_Memory_Face(ftlibrary, (unsigned char*)data, data_size, face_index, &face); - if (rc) { - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_ErrorOpeningMemoryFont, name); - return; - } - num_faces = face->num_faces; + pattern = + FcFreeTypeQueryFace(face, (unsigned char *) name, 0, + FcConfigGetBlanks(priv->config)); + if (!pattern) { + ass_msg(library, MSGL_WARN, "%s failed", "FcFreeTypeQueryFace"); + FT_Done_Face(face); + return; + } - pattern = FcFreeTypeQueryFace(face, (unsigned char*)name, 0, FcConfigGetBlanks(priv->config)); - if (!pattern) { - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FunctionCallFailed, "FcFreeTypeQueryFace"); - FT_Done_Face(face); - return; - } + fset = FcConfigGetFonts(priv->config, FcSetSystem); // somehow it failes when asked for FcSetApplication + if (!fset) { + ass_msg(library, MSGL_WARN, "%s failed", "FcConfigGetFonts"); + FT_Done_Face(face); + return; + } - fset = FcConfigGetFonts(priv->config, FcSetSystem); // somehow it failes when asked for FcSetApplication - if (!fset) { - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FunctionCallFailed, "FcConfigGetFonts"); - FT_Done_Face(face); - return; - } + res = FcFontSetAdd(fset, pattern); + if (!res) { + ass_msg(library, MSGL_WARN, "%s failed", "FcFontSetAdd"); + FT_Done_Face(face); + return; + } - res = FcFontSetAdd(fset, pattern); - if (!res) { - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FunctionCallFailed, "FcFontSetAdd"); - FT_Done_Face(face); - return; - } - - FT_Done_Face(face); - } -#endif + FT_Done_Face(face); + } } /** @@ -406,113 +343,119 @@ * \param ftlibrary freetype library object * \param family default font family * \param path default font path + * \param fc whether fontconfig should be used + * \param config path to a fontconfig configuration file, or NULL + * \param update whether the fontconfig cache should be built/updated * \return pointer to fontconfig private data */ -fc_instance_t* fontconfig_init(ass_library_t* library, FT_Library ftlibrary, const char* family, const char* path, int fc) +FCInstance *fontconfig_init(ASS_Library *library, + FT_Library ftlibrary, const char *family, + const char *path, int fc, const char *config, + int update) { - int rc; - fc_instance_t* priv = calloc(1, sizeof(fc_instance_t)); - const char* dir = library->fonts_dir; - int i; + int rc; + FCInstance *priv = calloc(1, sizeof(FCInstance)); + const char *dir = library->fonts_dir; + int i; - if (!fc) { - mp_msg(MSGT_ASS, MSGL_WARN, - MSGTR_LIBASS_FontconfigDisabledDefaultFontWillBeUsed); - goto exit; - } - - rc = FcInit(); - assert(rc); - - priv->config = FcConfigGetCurrent(); - if (!priv->config) { - mp_msg(MSGT_ASS, MSGL_FATAL, MSGTR_LIBASS_FcInitLoadConfigAndFontsFailed); - goto exit; - } - - for (i = 0; i < library->num_fontdata; ++i) - process_fontdata(priv, library, ftlibrary, i); + if (!fc) { + ass_msg(library, MSGL_WARN, + "Fontconfig disabled, only default font will be used."); + goto exit; + } - if (dir) { - if (FcDirCacheValid((const FcChar8 *)dir) == FcFalse) - { - mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_UpdatingFontCache); - if (FcGetVersion() >= 20390 && FcGetVersion() < 20400) - mp_msg(MSGT_ASS, MSGL_WARN, - MSGTR_LIBASS_BetaVersionsOfFontconfigAreNotSupported); - // FontConfig >= 2.4.0 updates cache automatically in FcConfigAppFontAddDir() - if (FcGetVersion() < 20390) { - FcFontSet* fcs; - FcStrSet* fss; - fcs = FcFontSetCreate(); - fss = FcStrSetCreate(); - rc = FcStrSetAdd(fss, (const FcChar8*)dir); - if (!rc) { - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FcStrSetAddFailed); - goto ErrorFontCache; - } + priv->config = FcConfigCreate(); + rc = FcConfigParseAndLoad(priv->config, (unsigned char *) config, FcTrue); + if (!rc) { + ass_msg(library, MSGL_WARN, "No usable fontconfig configuration " + "file found, using fallback."); + FcConfigDestroy(priv->config); + priv->config = FcInitLoadConfig(); + rc++; + } + if (rc && update) { + FcConfigBuildFonts(priv->config); + } - rc = FcDirScan(fcs, fss, NULL, FcConfigGetBlanks(priv->config), - (const FcChar8 *)dir, FcFalse); - if (!rc) { - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FcDirScanFailed); - goto ErrorFontCache; - } + if (!rc || !priv->config) { + ass_msg(library, MSGL_FATAL, + "No valid fontconfig configuration found!"); + FcConfigDestroy(priv->config); + goto exit; + } + + for (i = 0; i < library->num_fontdata; ++i) + process_fontdata(priv, library, ftlibrary, i); + + if (dir) { + ass_msg(library, MSGL_INFO, "Updating font cache"); - rc = FcDirSave(fcs, fss, (const FcChar8 *)dir); - if (!rc) { - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FcDirSave); - goto ErrorFontCache; - } - ErrorFontCache: - ; - } - } + rc = FcConfigAppFontAddDir(priv->config, (const FcChar8 *) dir); + if (!rc) { + ass_msg(library, MSGL_WARN, "%s failed", "FcConfigAppFontAddDir"); + } + } - rc = FcConfigAppFontAddDir(priv->config, (const FcChar8*)dir); - if (!rc) { - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FcConfigAppFontAddDirFailed); - } - } + priv->family_default = family ? strdup(family) : NULL; +exit: + priv->path_default = path ? strdup(path) : NULL; + priv->index_default = 0; - priv->family_default = family ? strdup(family) : NULL; -exit: - priv->path_default = path ? strdup(path) : NULL; - priv->index_default = 0; - - return priv; + return priv; } -#else /* CONFIG_FONTCONFIG */ +int fontconfig_update(FCInstance *priv) +{ + return FcConfigBuildFonts(priv->config); +} + +#else /* CONFIG_FONTCONFIG */ -char* fontconfig_select(fc_instance_t* priv, const char* family, int treat_family_as_pattern, - unsigned bold, unsigned italic, int* index, uint32_t code) +char *fontconfig_select(ASS_Library *library, FCInstance *priv, + const char *family, int treat_family_as_pattern, + unsigned bold, unsigned italic, int *index, + uint32_t code) { - *index = priv->index_default; - return priv->path_default; + *index = priv->index_default; + char* res = priv->path_default ? strdup(priv->path_default) : 0; + return res; } -fc_instance_t* fontconfig_init(ass_library_t* library, FT_Library ftlibrary, const char* family, const char* path, int fc) +FCInstance *fontconfig_init(ASS_Library *library, + FT_Library ftlibrary, const char *family, + const char *path, int fc, const char *config, + int update) { - fc_instance_t* priv; + FCInstance *priv; - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FontconfigDisabledDefaultFontWillBeUsed); + ass_msg(library, MSGL_WARN, + "Fontconfig disabled, only default font will be used."); - priv = calloc(1, sizeof(fc_instance_t)); + priv = calloc(1, sizeof(FCInstance)); - priv->path_default = strdup(path); - priv->index_default = 0; - return priv; + priv->path_default = path ? strdup(path) : 0; + priv->index_default = 0; + return priv; +} + +int fontconfig_update(FCInstance *priv) +{ + // Do nothing + return 1; } #endif -void fontconfig_done(fc_instance_t* priv) +void fontconfig_done(FCInstance *priv) { - // don't call FcFini() here, library can still be used by some code - if (priv && priv->path_default) free(priv->path_default); - if (priv && priv->family_default) free(priv->family_default); - if (priv) free(priv); +#ifdef CONFIG_FONTCONFIG + if (priv && priv->config) + FcConfigDestroy(priv->config); +#endif + if (priv && priv->path_default) + free(priv->path_default); + if (priv && priv->family_default) + free(priv->family_default); + if (priv) + free(priv); } - -
--- a/libass/ass_fontconfig.h Fri Jan 08 18:19:10 2010 +0000 +++ b/libass/ass_fontconfig.h Fri Jan 08 18:35:44 2010 +0000 @@ -1,5 +1,3 @@ -// -*- c-basic-offset: 8; indent-tabs-mode: t -*- -// vim:ts=8:sw=8:noet:ai: /* * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com> * @@ -25,6 +23,7 @@ #include <stdint.h> #include "ass_types.h" +#include "ass.h" #include <ft2build.h> #include FT_FREETYPE_H @@ -32,10 +31,17 @@ #include <fontconfig/fontconfig.h> #endif -typedef struct fc_instance_s fc_instance_t; +typedef struct fc_instance FCInstance; -fc_instance_t* fontconfig_init(ass_library_t* library, FT_Library ftlibrary, const char* family, const char* path, int fc); -char* fontconfig_select(fc_instance_t* priv, const char* family, int treat_family_as_pattern, unsigned bold, unsigned italic, int* index, uint32_t code); -void fontconfig_done(fc_instance_t* priv); +FCInstance *fontconfig_init(ASS_Library *library, + FT_Library ftlibrary, const char *family, + const char *path, int fc, const char *config, + int update); +char *fontconfig_select(ASS_Library *library, FCInstance *priv, + const char *family, int treat_family_as_pattern, + unsigned bold, unsigned italic, int *index, + uint32_t code); +void fontconfig_done(FCInstance *priv); +int fontconfig_update(FCInstance *priv); -#endif /* LIBASS_FONTCONFIG_H */ +#endif /* LIBASS_FONTCONFIG_H */
--- a/libass/ass_library.c Fri Jan 08 18:19:10 2010 +0000 +++ b/libass/ass_library.c Fri Jan 08 18:35:44 2010 +0000 @@ -1,5 +1,3 @@ -// -*- c-basic-offset: 8; indent-tabs-mode: t -*- -// vim:ts=8:sw=8:noet:ai: /* * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com> * @@ -24,92 +22,126 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <stdarg.h> #include "ass.h" #include "ass_library.h" - +#include "ass_utils.h" -ass_library_t* ass_library_init(void) +static void ass_msg_handler(int level, const char *fmt, va_list va, void *data) { - return calloc(1, sizeof(ass_library_t)); + if (level > MSGL_INFO) + return; + fprintf(stderr, "[ass] "); + vfprintf(stderr, fmt, va); + fprintf(stderr, "\n"); } -void ass_library_done(ass_library_t* priv) +ASS_Library *ass_library_init(void) { - if (priv) { - ass_set_fonts_dir(priv, NULL); - ass_set_style_overrides(priv, NULL); - ass_clear_fonts(priv); - free(priv); - } + ASS_Library* lib = calloc(1, sizeof(*lib)); + lib->msg_callback = ass_msg_handler; + + return lib; } -void ass_set_fonts_dir(ass_library_t* priv, const char* fonts_dir) +void ass_library_done(ASS_Library *priv) { - if (priv->fonts_dir) - free(priv->fonts_dir); - - priv->fonts_dir = fonts_dir ? strdup(fonts_dir) : 0; + if (priv) { + ass_set_fonts_dir(priv, NULL); + ass_set_style_overrides(priv, NULL); + ass_clear_fonts(priv); + free(priv); + } } -void ass_set_extract_fonts(ass_library_t* priv, int extract) +void ass_set_fonts_dir(ASS_Library *priv, const char *fonts_dir) { - priv->extract_fonts = !!extract; + if (priv->fonts_dir) + free(priv->fonts_dir); + + priv->fonts_dir = fonts_dir ? strdup(fonts_dir) : 0; +} + +void ass_set_extract_fonts(ASS_Library *priv, int extract) +{ + priv->extract_fonts = !!extract; } -void ass_set_style_overrides(ass_library_t* priv, char** list) +void ass_set_style_overrides(ASS_Library *priv, char **list) { - char** p; - char** q; - int cnt; + char **p; + char **q; + int cnt; - if (priv->style_overrides) { - for (p = priv->style_overrides; *p; ++p) - free(*p); - free(priv->style_overrides); - } + if (priv->style_overrides) { + for (p = priv->style_overrides; *p; ++p) + free(*p); + free(priv->style_overrides); + } - if (!list) return; - - for (p = list, cnt = 0; *p; ++p, ++cnt) {} + if (!list) + return; - priv->style_overrides = malloc((cnt + 1) * sizeof(char*)); - for (p = list, q = priv->style_overrides; *p; ++p, ++q) - *q = strdup(*p); - priv->style_overrides[cnt] = NULL; + for (p = list, cnt = 0; *p; ++p, ++cnt) { + } + + priv->style_overrides = malloc((cnt + 1) * sizeof(char *)); + for (p = list, q = priv->style_overrides; *p; ++p, ++q) + *q = strdup(*p); + priv->style_overrides[cnt] = NULL; } static void grow_array(void **array, int nelem, size_t elsize) { - if (!(nelem & 31)) - *array = realloc(*array, (nelem + 32) * elsize); + if (!(nelem & 31)) + *array = realloc(*array, (nelem + 32) * elsize); +} + +void ass_add_font(ASS_Library *priv, char *name, char *data, int size) +{ + int idx = priv->num_fontdata; + if (!name || !data || !size) + return; + grow_array((void **) &priv->fontdata, priv->num_fontdata, + sizeof(*priv->fontdata)); + + priv->fontdata[idx].name = strdup(name); + + priv->fontdata[idx].data = malloc(size); + memcpy(priv->fontdata[idx].data, data, size); + + priv->fontdata[idx].size = size; + + priv->num_fontdata++; } -void ass_add_font(ass_library_t* priv, char* name, char* data, int size) +void ass_clear_fonts(ASS_Library *priv) { - int idx = priv->num_fontdata; - if (!name || !data || !size) - return; - grow_array((void**)&priv->fontdata, priv->num_fontdata, sizeof(*priv->fontdata)); - - priv->fontdata[idx].name = strdup(name); - - priv->fontdata[idx].data = malloc(size); - memcpy(priv->fontdata[idx].data, data, size); - - priv->fontdata[idx].size = size; - - priv->num_fontdata ++; + int i; + for (i = 0; i < priv->num_fontdata; ++i) { + free(priv->fontdata[i].name); + free(priv->fontdata[i].data); + } + free(priv->fontdata); + priv->fontdata = NULL; + priv->num_fontdata = 0; } -void ass_clear_fonts(ass_library_t* priv) +/* + * Register a message callback function with libass. Without setting one, + * a default handler is used which prints everything with MSGL_INFO or + * higher to the standard output. + * + * \param msg_cb the callback function + * \param data additional data that will be passed to the callback + */ +void ass_set_message_cb(ASS_Library *priv, + void (*msg_cb)(int, const char *, va_list, void *), + void *data) { - int i; - for (i = 0; i < priv->num_fontdata; ++i) { - free(priv->fontdata[i].name); - free(priv->fontdata[i].data); - } - free(priv->fontdata); - priv->fontdata = NULL; - priv->num_fontdata = 0; + if (msg_cb) { + priv->msg_callback = msg_cb; + priv->msg_callback_data = data; + } }
--- a/libass/ass_library.h Fri Jan 08 18:19:10 2010 +0000 +++ b/libass/ass_library.h Fri Jan 08 18:35:44 2010 +0000 @@ -1,5 +1,3 @@ -// -*- c-basic-offset: 8; indent-tabs-mode: t -*- -// vim:ts=8:sw=8:noet:ai: /* * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com> * @@ -23,19 +21,23 @@ #ifndef LIBASS_LIBRARY_H #define LIBASS_LIBRARY_H -typedef struct ass_fontdata_s { - char* name; - char* data; - int size; -} ass_fontdata_t; +#include <stdarg.h> + +typedef struct { + char *name; + char *data; + int size; +} ASS_Fontdata; -struct ass_library_s { - char* fonts_dir; - int extract_fonts; - char** style_overrides; +struct ass_library { + char *fonts_dir; + int extract_fonts; + char **style_overrides; - ass_fontdata_t* fontdata; - int num_fontdata; + ASS_Fontdata *fontdata; + int num_fontdata; + void (*msg_callback)(int, const char *, va_list, void *); + void *msg_callback_data; }; -#endif /* LIBASS_LIBRARY_H */ +#endif /* LIBASS_LIBRARY_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libass/ass_parse.c Fri Jan 08 18:35:44 2010 +0000 @@ -0,0 +1,926 @@ +/* + * Copyright (C) 2009 Grigori Goronzy <greg@geekmind.org> + * + * This file is part of libass. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> + +#include "ass_render.h" +#include "ass_parse.h" + +#define MAX_BE 127 +#define NBSP 0xa0 // unicode non-breaking space character + +#define skip_to(x) while ((*p != (x)) && (*p != '}') && (*p != 0)) { ++p;} +#define skip(x) if (*p == (x)) ++p; else { return p; } +#define skipopt(x) if (*p == (x)) { ++p; } + +/** + * \brief Check if starting part of (*p) matches sample. + * If true, shift p to the first symbol after the matching part. + */ +static inline int mystrcmp(char **p, const char *sample) +{ + int len = strlen(sample); + if (strncmp(*p, sample, len) == 0) { + (*p) += len; + return 1; + } else + return 0; +} + +static void change_font_size(ASS_Renderer *render_priv, double sz) +{ + double size = sz * render_priv->font_scale; + + if (size < 1) + size = 1; + else if (size > render_priv->height * 2) + size = render_priv->height * 2; + + ass_font_set_size(render_priv->state.font, size); + + render_priv->state.font_size = sz; +} + +/** + * \brief Change current font, using setting from render_priv->state. + */ +void update_font(ASS_Renderer *render_priv) +{ + unsigned val; + ASS_FontDesc desc; + desc.family = strdup(render_priv->state.family); + desc.treat_family_as_pattern = + render_priv->state.treat_family_as_pattern; + + val = render_priv->state.bold; + // 0 = normal, 1 = bold, >1 = exact weight + if (val == 1 || val == -1) + val = 200; // bold + else if (val <= 0) + val = 80; // normal + desc.bold = val; + + val = render_priv->state.italic; + if (val == 1 || val == -1) + val = 110; // italic + else if (val <= 0) + val = 0; // normal + desc.italic = val; + + render_priv->state.font = + ass_font_new(render_priv->cache.font_cache, render_priv->library, + render_priv->ftlibrary, render_priv->fontconfig_priv, + &desc); + free(desc.family); + + if (render_priv->state.font) + change_font_size(render_priv, render_priv->state.font_size); +} + +/** + * \brief Change border width + * negative value resets border to style value + */ +void change_border(ASS_Renderer *render_priv, double border_x, + double border_y) +{ + int bord; + if (!render_priv->state.font) + return; + + if (border_x < 0 && border_y < 0) { + if (render_priv->state.style->BorderStyle == 1 || + render_priv->state.style->BorderStyle == 3) + border_x = border_y = render_priv->state.style->Outline; + else + border_x = border_y = 1.; + } + + render_priv->state.border_x = border_x; + render_priv->state.border_y = border_y; + + bord = 64 * border_x * render_priv->border_scale; + if (bord > 0 && border_x == border_y) { + if (!render_priv->state.stroker) { + int error; + error = + FT_Stroker_New(render_priv->ftlibrary, + &render_priv->state.stroker); + if (error) { + ass_msg(render_priv->library, MSGL_V, + "failed to get stroker"); + render_priv->state.stroker = 0; + } + } + if (render_priv->state.stroker) + FT_Stroker_Set(render_priv->state.stroker, bord, + FT_STROKER_LINECAP_ROUND, + FT_STROKER_LINEJOIN_ROUND, 0); + } else { + FT_Stroker_Done(render_priv->state.stroker); + render_priv->state.stroker = 0; + } +} + +/** + * \brief Calculate a weighted average of two colors + * calculates c1*(1-a) + c2*a, but separately for each component except alpha + */ +static void change_color(uint32_t *var, uint32_t new, double pwr) +{ + (*var) = ((uint32_t) (_r(*var) * (1 - pwr) + _r(new) * pwr) << 24) + + ((uint32_t) (_g(*var) * (1 - pwr) + _g(new) * pwr) << 16) + + ((uint32_t) (_b(*var) * (1 - pwr) + _b(new) * pwr) << 8) + _a(*var); +} + +// like change_color, but for alpha component only +inline void change_alpha(uint32_t *var, uint32_t new, double pwr) +{ + *var = + (_r(*var) << 24) + (_g(*var) << 16) + (_b(*var) << 8) + + (uint32_t) (_a(*var) * (1 - pwr) + _a(new) * pwr); +} + +/** + * \brief Multiply two alpha values + * \param a first value + * \param b second value + * \return result of multiplication + * Parameters and result are limited by 0xFF. + */ +inline uint32_t mult_alpha(uint32_t a, uint32_t b) +{ + return 0xFF - (0xFF - a) * (0xFF - b) / 0xFF; +} + +/** + * \brief Calculate alpha value by piecewise linear function + * Used for \fad, \fade implementation. + */ +static unsigned +interpolate_alpha(long long now, long long t1, long long t2, long long t3, + long long t4, unsigned a1, unsigned a2, unsigned a3) +{ + unsigned a; + double cf; + if (now <= t1) { + a = a1; + } else if (now >= t4) { + a = a3; + } else if (now < t2) { // and > t1 + cf = ((double) (now - t1)) / (t2 - t1); + a = a1 * (1 - cf) + a2 * cf; + } else if (now > t3) { + cf = ((double) (now - t3)) / (t4 - t3); + a = a2 * (1 - cf) + a3 * cf; + } else { // t2 <= now <= t3 + a = a2; + } + + return a; +} + +/** + * Parse a vector clip into an outline, using the proper scaling + * parameters. Translate it to correct for screen borders, if needed. + */ +static char *parse_vector_clip(ASS_Renderer *render_priv, char *p) +{ + int scale = 1; + int res = 0; + ASS_Drawing *drawing; + + render_priv->state.clip_drawing = ass_drawing_new( + render_priv->fontconfig_priv, + render_priv->state.font, + render_priv->settings.hinting, + render_priv->ftlibrary); + drawing = render_priv->state.clip_drawing; + skipopt('('); + res = mystrtoi(&p, &scale); + skipopt(',') + if (!res) + scale = 1; + drawing->scale = scale; + drawing->scale_x = render_priv->font_scale_x * render_priv->font_scale; + drawing->scale_y = render_priv->font_scale; + while (*p != ')' && *p != '}' && p != 0) + ass_drawing_add_char(drawing, *p++); + skipopt(')'); + if (ass_drawing_parse(drawing, 1)) { + // We need to translate the clip according to screen borders + if (render_priv->settings.left_margin != 0 || + render_priv->settings.top_margin != 0) { + FT_Vector trans = { + .x = int_to_d6(render_priv->settings.left_margin), + .y = -int_to_d6(render_priv->settings.top_margin), + }; + FT_Outline_Translate(&drawing->glyph->outline, trans.x, trans.y); + } + ass_msg(render_priv->library, MSGL_DBG2, + "Parsed vector clip: scale %d, scales (%f, %f) string [%s]\n", + scale, drawing->scale_x, drawing->scale_y, drawing->text); + } + + return p; +} + +/** + * \brief Parse style override tag. + * \param p string to parse + * \param pwr multiplier for some tag effects (comes from \t tags) + */ +static char *parse_tag(ASS_Renderer *render_priv, char *p, double pwr) +{ + skip_to('\\'); + skip('\\'); + if ((*p == '}') || (*p == 0)) + return p; + + // New tags introduced in vsfilter 2.39 + if (mystrcmp(&p, "xbord")) { + double val; + if (mystrtod(&p, &val)) + val = render_priv->state.border_x * (1 - pwr) + val * pwr; + else + val = -1.; + change_border(render_priv, val, render_priv->state.border_y); + } else if (mystrcmp(&p, "ybord")) { + double val; + if (mystrtod(&p, &val)) + val = render_priv->state.border_y * (1 - pwr) + val * pwr; + else + val = -1.; + change_border(render_priv, render_priv->state.border_x, val); + } else if (mystrcmp(&p, "xshad")) { + double val; + if (mystrtod(&p, &val)) + val = render_priv->state.shadow_x * (1 - pwr) + val * pwr; + else + val = 0.; + render_priv->state.shadow_x = val; + } else if (mystrcmp(&p, "yshad")) { + double val; + if (mystrtod(&p, &val)) + val = render_priv->state.shadow_y * (1 - pwr) + val * pwr; + else + val = 0.; + render_priv->state.shadow_y = val; + } else if (mystrcmp(&p, "fax")) { + double val; + if (mystrtod(&p, &val)) + render_priv->state.fax = + val * pwr + render_priv->state.fax * (1 - pwr); + else + render_priv->state.fax = 0.; + } else if (mystrcmp(&p, "fay")) { + double val; + if (mystrtod(&p, &val)) + render_priv->state.fay = + val * pwr + render_priv->state.fay * (1 - pwr); + else + render_priv->state.fay = 0.; + } else if (mystrcmp(&p, "iclip")) { + int x0, y0, x1, y1; + int res = 1; + char *start = p; + skipopt('('); + res &= mystrtoi(&p, &x0); + skipopt(','); + res &= mystrtoi(&p, &y0); + skipopt(','); + res &= mystrtoi(&p, &x1); + skipopt(','); + res &= mystrtoi(&p, &y1); + skipopt(')'); + if (res) { + render_priv->state.clip_x0 = + render_priv->state.clip_x0 * (1 - pwr) + x0 * pwr; + render_priv->state.clip_x1 = + render_priv->state.clip_x1 * (1 - pwr) + x1 * pwr; + render_priv->state.clip_y0 = + render_priv->state.clip_y0 * (1 - pwr) + y0 * pwr; + render_priv->state.clip_y1 = + render_priv->state.clip_y1 * (1 - pwr) + y1 * pwr; + render_priv->state.clip_mode = 1; + } else if (!render_priv->state.clip_drawing) { + p = parse_vector_clip(render_priv, start); + render_priv->state.clip_drawing_mode = 1; + } else + render_priv->state.clip_mode = 0; + } else if (mystrcmp(&p, "blur")) { + double val; + if (mystrtod(&p, &val)) { + val = render_priv->state.blur * (1 - pwr) + val * pwr; + val = (val < 0) ? 0 : val; + val = (val > BLUR_MAX_RADIUS) ? BLUR_MAX_RADIUS : val; + render_priv->state.blur = val; + } else + render_priv->state.blur = 0.0; + // ASS standard tags + } else if (mystrcmp(&p, "fsc")) { + char tp = *p++; + double val; + if (tp == 'x') { + if (mystrtod(&p, &val)) { + val /= 100; + render_priv->state.scale_x = + render_priv->state.scale_x * (1 - pwr) + val * pwr; + } else + render_priv->state.scale_x = + render_priv->state.style->ScaleX; + } else if (tp == 'y') { + if (mystrtod(&p, &val)) { + val /= 100; + render_priv->state.scale_y = + render_priv->state.scale_y * (1 - pwr) + val * pwr; + } else + render_priv->state.scale_y = + render_priv->state.style->ScaleY; + } + } else if (mystrcmp(&p, "fsp")) { + double val; + if (mystrtod(&p, &val)) + render_priv->state.hspacing = + render_priv->state.hspacing * (1 - pwr) + val * pwr; + else + render_priv->state.hspacing = render_priv->state.style->Spacing; + } else if (mystrcmp(&p, "fs")) { + double val; + if (mystrtod(&p, &val)) + val = render_priv->state.font_size * (1 - pwr) + val * pwr; + else + val = render_priv->state.style->FontSize; + if (render_priv->state.font) + change_font_size(render_priv, val); + } else if (mystrcmp(&p, "bord")) { + double val; + if (mystrtod(&p, &val)) { + if (render_priv->state.border_x == render_priv->state.border_y) + val = render_priv->state.border_x * (1 - pwr) + val * pwr; + } else + val = -1.; // reset to default + change_border(render_priv, val, val); + } else if (mystrcmp(&p, "move")) { + double x1, x2, y1, y2; + long long t1, t2, delta_t, t; + double x, y; + double k; + skip('('); + mystrtod(&p, &x1); + skip(','); + mystrtod(&p, &y1); + skip(','); + mystrtod(&p, &x2); + skip(','); + mystrtod(&p, &y2); + if (*p == ',') { + skip(','); + mystrtoll(&p, &t1); + skip(','); + mystrtoll(&p, &t2); + ass_msg(render_priv->library, MSGL_DBG2, + "movement6: (%f, %f) -> (%f, %f), (%" PRId64 " .. %" + PRId64 ")\n", x1, y1, x2, y2, (int64_t) t1, + (int64_t) t2); + } else { + t1 = 0; + t2 = render_priv->state.event->Duration; + ass_msg(render_priv->library, MSGL_DBG2, + "movement: (%f, %f) -> (%f, %f)", x1, y1, x2, y2); + } + skip(')'); + delta_t = t2 - t1; + t = render_priv->time - render_priv->state.event->Start; + if (t < t1) + k = 0.; + else if (t > t2) + k = 1.; + else + k = ((double) (t - t1)) / delta_t; + x = k * (x2 - x1) + x1; + y = k * (y2 - y1) + y1; + if (render_priv->state.evt_type != EVENT_POSITIONED) { + render_priv->state.pos_x = x; + render_priv->state.pos_y = y; + render_priv->state.detect_collisions = 0; + render_priv->state.evt_type = EVENT_POSITIONED; + } + } else if (mystrcmp(&p, "frx")) { + double val; + if (mystrtod(&p, &val)) { + val *= M_PI / 180; + render_priv->state.frx = + val * pwr + render_priv->state.frx * (1 - pwr); + } else + render_priv->state.frx = 0.; + } else if (mystrcmp(&p, "fry")) { + double val; + if (mystrtod(&p, &val)) { + val *= M_PI / 180; + render_priv->state.fry = + val * pwr + render_priv->state.fry * (1 - pwr); + } else + render_priv->state.fry = 0.; + } else if (mystrcmp(&p, "frz") || mystrcmp(&p, "fr")) { + double val; + if (mystrtod(&p, &val)) { + val *= M_PI / 180; + render_priv->state.frz = + val * pwr + render_priv->state.frz * (1 - pwr); + } else + render_priv->state.frz = + M_PI * render_priv->state.style->Angle / 180.; + } else if (mystrcmp(&p, "fn")) { + char *start = p; + char *family; + skip_to('\\'); + if (p > start) { + family = malloc(p - start + 1); + strncpy(family, start, p - start); + family[p - start] = '\0'; + } else + family = strdup(render_priv->state.style->FontName); + if (render_priv->state.family) + free(render_priv->state.family); + render_priv->state.family = family; + update_font(render_priv); + } else if (mystrcmp(&p, "alpha")) { + uint32_t val; + int i; + int hex = render_priv->track->track_type == TRACK_TYPE_ASS; + if (strtocolor(render_priv->library, &p, &val, hex)) { + unsigned char a = val >> 24; + for (i = 0; i < 4; ++i) + change_alpha(&render_priv->state.c[i], a, pwr); + } else { + change_alpha(&render_priv->state.c[0], + render_priv->state.style->PrimaryColour, pwr); + change_alpha(&render_priv->state.c[1], + render_priv->state.style->SecondaryColour, pwr); + change_alpha(&render_priv->state.c[2], + render_priv->state.style->OutlineColour, pwr); + change_alpha(&render_priv->state.c[3], + render_priv->state.style->BackColour, pwr); + } + // FIXME: simplify + } else if (mystrcmp(&p, "an")) { + int val; + if (mystrtoi(&p, &val) && val) { + int v = (val - 1) / 3; // 0, 1 or 2 for vertical alignment + ass_msg(render_priv->library, MSGL_DBG2, "an %d", val); + if (v != 0) + v = 3 - v; + val = ((val - 1) % 3) + 1; // horizontal alignment + val += v * 4; + ass_msg(render_priv->library, MSGL_DBG2, "align %d", val); + render_priv->state.alignment = val; + } else + render_priv->state.alignment = + render_priv->state.style->Alignment; + } else if (mystrcmp(&p, "a")) { + int val; + if (mystrtoi(&p, &val) && val) + // take care of a vsfilter quirk: handle illegal \a8 like \a5 + render_priv->state.alignment = (val == 8) ? 5 : val; + else + render_priv->state.alignment = + render_priv->state.style->Alignment; + } else if (mystrcmp(&p, "pos")) { + double v1, v2; + skip('('); + mystrtod(&p, &v1); + skip(','); + mystrtod(&p, &v2); + skip(')'); + ass_msg(render_priv->library, MSGL_DBG2, "pos(%f, %f)", v1, v2); + if (render_priv->state.evt_type == EVENT_POSITIONED) { + ass_msg(render_priv->library, MSGL_V, "Subtitle has a new \\pos " + "after \\move or \\pos, ignoring"); + } else { + render_priv->state.evt_type = EVENT_POSITIONED; + render_priv->state.detect_collisions = 0; + render_priv->state.pos_x = v1; + render_priv->state.pos_y = v2; + } + } else if (mystrcmp(&p, "fad")) { + int a1, a2, a3; + long long t1, t2, t3, t4; + if (*p == 'e') + ++p; // either \fad or \fade + skip('('); + mystrtoi(&p, &a1); + skip(','); + mystrtoi(&p, &a2); + if (*p == ')') { + // 2-argument version (\fad, according to specs) + // a1 and a2 are fade-in and fade-out durations + t1 = 0; + t4 = render_priv->state.event->Duration; + t2 = a1; + t3 = t4 - a2; + a1 = 0xFF; + a2 = 0; + a3 = 0xFF; + } else { + // 6-argument version (\fade) + // a1 and a2 (and a3) are opacity values + skip(','); + mystrtoi(&p, &a3); + skip(','); + mystrtoll(&p, &t1); + skip(','); + mystrtoll(&p, &t2); + skip(','); + mystrtoll(&p, &t3); + skip(','); + mystrtoll(&p, &t4); + } + skip(')'); + render_priv->state.fade = + interpolate_alpha(render_priv->time - + render_priv->state.event->Start, t1, t2, + t3, t4, a1, a2, a3); + } else if (mystrcmp(&p, "org")) { + int v1, v2; + skip('('); + mystrtoi(&p, &v1); + skip(','); + mystrtoi(&p, &v2); + skip(')'); + ass_msg(render_priv->library, MSGL_DBG2, "org(%d, %d)", v1, v2); + if (!render_priv->state.have_origin) { + render_priv->state.org_x = v1; + render_priv->state.org_y = v2; + render_priv->state.have_origin = 1; + render_priv->state.detect_collisions = 0; + } + } else if (mystrcmp(&p, "t")) { + double v[3]; + int v1, v2; + double v3; + int cnt; + long long t1, t2, t, delta_t; + double k; + skip('('); + for (cnt = 0; cnt < 3; ++cnt) { + if (*p == '\\') + break; + v[cnt] = strtod(p, &p); + skip(','); + } + if (cnt == 3) { + v1 = v[0]; + v2 = (v[1] < v1) ? render_priv->state.event->Duration : v[1]; + v3 = v[2]; + } else if (cnt == 2) { + v1 = v[0]; + v2 = (v[1] < v1) ? render_priv->state.event->Duration : v[1]; + v3 = 1.; + } else if (cnt == 1) { + v1 = 0; + v2 = render_priv->state.event->Duration; + v3 = v[0]; + } else { // cnt == 0 + v1 = 0; + v2 = render_priv->state.event->Duration; + v3 = 1.; + } + render_priv->state.detect_collisions = 0; + t1 = v1; + t2 = v2; + delta_t = v2 - v1; + if (v3 < 0.) + v3 = 0.; + t = render_priv->time - render_priv->state.event->Start; // FIXME: move to render_context + if (t <= t1) + k = 0.; + else if (t >= t2) + k = 1.; + else { + assert(delta_t != 0.); + k = pow(((double) (t - t1)) / delta_t, v3); + } + while (*p == '\\') + p = parse_tag(render_priv, p, k); // maybe k*pwr ? no, specs forbid nested \t's + skip_to(')'); // in case there is some unknown tag or a comment + skip(')'); + } else if (mystrcmp(&p, "clip")) { + char *start = p; + int x0, y0, x1, y1; + int res = 1; + skipopt('('); + res &= mystrtoi(&p, &x0); + skipopt(','); + res &= mystrtoi(&p, &y0); + skipopt(','); + res &= mystrtoi(&p, &x1); + skipopt(','); + res &= mystrtoi(&p, &y1); + skipopt(')'); + if (res) { + render_priv->state.clip_x0 = + render_priv->state.clip_x0 * (1 - pwr) + x0 * pwr; + render_priv->state.clip_x1 = + render_priv->state.clip_x1 * (1 - pwr) + x1 * pwr; + render_priv->state.clip_y0 = + render_priv->state.clip_y0 * (1 - pwr) + y0 * pwr; + render_priv->state.clip_y1 = + render_priv->state.clip_y1 * (1 - pwr) + y1 * pwr; + // Might be a vector clip + } else if (!render_priv->state.clip_drawing) { + p = parse_vector_clip(render_priv, start); + render_priv->state.clip_drawing_mode = 0; + } else { + render_priv->state.clip_x0 = 0; + render_priv->state.clip_y0 = 0; + render_priv->state.clip_x1 = render_priv->track->PlayResX; + render_priv->state.clip_y1 = render_priv->track->PlayResY; + } + } else if (mystrcmp(&p, "c")) { + uint32_t val; + int hex = render_priv->track->track_type == TRACK_TYPE_ASS; + if (!strtocolor(render_priv->library, &p, &val, hex)) + val = render_priv->state.style->PrimaryColour; + ass_msg(render_priv->library, MSGL_DBG2, "color: %X", val); + change_color(&render_priv->state.c[0], val, pwr); + } else if ((*p >= '1') && (*p <= '4') && (++p) + && (mystrcmp(&p, "c") || mystrcmp(&p, "a"))) { + char n = *(p - 2); + int cidx = n - '1'; + char cmd = *(p - 1); + uint32_t val; + int hex = render_priv->track->track_type == TRACK_TYPE_ASS; + assert((n >= '1') && (n <= '4')); + if (!strtocolor(render_priv->library, &p, &val, hex)) + switch (n) { + case '1': + val = render_priv->state.style->PrimaryColour; + break; + case '2': + val = render_priv->state.style->SecondaryColour; + break; + case '3': + val = render_priv->state.style->OutlineColour; + break; + case '4': + val = render_priv->state.style->BackColour; + break; + default: + val = 0; + break; // impossible due to assert; avoid compilation warning + } + switch (cmd) { + case 'c': + change_color(render_priv->state.c + cidx, val, pwr); + break; + case 'a': + change_alpha(render_priv->state.c + cidx, val >> 24, pwr); + break; + default: + ass_msg(render_priv->library, MSGL_WARN, "Bad command: %c%c", + n, cmd); + break; + } + ass_msg(render_priv->library, MSGL_DBG2, "single c/a at %f: %c%c = %X", + pwr, n, cmd, render_priv->state.c[cidx]); + } else if (mystrcmp(&p, "r")) { + reset_render_context(render_priv); + } else if (mystrcmp(&p, "be")) { + int val; + if (mystrtoi(&p, &val)) { + // Clamp to a safe upper limit, since high values need excessive CPU + val = (val < 0) ? 0 : val; + val = (val > MAX_BE) ? MAX_BE : val; + render_priv->state.be = val; + } else + render_priv->state.be = 0; + } else if (mystrcmp(&p, "b")) { + int b; + if (mystrtoi(&p, &b)) { + if (pwr >= .5) + render_priv->state.bold = b; + } else + render_priv->state.bold = render_priv->state.style->Bold; + update_font(render_priv); + } else if (mystrcmp(&p, "i")) { + int i; + if (mystrtoi(&p, &i)) { + if (pwr >= .5) + render_priv->state.italic = i; + } else + render_priv->state.italic = render_priv->state.style->Italic; + update_font(render_priv); + } else if (mystrcmp(&p, "kf") || mystrcmp(&p, "K")) { + int val = 0; + mystrtoi(&p, &val); + render_priv->state.effect_type = EF_KARAOKE_KF; + if (render_priv->state.effect_timing) + render_priv->state.effect_skip_timing += + render_priv->state.effect_timing; + render_priv->state.effect_timing = val * 10; + } else if (mystrcmp(&p, "ko")) { + int val = 0; + mystrtoi(&p, &val); + render_priv->state.effect_type = EF_KARAOKE_KO; + if (render_priv->state.effect_timing) + render_priv->state.effect_skip_timing += + render_priv->state.effect_timing; + render_priv->state.effect_timing = val * 10; + } else if (mystrcmp(&p, "k")) { + int val = 0; + mystrtoi(&p, &val); + render_priv->state.effect_type = EF_KARAOKE; + if (render_priv->state.effect_timing) + render_priv->state.effect_skip_timing += + render_priv->state.effect_timing; + render_priv->state.effect_timing = val * 10; + } else if (mystrcmp(&p, "shad")) { + double val; + if (mystrtod(&p, &val)) { + if (render_priv->state.shadow_x == render_priv->state.shadow_y) + val = render_priv->state.shadow_x * (1 - pwr) + val * pwr; + } else + val = 0.; + render_priv->state.shadow_x = render_priv->state.shadow_y = val; + } else if (mystrcmp(&p, "s")) { + int val; + if (mystrtoi(&p, &val) && val) + render_priv->state.flags |= DECO_STRIKETHROUGH; + else + render_priv->state.flags &= ~DECO_STRIKETHROUGH; + } else if (mystrcmp(&p, "u")) { + int val; + if (mystrtoi(&p, &val) && val) + render_priv->state.flags |= DECO_UNDERLINE; + else + render_priv->state.flags &= ~DECO_UNDERLINE; + } else if (mystrcmp(&p, "pbo")) { + double val = 0; + if (mystrtod(&p, &val)) + render_priv->state.drawing->pbo = val; + } else if (mystrcmp(&p, "p")) { + int val; + if (!mystrtoi(&p, &val)) + val = 0; + if (val) + render_priv->state.drawing->scale = val; + render_priv->state.drawing_mode = !!val; + } else if (mystrcmp(&p, "q")) { + int val; + if (!mystrtoi(&p, &val)) + val = render_priv->track->WrapStyle; + render_priv->state.wrap_style = val; + } + + return p; +} + +void apply_transition_effects(ASS_Renderer *render_priv, ASS_Event *event) +{ + int v[4]; + int cnt; + char *p = event->Effect; + + if (!p || !*p) + return; + + cnt = 0; + while (cnt < 4 && (p = strchr(p, ';'))) { + v[cnt++] = atoi(++p); + } + + if (strncmp(event->Effect, "Banner;", 7) == 0) { + int delay; + if (cnt < 1) { + ass_msg(render_priv->library, MSGL_V, + "Error parsing effect: '%s'", event->Effect); + return; + } + if (cnt >= 2 && v[1] == 0) // right-to-left + render_priv->state.scroll_direction = SCROLL_RL; + else // left-to-right + render_priv->state.scroll_direction = SCROLL_LR; + + delay = v[0]; + if (delay == 0) + delay = 1; // ? + render_priv->state.scroll_shift = + (render_priv->time - render_priv->state.event->Start) / delay; + render_priv->state.evt_type = EVENT_HSCROLL; + return; + } + + if (strncmp(event->Effect, "Scroll up;", 10) == 0) { + render_priv->state.scroll_direction = SCROLL_BT; + } else if (strncmp(event->Effect, "Scroll down;", 12) == 0) { + render_priv->state.scroll_direction = SCROLL_TB; + } else { + ass_msg(render_priv->library, MSGL_V, + "Unknown transition effect: '%s'", event->Effect); + return; + } + // parse scroll up/down parameters + { + int delay; + int y0, y1; + if (cnt < 3) { + ass_msg(render_priv->library, MSGL_V, + "Error parsing effect: '%s'", event->Effect); + return; + } + delay = v[2]; + if (delay == 0) + delay = 1; // ? + render_priv->state.scroll_shift = + (render_priv->time - render_priv->state.event->Start) / delay; + if (v[0] < v[1]) { + y0 = v[0]; + y1 = v[1]; + } else { + y0 = v[1]; + y1 = v[0]; + } + if (y1 == 0) + y1 = render_priv->track->PlayResY; // y0=y1=0 means fullscreen scrolling + render_priv->state.clip_y0 = y0; + render_priv->state.clip_y1 = y1; + render_priv->state.evt_type = EVENT_VSCROLL; + render_priv->state.detect_collisions = 0; + } + +} + +/** + * \brief Get next ucs4 char from string, parsing and executing style overrides + * \param str string pointer + * \return ucs4 code of the next char + * On return str points to the unparsed part of the string + */ +unsigned get_next_char(ASS_Renderer *render_priv, char **str) +{ + char *p = *str; + unsigned chr; + if (*p == '{') { // '\0' goes here + p++; + while (1) { + p = parse_tag(render_priv, p, 1.); + if (*p == '}') { // end of tag + p++; + if (*p == '{') { + p++; + continue; + } else + break; + } else if (*p != '\\') + ass_msg(render_priv->library, MSGL_V, + "Unable to parse: '%s'", p); + if (*p == 0) + break; + } + } + if (*p == '\t') { + ++p; + *str = p; + return ' '; + } + if (*p == '\\') { + if ((p[1] == 'N') || ((p[1] == 'n') && + (render_priv->state.wrap_style == 2))) { + p += 2; + *str = p; + return '\n'; + } else if (p[1] == 'n') { + p += 2; + *str = p; + return ' '; + } else if (p[1] == 'h') { + p += 2; + *str = p; + return NBSP; + } + } + chr = ass_utf8_get_char((char **) &p); + *str = p; + return chr; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libass/ass_parse.h Fri Jan 08 18:35:44 2010 +0000 @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2009 Grigori Goronzy <greg@geekmind.org> + * + * This file is part of libass. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef LIBASS_PARSE_H +#define LIBASS_PARSE_H + +#define BLUR_MAX_RADIUS 100.0 + +#define _r(c) ((c) >> 24) +#define _g(c) (((c) >> 16) & 0xFF) +#define _b(c) (((c) >> 8) & 0xFF) +#define _a(c) ((c) & 0xFF) + +void update_font(ASS_Renderer *render_priv); +void change_border(ASS_Renderer *render_priv, double border_x, + double border_y); +void apply_transition_effects(ASS_Renderer *render_priv, ASS_Event *event); +unsigned get_next_char(ASS_Renderer *render_priv, char **str); +extern void change_alpha(uint32_t *var, uint32_t new, double pwr); +extern uint32_t mult_alpha(uint32_t a, uint32_t b); + + +#endif /* LIBASS_PARSE_H */
--- a/libass/ass_render.c Fri Jan 08 18:19:10 2010 +0000 +++ b/libass/ass_render.c Fri Jan 08 18:35:44 2010 +0000 @@ -1,5 +1,3 @@ -// -*- c-basic-offset: 8; indent-tabs-mode: t -*- -// vim:ts=8:sw=8:noet:ai: /* * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com> * @@ -31,8 +29,6 @@ #include FT_GLYPH_H #include FT_SYNTHESIS_H -#include "mputils.h" - #include "ass.h" #include "ass_font.h" #include "ass_bitmap.h" @@ -40,301 +36,283 @@ #include "ass_utils.h" #include "ass_fontconfig.h" #include "ass_library.h" - -#define MAX_GLYPHS 3000 -#define MAX_LINES 300 -#define BLUR_MAX_RADIUS 50.0 -#define MAX_BE 100 -#define ROUND(x) ((int) ((x) + .5)) -#define SUBPIXEL_MASK 56 // d6 bitmask for subpixel accuracy adjustment - -static int last_render_id = 0; - -typedef struct ass_settings_s { - int frame_width; - int frame_height; - double font_size_coeff; // font size multiplier - double line_spacing; // additional line spacing (in frame pixels) - int top_margin; // height of top margin. Everything except toptitles is shifted down by top_margin. - int bottom_margin; // height of bottom margin. (frame_height - top_margin - bottom_margin) is original video height. - int left_margin; - int right_margin; - int use_margins; // 0 - place all subtitles inside original frame - // 1 - use margins for placing toptitles and subtitles - double aspect; // frame aspect ratio, d_width / d_height. - ass_hinting_t hinting; - - char* default_font; - char* default_family; -} ass_settings_t; - -// a rendered event -typedef struct event_images_s { - ass_image_t* imgs; - int top, height; - int detect_collisions; - int shift_direction; - ass_event_t* event; -} event_images_t; - -struct ass_renderer_s { - ass_library_t* library; - FT_Library ftlibrary; - fc_instance_t* fontconfig_priv; - ass_settings_t settings; - int render_id; - ass_synth_priv_t* synth_priv; - - ass_image_t* images_root; // rendering result is stored here - ass_image_t* prev_images_root; +#include "ass_drawing.h" +#include "ass_render.h" +#include "ass_parse.h" - event_images_t* eimg; // temporary buffer for sorting rendered events - int eimg_size; // allocated buffer size -}; - -typedef enum {EF_NONE = 0, EF_KARAOKE, EF_KARAOKE_KF, EF_KARAOKE_KO} effect_t; +#define MAX_GLYPHS_INITIAL 1024 +#define MAX_LINES_INITIAL 64 +#define SUBPIXEL_MASK 63 +#define SUBPIXEL_ACCURACY 7 // d6 mask for subpixel accuracy adjustment +#define GLYPH_CACHE_MAX 1000 +#define BITMAP_CACHE_MAX_SIZE 50 * 1048576 -// describes a glyph -// glyph_info_t and text_info_t are used for text centering and word-wrapping operations -typedef struct glyph_info_s { - unsigned symbol; - FT_Glyph glyph; - FT_Glyph outline_glyph; - bitmap_t* bm; // glyph bitmap - bitmap_t* bm_o; // outline bitmap - bitmap_t* bm_s; // shadow bitmap - FT_BBox bbox; - FT_Vector pos; - char linebreak; // the first (leading) glyph of some line ? - uint32_t c[4]; // colors - FT_Vector advance; // 26.6 - effect_t effect_type; - int effect_timing; // time duration of current karaoke word - // after process_karaoke_effects: distance in pixels from the glyph origin. - // part of the glyph to the left of it is displayed in a different color. - int effect_skip_timing; // delay after the end of last karaoke word - int asc, desc; // font max ascender and descender -// int height; - int be; // blur edges - double blur; // gaussian blur - double shadow; - double frx, fry, frz; // rotation - - bitmap_hash_key_t hash_key; -} glyph_info_t; - -typedef struct line_info_s { - int asc, desc; -} line_info_t; - -typedef struct text_info_s { - glyph_info_t* glyphs; - int length; - line_info_t lines[MAX_LINES]; - int n_lines; - int height; -} text_info_t; - +static void ass_lazy_track_init(ASS_Renderer *render_priv) +{ + ASS_Track *track = render_priv->track; -// Renderer state. -// Values like current font face, color, screen position, clipping and so on are stored here. -typedef struct render_context_s { - ass_event_t* event; - ass_style_t* style; - - ass_font_t* font; - char* font_path; - double font_size; - - FT_Stroker stroker; - int alignment; // alignment overrides go here; if zero, style value will be used - double frx, fry, frz; - enum { EVENT_NORMAL, // "normal" top-, sub- or mid- title - EVENT_POSITIONED, // happens after pos(,), margins are ignored - EVENT_HSCROLL, // "Banner" transition effect, text_width is unlimited - EVENT_VSCROLL // "Scroll up", "Scroll down" transition effects - } evt_type; - double pos_x, pos_y; // position - double org_x, org_y; // origin - char have_origin; // origin is explicitly defined; if 0, get_base_point() is used - double scale_x, scale_y; - double hspacing; // distance between letters, in pixels - double border; // outline width - uint32_t c[4]; // colors(Primary, Secondary, so on) in RGBA - int clip_x0, clip_y0, clip_x1, clip_y1; - char detect_collisions; - uint32_t fade; // alpha from \fad - char be; // blur edges - double blur; // gaussian blur - double shadow; - int drawing_mode; // not implemented; when != 0 text is discarded, except for style override tags - - effect_t effect_type; - int effect_timing; - int effect_skip_timing; - - enum { SCROLL_LR, // left-to-right - SCROLL_RL, - SCROLL_TB, // top-to-bottom - SCROLL_BT - } scroll_direction; // for EVENT_HSCROLL, EVENT_VSCROLL - int scroll_shift; - - // face properties - char* family; - unsigned bold; - unsigned italic; - int treat_family_as_pattern; - -} render_context_t; - -// frame-global data -typedef struct frame_context_s { - ass_renderer_t* ass_priv; - int width, height; // screen dimensions - int orig_height; // frame height ( = screen height - margins ) - int orig_width; // frame width ( = screen width - margins ) - int orig_height_nocrop; // frame height ( = screen height - margins + cropheight) - int orig_width_nocrop; // frame width ( = screen width - margins + cropwidth) - ass_track_t* track; - long long time; // frame's timestamp, ms - double font_scale; - double font_scale_x; // x scale applied to all glyphs to preserve text aspect ratio - double border_scale; -} frame_context_t; - -static ass_renderer_t* ass_renderer; -static ass_settings_t* global_settings; -static text_info_t text_info; -static render_context_t render_context; -static frame_context_t frame_context; - -struct render_priv_s { - int top, height; - int render_id; -}; - -static void ass_lazy_track_init(void) -{ - ass_track_t* track = frame_context.track; - if (track->PlayResX && track->PlayResY) - return; - if (!track->PlayResX && !track->PlayResY) { - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_NeitherPlayResXNorPlayResYDefined); - track->PlayResX = 384; - track->PlayResY = 288; - } else { - double orig_aspect = (global_settings->aspect * frame_context.height * frame_context.orig_width) / - frame_context.orig_height / frame_context.width; - if (!track->PlayResY && track->PlayResX == 1280) { - track->PlayResY = 1024; - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_PlayResYUndefinedSettingY, track->PlayResY); - } else if (!track->PlayResY) { - track->PlayResY = track->PlayResX / orig_aspect + .5; - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_PlayResYUndefinedSettingY, track->PlayResY); - } else if (!track->PlayResX && track->PlayResY == 1024) { - track->PlayResX = 1280; - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_PlayResXUndefinedSettingX, track->PlayResX); - } else if (!track->PlayResX) { - track->PlayResX = track->PlayResY * orig_aspect + .5; - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_PlayResXUndefinedSettingX, track->PlayResX); - } - } + if (track->PlayResX && track->PlayResY) + return; + if (!track->PlayResX && !track->PlayResY) { + ass_msg(render_priv->library, MSGL_WARN, + "Neither PlayResX nor PlayResY defined. Assuming 384x288"); + track->PlayResX = 384; + track->PlayResY = 288; + } else { + if (!track->PlayResY && track->PlayResX == 1280) { + track->PlayResY = 1024; + ass_msg(render_priv->library, MSGL_WARN, + "PlayResY undefined, setting to %d", track->PlayResY); + } else if (!track->PlayResY) { + track->PlayResY = track->PlayResX * 3 / 4; + ass_msg(render_priv->library, MSGL_WARN, + "PlayResY undefined, setting to %d", track->PlayResY); + } else if (!track->PlayResX && track->PlayResY == 1024) { + track->PlayResX = 1280; + ass_msg(render_priv->library, MSGL_WARN, + "PlayResX undefined, setting to %d", track->PlayResX); + } else if (!track->PlayResX) { + track->PlayResX = track->PlayResY * 4 / 3; + ass_msg(render_priv->library, MSGL_WARN, + "PlayResX undefined, setting to %d", track->PlayResX); + } + } } -ass_renderer_t* ass_renderer_init(ass_library_t* library) +ASS_Renderer *ass_renderer_init(ASS_Library *library) { - int error; - FT_Library ft; - ass_renderer_t* priv = 0; - int vmajor, vminor, vpatch; - - memset(&render_context, 0, sizeof(render_context)); - memset(&frame_context, 0, sizeof(frame_context)); - memset(&text_info, 0, sizeof(text_info)); + int error; + FT_Library ft; + ASS_Renderer *priv = 0; + int vmajor, vminor, vpatch; - error = FT_Init_FreeType( &ft ); - if ( error ) { - mp_msg(MSGT_ASS, MSGL_FATAL, MSGTR_LIBASS_FT_Init_FreeTypeFailed); - goto ass_init_exit; - } + error = FT_Init_FreeType(&ft); + if (error) { + ass_msg(library, MSGL_FATAL, "%s failed", "FT_Init_FreeType"); + goto ass_init_exit; + } - FT_Library_Version(ft, &vmajor, &vminor, &vpatch); - mp_msg(MSGT_ASS, MSGL_V, "FreeType library version: %d.%d.%d\n", - vmajor, vminor, vpatch); - mp_msg(MSGT_ASS, MSGL_V, "FreeType headers version: %d.%d.%d\n", - FREETYPE_MAJOR, FREETYPE_MINOR, FREETYPE_PATCH); + FT_Library_Version(ft, &vmajor, &vminor, &vpatch); + ass_msg(library, MSGL_V, "FreeType library version: %d.%d.%d", + vmajor, vminor, vpatch); + ass_msg(library, MSGL_V, "FreeType headers version: %d.%d.%d", + FREETYPE_MAJOR, FREETYPE_MINOR, FREETYPE_PATCH); + + priv = calloc(1, sizeof(ASS_Renderer)); + if (!priv) { + FT_Done_FreeType(ft); + goto ass_init_exit; + } - priv = calloc(1, sizeof(ass_renderer_t)); - if (!priv) { - FT_Done_FreeType(ft); - goto ass_init_exit; - } + priv->synth_priv = ass_synth_init(BLUR_MAX_RADIUS); + + priv->library = library; + priv->ftlibrary = ft; + // images_root and related stuff is zero-filled in calloc - priv->synth_priv = ass_synth_init(BLUR_MAX_RADIUS); - - priv->library = library; - priv->ftlibrary = ft; - // images_root and related stuff is zero-filled in calloc + priv->cache.font_cache = ass_font_cache_init(library); + priv->cache.bitmap_cache = ass_bitmap_cache_init(library); + priv->cache.composite_cache = ass_composite_cache_init(library); + priv->cache.glyph_cache = ass_glyph_cache_init(library); + priv->cache.glyph_max = GLYPH_CACHE_MAX; + priv->cache.bitmap_max_size = BITMAP_CACHE_MAX_SIZE; - ass_font_cache_init(); - ass_bitmap_cache_init(); - ass_composite_cache_init(); - ass_glyph_cache_init(); + priv->text_info.max_glyphs = MAX_GLYPHS_INITIAL; + priv->text_info.max_lines = MAX_LINES_INITIAL; + priv->text_info.glyphs = + calloc(MAX_GLYPHS_INITIAL, sizeof(GlyphInfo)); + priv->text_info.lines = calloc(MAX_LINES_INITIAL, sizeof(LineInfo)); - text_info.glyphs = calloc(MAX_GLYPHS, sizeof(glyph_info_t)); + ass_init_exit: + if (priv) + ass_msg(library, MSGL_INFO, "Init"); + else + ass_msg(library, MSGL_ERR, "Init failed"); -ass_init_exit: - if (priv) mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_Init); - else mp_msg(MSGT_ASS, MSGL_ERR, MSGTR_LIBASS_InitFailed); - - return priv; + return priv; } -void ass_renderer_done(ass_renderer_t* priv) +void ass_set_cache_limits(ASS_Renderer *render_priv, int glyph_max, + int bitmap_max) +{ + render_priv->cache.glyph_max = glyph_max ? glyph_max : GLYPH_CACHE_MAX; + render_priv->cache.bitmap_max_size = bitmap_max ? 1048576 * bitmap_max : + BITMAP_CACHE_MAX_SIZE; +} + +static void free_list_clear(ASS_Renderer *render_priv) +{ + if (render_priv->free_head) { + FreeList *item = render_priv->free_head; + while(item) { + FreeList *oi = item; + free(item->object); + item = item->next; + free(oi); + } + render_priv->free_head = NULL; + } +} + +static void ass_free_images(ASS_Image *img); + +void ass_renderer_done(ASS_Renderer *render_priv) { - ass_font_cache_done(); - ass_bitmap_cache_done(); - ass_composite_cache_done(); - ass_glyph_cache_done(); - if (render_context.stroker) { - FT_Stroker_Done(render_context.stroker); - render_context.stroker = 0; - } - if (priv && priv->ftlibrary) FT_Done_FreeType(priv->ftlibrary); - if (priv && priv->fontconfig_priv) fontconfig_done(priv->fontconfig_priv); - if (priv && priv->synth_priv) ass_synth_done(priv->synth_priv); - if (priv && priv->eimg) free(priv->eimg); - if (priv) free(priv); - if (text_info.glyphs) free(text_info.glyphs); + ass_font_cache_done(render_priv->cache.font_cache); + ass_bitmap_cache_done(render_priv->cache.bitmap_cache); + ass_composite_cache_done(render_priv->cache.composite_cache); + ass_glyph_cache_done(render_priv->cache.glyph_cache); + + ass_free_images(render_priv->images_root); + ass_free_images(render_priv->prev_images_root); + + if (render_priv->state.stroker) { + FT_Stroker_Done(render_priv->state.stroker); + render_priv->state.stroker = 0; + } + if (render_priv && render_priv->ftlibrary) + FT_Done_FreeType(render_priv->ftlibrary); + if (render_priv && render_priv->fontconfig_priv) + fontconfig_done(render_priv->fontconfig_priv); + if (render_priv && render_priv->synth_priv) + ass_synth_done(render_priv->synth_priv); + if (render_priv && render_priv->eimg) + free(render_priv->eimg); + free(render_priv->text_info.glyphs); + free(render_priv->text_info.lines); + + free(render_priv->settings.default_font); + free(render_priv->settings.default_family); + + free_list_clear(render_priv); + free(render_priv); } /** - * \brief Create a new ass_image_t - * Parameters are the same as ass_image_t fields. + * \brief Create a new ASS_Image + * Parameters are the same as ASS_Image fields. */ -static ass_image_t* my_draw_bitmap(unsigned char* bitmap, int bitmap_w, int bitmap_h, int stride, int dst_x, int dst_y, uint32_t color) +static ASS_Image *my_draw_bitmap(unsigned char *bitmap, int bitmap_w, + int bitmap_h, int stride, int dst_x, + int dst_y, uint32_t color) { - ass_image_t* img = calloc(1, sizeof(ass_image_t)); + ASS_Image *img = calloc(1, sizeof(ASS_Image)); + + img->w = bitmap_w; + img->h = bitmap_h; + img->stride = stride; + img->bitmap = bitmap; + img->color = color; + img->dst_x = dst_x; + img->dst_y = dst_y; + + return img; +} + +static double x2scr_pos(ASS_Renderer *render_priv, double x); +static double y2scr_pos(ASS_Renderer *render_priv, double y); + +/* + * \brief Convert bitmap glyphs into ASS_Image list with inverse clipping + * + * Inverse clipping with the following strategy: + * - find rectangle from (x0, y0) to (cx0, y1) + * - find rectangle from (cx0, y0) to (cx1, cy0) + * - find rectangle from (cx0, cy1) to (cx1, y1) + * - find rectangle from (cx1, y0) to (x1, y1) + * These rectangles can be invalid and in this case are discarded. + * Afterwards, they are clipped against the screen coordinates. + * In an additional pass, the rectangles need to be split up left/right for + * karaoke effects. This can result in a lot of bitmaps (6 to be exact). + */ +static ASS_Image **render_glyph_i(ASS_Renderer *render_priv, + Bitmap *bm, int dst_x, int dst_y, + uint32_t color, uint32_t color2, int brk, + ASS_Image **tail) +{ + int i, j, x0, y0, x1, y1, cx0, cy0, cx1, cy1, sx, sy, zx, zy; + Rect r[4]; + ASS_Image *img; + + dst_x += bm->left; + dst_y += bm->top; + + // we still need to clip against screen boundaries + zx = x2scr_pos(render_priv, 0); + zy = y2scr_pos(render_priv, 0); + sx = x2scr_pos(render_priv, render_priv->track->PlayResX); + sy = y2scr_pos(render_priv, render_priv->track->PlayResY); - assert(dst_x >= 0); - assert(dst_y >= 0); - assert(dst_x + bitmap_w <= frame_context.width); - assert(dst_y + bitmap_h <= frame_context.height); + x0 = 0; + y0 = 0; + x1 = bm->w; + y1 = bm->h; + cx0 = render_priv->state.clip_x0 - dst_x; + cy0 = render_priv->state.clip_y0 - dst_y; + cx1 = render_priv->state.clip_x1 - dst_x; + cy1 = render_priv->state.clip_y1 - dst_y; + + // calculate rectangles and discard invalid ones while we're at it. + i = 0; + r[i].x0 = x0; + r[i].y0 = y0; + r[i].x1 = (cx0 > x1) ? x1 : cx0; + r[i].y1 = y1; + if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++; + r[i].x0 = (cx0 < 0) ? x0 : cx0; + r[i].y0 = y0; + r[i].x1 = (cx1 > x1) ? x1 : cx1; + r[i].y1 = (cy0 > y1) ? y1 : cy0; + if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++; + r[i].x0 = (cx0 < 0) ? x0 : cx0; + r[i].y0 = (cy1 < 0) ? y0 : cy1; + r[i].x1 = (cx1 > x1) ? x1 : cx1; + r[i].y1 = y1; + if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++; + r[i].x0 = (cx1 < 0) ? x0 : cx1; + r[i].y0 = y0; + r[i].x1 = x1; + r[i].y1 = y1; + if (r[i].x1 > r[i].x0 && r[i].y1 > r[i].y0) i++; - img->w = bitmap_w; - img->h = bitmap_h; - img->stride = stride; - img->bitmap = bitmap; - img->color = color; - img->dst_x = dst_x; - img->dst_y = dst_y; + // clip each rectangle to screen coordinates + for (j = 0; j < i; j++) { + r[j].x0 = (r[j].x0 + dst_x < zx) ? zx - dst_x : r[j].x0; + r[j].y0 = (r[j].y0 + dst_y < zy) ? zy - dst_y : r[j].y0; + r[j].x1 = (r[j].x1 + dst_x > sx) ? sx - dst_x : r[j].x1; + r[j].y1 = (r[j].y1 + dst_y > sy) ? sy - dst_y : r[j].y1; + } - return img; + // draw the rectangles + for (j = 0; j < i; j++) { + int lbrk = brk; + // kick out rectangles that are invalid now + if (r[j].x1 <= r[j].x0 || r[j].y1 <= r[j].y0) + continue; + // split up into left and right for karaoke, if needed + if (lbrk > r[j].x0) { + if (lbrk > r[j].x1) lbrk = r[j].x1; + img = my_draw_bitmap(bm->buffer + r[j].y0 * bm->w + r[j].x0, + lbrk - r[j].x0, r[j].y1 - r[j].y0, + bm->w, dst_x + r[j].x0, dst_y + r[j].y0, color); + *tail = img; + tail = &img->next; + } + if (lbrk < r[j].x1) { + if (lbrk < r[j].x0) lbrk = r[j].x0; + img = my_draw_bitmap(bm->buffer + r[j].y0 * bm->w + lbrk, + r[j].x1 - lbrk, r[j].y1 - r[j].y0, + bm->w, dst_x + lbrk, dst_y + r[j].y0, color2); + *tail = img; + tail = &img->next; + } + } + + return tail; } /** - * \brief convert bitmap glyph into ass_image_t struct(s) + * \brief convert bitmap glyph into ASS_Image struct(s) * \param bit freetype bitmap glyph, FT_PIXEL_MODE_GRAY * \param dst_x bitmap x coordinate in video frame * \param dst_y bitmap y coordinate in video frame @@ -345,1156 +323,871 @@ * \return pointer to the new list tail * Performs clipping. Uses my_draw_bitmap for actual bitmap convertion. */ -static ass_image_t** render_glyph(bitmap_t* bm, int dst_x, int dst_y, uint32_t color, uint32_t color2, int brk, ass_image_t** tail) +static ASS_Image ** +render_glyph(ASS_Renderer *render_priv, Bitmap *bm, int dst_x, int dst_y, + uint32_t color, uint32_t color2, int brk, ASS_Image **tail) { - // brk is relative to dst_x - // color = color left of brk - // color2 = color right of brk - int b_x0, b_y0, b_x1, b_y1; // visible part of the bitmap - int tmp; - ass_image_t* img; + // Inverse clipping in use? + if (render_priv->state.clip_mode) + return render_glyph_i(render_priv, bm, dst_x, dst_y, color, color2, + brk, tail); - const int clip_x0 = render_context.clip_x0; - const int clip_y0 = render_context.clip_y0; - const int clip_x1 = render_context.clip_x1; - const int clip_y1 = render_context.clip_y1; + // brk is relative to dst_x + // color = color left of brk + // color2 = color right of brk + int b_x0, b_y0, b_x1, b_y1; // visible part of the bitmap + int clip_x0, clip_y0, clip_x1, clip_y1; + int tmp; + ASS_Image *img; - dst_x += bm->left; - dst_y += bm->top; - brk -= bm->left; + dst_x += bm->left; + dst_y += bm->top; + brk -= bm->left; - b_x0 = 0; - b_y0 = 0; - b_x1 = bm->w; - b_y1 = bm->h; + // clipping + clip_x0 = FFMINMAX(render_priv->state.clip_x0, 0, render_priv->width); + clip_y0 = FFMINMAX(render_priv->state.clip_y0, 0, render_priv->height); + clip_x1 = FFMINMAX(render_priv->state.clip_x1, 0, render_priv->width); + clip_y1 = FFMINMAX(render_priv->state.clip_y1, 0, render_priv->height); + b_x0 = 0; + b_y0 = 0; + b_x1 = bm->w; + b_y1 = bm->h; - tmp = dst_x - clip_x0; - if (tmp < 0) { - mp_msg(MSGT_ASS, MSGL_DBG2, "clip left\n"); - b_x0 = - tmp; - } - tmp = dst_y - clip_y0; - if (tmp < 0) { - mp_msg(MSGT_ASS, MSGL_DBG2, "clip top\n"); - b_y0 = - tmp; - } - tmp = clip_x1 - dst_x - bm->w; - if (tmp < 0) { - mp_msg(MSGT_ASS, MSGL_DBG2, "clip right\n"); - b_x1 = bm->w + tmp; - } - tmp = clip_y1 - dst_y - bm->h; - if (tmp < 0) { - mp_msg(MSGT_ASS, MSGL_DBG2, "clip bottom\n"); - b_y1 = bm->h + tmp; - } + tmp = dst_x - clip_x0; + if (tmp < 0) { + ass_msg(render_priv->library, MSGL_DBG2, "clip left"); + b_x0 = -tmp; + } + tmp = dst_y - clip_y0; + if (tmp < 0) { + ass_msg(render_priv->library, MSGL_DBG2, "clip top"); + b_y0 = -tmp; + } + tmp = clip_x1 - dst_x - bm->w; + if (tmp < 0) { + ass_msg(render_priv->library, MSGL_DBG2, "clip right"); + b_x1 = bm->w + tmp; + } + tmp = clip_y1 - dst_y - bm->h; + if (tmp < 0) { + ass_msg(render_priv->library, MSGL_DBG2, "clip bottom"); + b_y1 = bm->h + tmp; + } - if ((b_y0 >= b_y1) || (b_x0 >= b_x1)) - return tail; + if ((b_y0 >= b_y1) || (b_x0 >= b_x1)) + return tail; - if (brk > b_x0) { // draw left part - if (brk > b_x1) brk = b_x1; - img = my_draw_bitmap(bm->buffer + bm->w * b_y0 + b_x0, - brk - b_x0, b_y1 - b_y0, bm->w, - dst_x + b_x0, dst_y + b_y0, color); - *tail = img; - tail = &img->next; - } - if (brk < b_x1) { // draw right part - if (brk < b_x0) brk = b_x0; - img = my_draw_bitmap(bm->buffer + bm->w * b_y0 + brk, - b_x1 - brk, b_y1 - b_y0, bm->w, - dst_x + brk, dst_y + b_y0, color2); - *tail = img; - tail = &img->next; - } - return tail; + if (brk > b_x0) { // draw left part + if (brk > b_x1) + brk = b_x1; + img = my_draw_bitmap(bm->buffer + bm->w * b_y0 + b_x0, + brk - b_x0, b_y1 - b_y0, bm->w, + dst_x + b_x0, dst_y + b_y0, color); + *tail = img; + tail = &img->next; + } + if (brk < b_x1) { // draw right part + if (brk < b_x0) + brk = b_x0; + img = my_draw_bitmap(bm->buffer + bm->w * b_y0 + brk, + b_x1 - brk, b_y1 - b_y0, bm->w, + dst_x + brk, dst_y + b_y0, color2); + *tail = img; + tail = &img->next; + } + return tail; } /** - * \brief Replaces the bitmap buffer in ass_image_t with its copy. - * - * @param img Image to operate on. - * @return Address of the old buffer. + * \brief Replace the bitmap buffer in ASS_Image with a copy + * \param img ASS_Image to operate on + * \return pointer to old bitmap buffer */ -static unsigned char* clone_bitmap_data(ass_image_t* img) +static unsigned char *clone_bitmap_buffer(ASS_Image *img) { - unsigned char* old_bitmap = img->bitmap; - int size = img->stride * (img->h - 1) + img->w; - img->bitmap = malloc(size); - memcpy(img->bitmap, old_bitmap, size); - return old_bitmap; + unsigned char *old_bitmap = img->bitmap; + int size = img->stride * (img->h - 1) + img->w; + img->bitmap = malloc(size); + memcpy(img->bitmap, old_bitmap, size); + return old_bitmap; } /** * \brief Calculate overlapping area of two consecutive bitmaps and in case they - * overlap, composite them together + * overlap, blend them together * Mainly useful for translucent glyphs and especially borders, to avoid the * luminance adding up where they overlap (which looks ugly) */ -static void render_overlap(ass_image_t** last_tail, ass_image_t** tail, bitmap_hash_key_t *last_hash, bitmap_hash_key_t* hash) { - int left, top, bottom, right; - int old_left, old_top, w, h, cur_left, cur_top; - int x, y, opos, cpos; - char m; - composite_hash_key_t hk; - composite_hash_val_t *hv; - composite_hash_key_t *nhk; - int ax = (*last_tail)->dst_x; - int ay = (*last_tail)->dst_y; - int aw = (*last_tail)->w; - int as = (*last_tail)->stride; - int ah = (*last_tail)->h; - int bx = (*tail)->dst_x; - int by = (*tail)->dst_y; - int bw = (*tail)->w; - int bs = (*tail)->stride; - int bh = (*tail)->h; - unsigned char* a; - unsigned char* b; +static void +render_overlap(ASS_Renderer *render_priv, ASS_Image **last_tail, + ASS_Image **tail) +{ + int left, top, bottom, right; + int old_left, old_top, w, h, cur_left, cur_top; + int x, y, opos, cpos; + char m; + CompositeHashKey hk; + CompositeHashValue *hv; + CompositeHashValue chv; + int ax = (*last_tail)->dst_x; + int ay = (*last_tail)->dst_y; + int aw = (*last_tail)->w; + int as = (*last_tail)->stride; + int ah = (*last_tail)->h; + int bx = (*tail)->dst_x; + int by = (*tail)->dst_y; + int bw = (*tail)->w; + int bs = (*tail)->stride; + int bh = (*tail)->h; + unsigned char *a; + unsigned char *b; - if ((*last_tail)->bitmap == (*tail)->bitmap) - return; + if ((*last_tail)->bitmap == (*tail)->bitmap) + return; - if ((*last_tail)->color != (*tail)->color) - return; + if ((*last_tail)->color != (*tail)->color) + return; - // Calculate overlap coordinates - left = (ax > bx) ? ax : bx; - top = (ay > by) ? ay : by; - right = ((ax+aw) < (bx+bw)) ? (ax+aw) : (bx+bw); - bottom = ((ay+ah) < (by+bh)) ? (ay+ah) : (by+bh); - if ((right <= left) || (bottom <= top)) - return; - old_left = left-ax; - old_top = top-ay; - w = right-left; - h = bottom-top; - cur_left = left-bx; - cur_top = top-by; + // Calculate overlap coordinates + left = (ax > bx) ? ax : bx; + top = (ay > by) ? ay : by; + right = ((ax + aw) < (bx + bw)) ? (ax + aw) : (bx + bw); + bottom = ((ay + ah) < (by + bh)) ? (ay + ah) : (by + bh); + if ((right <= left) || (bottom <= top)) + return; + old_left = left - ax; + old_top = top - ay; + w = right - left; + h = bottom - top; + cur_left = left - bx; + cur_top = top - by; - // Query cache - memset(&hk, 0, sizeof(hk)); - memcpy(&hk.a, last_hash, sizeof(*last_hash)); - memcpy(&hk.b, hash, sizeof(*hash)); - hk.aw = aw; - hk.ah = ah; - hk.bw = bw; - hk.bh = bh; - hk.ax = ax; - hk.ay = ay; - hk.bx = bx; - hk.by = by; - hv = cache_find_composite(&hk); - if (hv) { - (*last_tail)->bitmap = hv->a; - (*tail)->bitmap = hv->b; - return; - } + // Query cache + memset(&hk, 0, sizeof(hk)); + hk.a = (*last_tail)->bitmap; + hk.b = (*tail)->bitmap; + hk.aw = aw; + hk.ah = ah; + hk.bw = bw; + hk.bh = bh; + hk.ax = ax; + hk.ay = ay; + hk.bx = bx; + hk.by = by; + hk.as = as; + hk.bs = bs; + hv = cache_find_composite(render_priv->cache.composite_cache, &hk); + if (hv) { + (*last_tail)->bitmap = hv->a; + (*tail)->bitmap = hv->b; + return; + } + // Allocate new bitmaps and copy over data + a = clone_bitmap_buffer(*last_tail); + b = clone_bitmap_buffer(*tail); - // Allocate new bitmaps and copy over data - a = clone_bitmap_data(*last_tail); - b = clone_bitmap_data(*tail); + // Blend overlapping area + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) { + opos = (old_top + y) * (as) + (old_left + x); + cpos = (cur_top + y) * (bs) + (cur_left + x); + m = FFMIN(a[opos] + b[cpos], 0xff); + (*last_tail)->bitmap[opos] = 0; + (*tail)->bitmap[cpos] = m; + } - // Composite overlapping area - for (y=0; y<h; y++) - for (x=0; x<w; x++) { - opos = (old_top+y)*(as) + (old_left+x); - cpos = (cur_top+y)*(bs) + (cur_left+x); - m = (a[opos] > b[cpos]) ? a[opos] : b[cpos]; - (*last_tail)->bitmap[opos] = 0; - (*tail)->bitmap[cpos] = m; - } + // Insert bitmaps into the cache + chv.a = (*last_tail)->bitmap; + chv.b = (*tail)->bitmap; + cache_add_composite(render_priv->cache.composite_cache, &hk, &chv); +} - // Insert bitmaps into the cache - nhk = calloc(1, sizeof(*nhk)); - memcpy(nhk, &hk, sizeof(*nhk)); - hv = calloc(1, sizeof(*hv)); - hv->a = (*last_tail)->bitmap; - hv->b = (*tail)->bitmap; - cache_add_composite(nhk, hv); +static void free_list_add(ASS_Renderer *render_priv, void *object) +{ + if (!render_priv->free_head) { + render_priv->free_head = calloc(1, sizeof(FreeList)); + render_priv->free_head->object = object; + render_priv->free_tail = render_priv->free_head; + } else { + FreeList *l = calloc(1, sizeof(FreeList)); + l->object = object; + render_priv->free_tail->next = l; + render_priv->free_tail = render_priv->free_tail->next; + } } /** - * \brief Convert text_info_t struct to ass_image_t list - * Splits glyphs in halves when needed (for \kf karaoke). + * Iterate through a list of bitmaps and blend with clip vector, if + * applicable. The blended bitmaps are added to a free list which is freed + * at the start of a new frame. */ -static ass_image_t* render_text(text_info_t* text_info, int dst_x, int dst_y) +static void blend_vector_clip(ASS_Renderer *render_priv, + ASS_Image *head) { - int pen_x, pen_y; - int i; - bitmap_t* bm; - ass_image_t* head; - ass_image_t** tail = &head; - ass_image_t** last_tail = 0; - ass_image_t** here_tail = 0; - bitmap_hash_key_t* last_hash = 0; + FT_Glyph glyph; + FT_BitmapGlyph clip_bm; + ASS_Image *cur; + ASS_Drawing *drawing = render_priv->state.clip_drawing; + int error; + + if (!drawing) + return; - for (i = 0; i < text_info->length; ++i) { - glyph_info_t* info = text_info->glyphs + i; - if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm_s || (info->shadow == 0)) - continue; + // Rasterize it + FT_Glyph_Copy((FT_Glyph) drawing->glyph, &glyph); + error = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 1); + if (error) { + ass_msg(render_priv->library, MSGL_V, + "Clip vector rasterization failed: %d. Skipping.", error); + goto blend_vector_exit; + } + clip_bm = (FT_BitmapGlyph) glyph; + clip_bm->top = -clip_bm->top; - pen_x = dst_x + info->pos.x + ROUND(info->shadow * frame_context.border_scale); - pen_y = dst_y + info->pos.y + ROUND(info->shadow * frame_context.border_scale); - bm = info->bm_s; + assert(clip_bm->bitmap.pitch >= 0); - here_tail = tail; - tail = render_glyph(bm, pen_x, pen_y, info->c[3], 0, 1000000, tail); - if (last_tail && tail != here_tail && ((info->c[3] & 0xff) > 0)) - render_overlap(last_tail, here_tail, last_hash, &info->hash_key); - last_tail = here_tail; - last_hash = &info->hash_key; - } + // Iterate through bitmaps and blend/clip them + for (cur = head; cur; cur = cur->next) { + int left, top, right, bottom, apos, bpos, y, x, w, h; + int ax, ay, aw, ah, as; + int bx, by, bw, bh, bs; + int aleft, atop, bleft, btop; + unsigned char *abuffer, *bbuffer, *nbuffer; - last_tail = 0; - for (i = 0; i < text_info->length; ++i) { - glyph_info_t* info = text_info->glyphs + i; - if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm_o) - continue; + abuffer = cur->bitmap; + bbuffer = clip_bm->bitmap.buffer; + ax = cur->dst_x; + ay = cur->dst_y; + aw = cur->w; + ah = cur->h; + as = cur->stride; + bx = clip_bm->left; + by = clip_bm->top; + bw = clip_bm->bitmap.width; + bh = clip_bm->bitmap.rows; + bs = clip_bm->bitmap.pitch; - pen_x = dst_x + info->pos.x; - pen_y = dst_y + info->pos.y; - bm = info->bm_o; + // Calculate overlap coordinates + left = (ax > bx) ? ax : bx; + top = (ay > by) ? ay : by; + right = ((ax + aw) < (bx + bw)) ? (ax + aw) : (bx + bw); + bottom = ((ay + ah) < (by + bh)) ? (ay + ah) : (by + bh); + aleft = left - ax; + atop = top - ay; + w = right - left; + h = bottom - top; + bleft = left - bx; + btop = top - by; + + if (render_priv->state.clip_drawing_mode) { + // Inverse clip + if (ax + aw < bx || ay + ah < by || ax > bx + bw || + ay > by + bh) { + continue; + } + + // Allocate new buffer and add to free list + nbuffer = malloc(as * ah); + free_list_add(render_priv, nbuffer); - if ((info->effect_type == EF_KARAOKE_KO) && (info->effect_timing <= info->bbox.xMax)) { - // do nothing - } else { - here_tail = tail; - tail = render_glyph(bm, pen_x, pen_y, info->c[2], 0, 1000000, tail); - if (last_tail && tail != here_tail && ((info->c[2] & 0xff) > 0)) - render_overlap(last_tail, here_tail, last_hash, &info->hash_key); - last_tail = here_tail; - last_hash = &info->hash_key; - } - } - for (i = 0; i < text_info->length; ++i) { - glyph_info_t* info = text_info->glyphs + i; - if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm) - continue; + // Blend together + memcpy(nbuffer, abuffer, as * (ah - 1) + aw); + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) { + apos = (atop + y) * as + aleft + x; + bpos = (btop + y) * bs + bleft + x; + nbuffer[apos] = FFMAX(0, abuffer[apos] - bbuffer[bpos]); + } + } else { + // Regular clip + if (ax + aw < bx || ay + ah < by || ax > bx + bw || + ay > by + bh) { + cur->w = cur->h = 0; + continue; + } - pen_x = dst_x + info->pos.x; - pen_y = dst_y + info->pos.y; - bm = info->bm; + // Allocate new buffer and add to free list + nbuffer = calloc(as, ah); + free_list_add(render_priv, nbuffer); - if ((info->effect_type == EF_KARAOKE) || (info->effect_type == EF_KARAOKE_KO)) { - if (info->effect_timing > info->bbox.xMax) - tail = render_glyph(bm, pen_x, pen_y, info->c[0], 0, 1000000, tail); - else - tail = render_glyph(bm, pen_x, pen_y, info->c[1], 0, 1000000, tail); - } else if (info->effect_type == EF_KARAOKE_KF) { - tail = render_glyph(bm, pen_x, pen_y, info->c[0], info->c[1], info->effect_timing, tail); - } else - tail = render_glyph(bm, pen_x, pen_y, info->c[0], 0, 1000000, tail); - } + // Blend together + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) { + apos = (atop + y) * as + aleft + x; + bpos = (btop + y) * bs + bleft + x; + nbuffer[apos] = (abuffer[apos] * bbuffer[bpos] + 255) >> 8; + } + } + cur->bitmap = nbuffer; + } - *tail = 0; - return head; + // Free clip vector and its bitmap, we don't need it anymore + FT_Done_Glyph(glyph); +blend_vector_exit: + ass_drawing_free(render_priv->state.clip_drawing); + render_priv->state.clip_drawing = 0; } /** - * \brief Mapping between script and screen coordinates + * \brief Convert TextInfo struct to ASS_Image list + * Splits glyphs in halves when needed (for \kf karaoke). */ -static int x2scr(double x) { - return x*frame_context.orig_width_nocrop / frame_context.track->PlayResX + - FFMAX(global_settings->left_margin, 0); -} -static double x2scr_pos(double x) { - return x*frame_context.orig_width / frame_context.track->PlayResX + - global_settings->left_margin; +static ASS_Image *render_text(ASS_Renderer *render_priv, int dst_x, + int dst_y) +{ + int pen_x, pen_y; + int i; + Bitmap *bm; + ASS_Image *head; + ASS_Image **tail = &head; + ASS_Image **last_tail = 0; + ASS_Image **here_tail = 0; + TextInfo *text_info = &render_priv->text_info; + + for (i = 0; i < text_info->length; ++i) { + GlyphInfo *info = text_info->glyphs + i; + if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm_s + || (info->shadow_x == 0 && info->shadow_y == 0) || info->skip) + continue; + + pen_x = + dst_x + (info->pos.x >> 6) + + (int) (info->shadow_x * render_priv->border_scale); + pen_y = + dst_y + (info->pos.y >> 6) + + (int) (info->shadow_y * render_priv->border_scale); + bm = info->bm_s; + + here_tail = tail; + tail = + render_glyph(render_priv, bm, pen_x, pen_y, info->c[3], 0, + 1000000, tail); + if (last_tail && tail != here_tail && ((info->c[3] & 0xff) > 0)) + render_overlap(render_priv, last_tail, here_tail); + + last_tail = here_tail; + } + + last_tail = 0; + for (i = 0; i < text_info->length; ++i) { + GlyphInfo *info = text_info->glyphs + i; + if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm_o + || info->skip) + continue; + + pen_x = dst_x + (info->pos.x >> 6); + pen_y = dst_y + (info->pos.y >> 6); + bm = info->bm_o; + + if ((info->effect_type == EF_KARAOKE_KO) + && (info->effect_timing <= (info->bbox.xMax >> 6))) { + // do nothing + } else { + here_tail = tail; + tail = + render_glyph(render_priv, bm, pen_x, pen_y, info->c[2], + 0, 1000000, tail); + if (last_tail && tail != here_tail && ((info->c[2] & 0xff) > 0)) + render_overlap(render_priv, last_tail, here_tail); + + last_tail = here_tail; + } + } + + for (i = 0; i < text_info->length; ++i) { + GlyphInfo *info = text_info->glyphs + i; + if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm + || info->skip) + continue; + + pen_x = dst_x + (info->pos.x >> 6); + pen_y = dst_y + (info->pos.y >> 6); + bm = info->bm; + + if ((info->effect_type == EF_KARAOKE) + || (info->effect_type == EF_KARAOKE_KO)) { + if (info->effect_timing > (info->bbox.xMax >> 6)) + tail = + render_glyph(render_priv, bm, pen_x, pen_y, + info->c[0], 0, 1000000, tail); + else + tail = + render_glyph(render_priv, bm, pen_x, pen_y, + info->c[1], 0, 1000000, tail); + } else if (info->effect_type == EF_KARAOKE_KF) { + tail = + render_glyph(render_priv, bm, pen_x, pen_y, info->c[0], + info->c[1], info->effect_timing, tail); + } else + tail = + render_glyph(render_priv, bm, pen_x, pen_y, info->c[0], + 0, 1000000, tail); + } + + *tail = 0; + blend_vector_clip(render_priv, head); + + return head; } /** * \brief Mapping between script and screen coordinates */ -static double y2scr(double y) { - return y * frame_context.orig_height_nocrop / frame_context.track->PlayResY + - FFMAX(global_settings->top_margin, 0); -} -static double y2scr_pos(double y) { - return y * frame_context.orig_height / frame_context.track->PlayResY + - global_settings->top_margin; -} - -// the same for toptitles -static int y2scr_top(double y) { - if (global_settings->use_margins) - return y * frame_context.orig_height_nocrop / frame_context.track->PlayResY; - else - return y * frame_context.orig_height_nocrop / frame_context.track->PlayResY + - FFMAX(global_settings->top_margin, 0); -} -// the same for subtitles -static int y2scr_sub(double y) { - if (global_settings->use_margins) - return y * frame_context.orig_height_nocrop / frame_context.track->PlayResY + - FFMAX(global_settings->top_margin, 0) + - FFMAX(global_settings->bottom_margin, 0); - else - return y * frame_context.orig_height_nocrop / frame_context.track->PlayResY + - FFMAX(global_settings->top_margin, 0); -} - -static void compute_string_bbox( text_info_t* info, FT_BBox *abbox ) { - FT_BBox bbox; - int i; - - if (text_info.length > 0) { - bbox.xMin = 32000; - bbox.xMax = -32000; - bbox.yMin = - d6_to_int(text_info.lines[0].asc) + text_info.glyphs[0].pos.y; - bbox.yMax = d6_to_int(text_info.height - text_info.lines[0].asc) + text_info.glyphs[0].pos.y; - - for (i = 0; i < text_info.length; ++i) { - int s = text_info.glyphs[i].pos.x; - int e = s + d6_to_int(text_info.glyphs[i].advance.x); - bbox.xMin = FFMIN(bbox.xMin, s); - bbox.xMax = FFMAX(bbox.xMax, e); - } - } else - bbox.xMin = bbox.xMax = bbox.yMin = bbox.yMax = 0; - - /* return string bbox */ - *abbox = bbox; -} - - -/** - * \brief Check if starting part of (*p) matches sample. If true, shift p to the first symbol after the matching part. - */ -static inline int mystrcmp(char** p, const char* sample) { - int len = strlen(sample); - if (strncmp(*p, sample, len) == 0) { - (*p) += len; - return 1; - } else - return 0; -} - -static void change_font_size(double sz) -{ - double size = sz * frame_context.font_scale; - - if (size < 1) - size = 1; - else if (size > frame_context.height * 2) - size = frame_context.height * 2; - - ass_font_set_size(render_context.font, size); - - render_context.font_size = sz; -} - -/** - * \brief Change current font, using setting from render_context. - */ -static void update_font(void) +static double x2scr(ASS_Renderer *render_priv, double x) { - unsigned val; - ass_renderer_t* priv = frame_context.ass_priv; - ass_font_desc_t desc; - desc.family = strdup(render_context.family); - desc.treat_family_as_pattern = render_context.treat_family_as_pattern; - - val = render_context.bold; - // 0 = normal, 1 = bold, >1 = exact weight - if (val == 0) val = 80; // normal - else if (val == 1) val = 200; // bold - desc.bold = val; - - val = render_context.italic; - if (val == 0) val = 0; // normal - else if (val == 1) val = 110; //italic - desc.italic = val; - - render_context.font = ass_font_new(priv->library, priv->ftlibrary, priv->fontconfig_priv, &desc); - free(desc.family); - - if (render_context.font) - change_font_size(render_context.font_size); + return x * render_priv->orig_width_nocrop / + render_priv->track->PlayResX + + FFMAX(render_priv->settings.left_margin, 0); } - -/** - * \brief Change border width - * negative value resets border to style value - */ -static void change_border(double border) +static double x2scr_pos(ASS_Renderer *render_priv, double x) { - int b; - if (!render_context.font) return; - - if (border < 0) { - if (render_context.style->BorderStyle == 1) - border = render_context.style->Outline; - else - border = 1.; - } - render_context.border = border; - - b = 64 * border * frame_context.border_scale; - if (b > 0) { - if (!render_context.stroker) { - int error; -#if (FREETYPE_MAJOR > 2) || ((FREETYPE_MAJOR == 2) && (FREETYPE_MINOR > 1)) - error = FT_Stroker_New( ass_renderer->ftlibrary, &render_context.stroker ); -#else // < 2.2 - error = FT_Stroker_New( render_context.font->faces[0]->memory, &render_context.stroker ); -#endif - if (error) { - mp_msg(MSGT_ASS, MSGL_V, "failed to get stroker\n"); - render_context.stroker = 0; - } - } - if (render_context.stroker) - FT_Stroker_Set( render_context.stroker, b, - FT_STROKER_LINECAP_ROUND, - FT_STROKER_LINEJOIN_ROUND, - 0 ); - } else { - FT_Stroker_Done(render_context.stroker); - render_context.stroker = 0; - } -} - -#define _r(c) ((c)>>24) -#define _g(c) (((c)>>16)&0xFF) -#define _b(c) (((c)>>8)&0xFF) -#define _a(c) ((c)&0xFF) - -/** - * \brief Calculate a weighted average of two colors - * calculates c1*(1-a) + c2*a, but separately for each component except alpha - */ -static void change_color(uint32_t* var, uint32_t new, double pwr) -{ - (*var)= ((uint32_t)(_r(*var) * (1 - pwr) + _r(new) * pwr) << 24) + - ((uint32_t)(_g(*var) * (1 - pwr) + _g(new) * pwr) << 16) + - ((uint32_t)(_b(*var) * (1 - pwr) + _b(new) * pwr) << 8) + - _a(*var); -} - -// like change_color, but for alpha component only -static void change_alpha(uint32_t* var, uint32_t new, double pwr) -{ - *var = (_r(*var) << 24) + (_g(*var) << 16) + (_b(*var) << 8) + (_a(*var) * (1 - pwr) + _a(new) * pwr); -} - -/** - * \brief Multiply two alpha values - * \param a first value - * \param b second value - * \return result of multiplication - * Parameters and result are limited by 0xFF. - */ -static uint32_t mult_alpha(uint32_t a, uint32_t b) -{ - return 0xFF - (0xFF - a) * (0xFF - b) / 0xFF; + return x * render_priv->orig_width / render_priv->track->PlayResX + + render_priv->settings.left_margin; } /** - * \brief Calculate alpha value by piecewise linear function - * Used for \fad, \fade implementation. + * \brief Mapping between script and screen coordinates */ -static unsigned interpolate_alpha(long long now, - long long t1, long long t2, long long t3, long long t4, - unsigned a1, unsigned a2, unsigned a3) +static double y2scr(ASS_Renderer *render_priv, double y) +{ + return y * render_priv->orig_height_nocrop / + render_priv->track->PlayResY + + FFMAX(render_priv->settings.top_margin, 0); +} +static double y2scr_pos(ASS_Renderer *render_priv, double y) { - unsigned a; - double cf; - if (now <= t1) { - a = a1; - } else if (now >= t4) { - a = a3; - } else if (now < t2) { // and > t1 - cf = ((double)(now - t1)) / (t2 - t1); - a = a1 * (1 - cf) + a2 * cf; - } else if (now > t3) { - cf = ((double)(now - t3)) / (t4 - t3); - a = a2 * (1 - cf) + a3 * cf; - } else { // t2 <= now <= t3 - a = a2; - } + return y * render_priv->orig_height / render_priv->track->PlayResY + + render_priv->settings.top_margin; +} - return a; +// the same for toptitles +static double y2scr_top(ASS_Renderer *render_priv, double y) +{ + if (render_priv->settings.use_margins) + return y * render_priv->orig_height_nocrop / + render_priv->track->PlayResY; + else + return y * render_priv->orig_height_nocrop / + render_priv->track->PlayResY + + FFMAX(render_priv->settings.top_margin, 0); } -static void reset_render_context(void); - -/** - * \brief Parse style override tag. - * \param p string to parse - * \param pwr multiplier for some tag effects (comes from \t tags) - */ -static char* parse_tag(char* p, double pwr) { -#define skip_to(x) while ((*p != (x)) && (*p != '}') && (*p != 0)) { ++p;} -#define skip(x) if (*p == (x)) ++p; else { return p; } - - skip_to('\\'); - skip('\\'); - if ((*p == '}') || (*p == 0)) - return p; - - // New tags introduced in vsfilter 2.39 - if (mystrcmp(&p, "xbord")) { - double val; - if (mystrtod(&p, &val)) - mp_msg(MSGT_ASS, MSGL_V, "stub: \\xbord%.2f\n", val); - } else if (mystrcmp(&p, "ybord")) { - double val; - if (mystrtod(&p, &val)) - mp_msg(MSGT_ASS, MSGL_V, "stub: \\ybord%.2f\n", val); - } else if (mystrcmp(&p, "xshad")) { - int val; - if (mystrtoi(&p, &val)) - mp_msg(MSGT_ASS, MSGL_V, "stub: \\xshad%d\n", val); - } else if (mystrcmp(&p, "yshad")) { - int val; - if (mystrtoi(&p, &val)) - mp_msg(MSGT_ASS, MSGL_V, "stub: \\yshad%d\n", val); - } else if (mystrcmp(&p, "fax")) { - int val; - if (mystrtoi(&p, &val)) - mp_msg(MSGT_ASS, MSGL_V, "stub: \\fax%d\n", val); - } else if (mystrcmp(&p, "fay")) { - int val; - if (mystrtoi(&p, &val)) - mp_msg(MSGT_ASS, MSGL_V, "stub: \\fay%d\n", val); - } else if (mystrcmp(&p, "iclip")) { - int x0, y0, x1, y1; - int res = 1; - skip('('); - res &= mystrtoi(&p, &x0); - skip(','); - res &= mystrtoi(&p, &y0); - skip(','); - res &= mystrtoi(&p, &x1); - skip(','); - res &= mystrtoi(&p, &y1); - skip(')'); - mp_msg(MSGT_ASS, MSGL_V, "stub: \\iclip(%d,%d,%d,%d)\n", x0, y0, x1, y1); - } else if (mystrcmp(&p, "blur")) { - double val; - if (mystrtod(&p, &val)) { - val = (val < 0) ? 0 : val; - val = (val > BLUR_MAX_RADIUS) ? BLUR_MAX_RADIUS : val; - render_context.blur = val; - } else - render_context.blur = 0.0; - // ASS standard tags - } else if (mystrcmp(&p, "fsc")) { - char tp = *p++; - double val; - if (tp == 'x') { - if (mystrtod(&p, &val)) { - val /= 100; - render_context.scale_x = render_context.scale_x * ( 1 - pwr) + val * pwr; - } else - render_context.scale_x = render_context.style->ScaleX; - } else if (tp == 'y') { - if (mystrtod(&p, &val)) { - val /= 100; - render_context.scale_y = render_context.scale_y * ( 1 - pwr) + val * pwr; - } else - render_context.scale_y = render_context.style->ScaleY; - } - } else if (mystrcmp(&p, "fsp")) { - double val; - if (mystrtod(&p, &val)) - render_context.hspacing = render_context.hspacing * ( 1 - pwr ) + val * pwr; - else - render_context.hspacing = render_context.style->Spacing; - } else if (mystrcmp(&p, "fs")) { - double val; - if (mystrtod(&p, &val)) - val = render_context.font_size * ( 1 - pwr ) + val * pwr; - else - val = render_context.style->FontSize; - if (render_context.font) - change_font_size(val); - } else if (mystrcmp(&p, "bord")) { - double val; - if (mystrtod(&p, &val)) - val = render_context.border * ( 1 - pwr ) + val * pwr; - else - val = -1.; // reset to default - change_border(val); - } else if (mystrcmp(&p, "move")) { - double x1, x2, y1, y2; - long long t1, t2, delta_t, t; - double x, y; - double k; - skip('('); - mystrtod(&p, &x1); - skip(','); - mystrtod(&p, &y1); - skip(','); - mystrtod(&p, &x2); - skip(','); - mystrtod(&p, &y2); - if (*p == ',') { - skip(','); - mystrtoll(&p, &t1); - skip(','); - mystrtoll(&p, &t2); - mp_msg(MSGT_ASS, MSGL_DBG2, "movement6: (%f, %f) -> (%f, %f), (%" PRId64 " .. %" PRId64 ")\n", - x1, y1, x2, y2, (int64_t)t1, (int64_t)t2); - } else { - t1 = 0; - t2 = render_context.event->Duration; - mp_msg(MSGT_ASS, MSGL_DBG2, "movement: (%f, %f) -> (%f, %f)\n", x1, y1, x2, y2); - } - skip(')'); - delta_t = t2 - t1; - t = frame_context.time - render_context.event->Start; - if (t < t1) - k = 0.; - else if (t > t2) - k = 1.; - else k = ((double)(t - t1)) / delta_t; - x = k * (x2 - x1) + x1; - y = k * (y2 - y1) + y1; - if (render_context.evt_type != EVENT_POSITIONED) { - render_context.pos_x = x; - render_context.pos_y = y; - render_context.detect_collisions = 0; - render_context.evt_type = EVENT_POSITIONED; - } - } else if (mystrcmp(&p, "frx")) { - double val; - if (mystrtod(&p, &val)) { - val *= M_PI / 180; - render_context.frx = val * pwr + render_context.frx * (1-pwr); - } else - render_context.frx = 0.; - } else if (mystrcmp(&p, "fry")) { - double val; - if (mystrtod(&p, &val)) { - val *= M_PI / 180; - render_context.fry = val * pwr + render_context.fry * (1-pwr); - } else - render_context.fry = 0.; - } else if (mystrcmp(&p, "frz") || mystrcmp(&p, "fr")) { - double val; - if (mystrtod(&p, &val)) { - val *= M_PI / 180; - render_context.frz = val * pwr + render_context.frz * (1-pwr); - } else - render_context.frz = M_PI * render_context.style->Angle / 180.; - } else if (mystrcmp(&p, "fn")) { - char* start = p; - char* family; - skip_to('\\'); - if (p > start) { - family = malloc(p - start + 1); - strncpy(family, start, p - start); - family[p - start] = '\0'; - } else - family = strdup(render_context.style->FontName); - if (render_context.family) - free(render_context.family); - render_context.family = family; - update_font(); - } else if (mystrcmp(&p, "alpha")) { - uint32_t val; - int i; - if (strtocolor(&p, &val)) { - unsigned char a = val >> 24; - for (i = 0; i < 4; ++i) - change_alpha(&render_context.c[i], a, pwr); - } else { - change_alpha(&render_context.c[0], render_context.style->PrimaryColour, pwr); - change_alpha(&render_context.c[1], render_context.style->SecondaryColour, pwr); - change_alpha(&render_context.c[2], render_context.style->OutlineColour, pwr); - change_alpha(&render_context.c[3], render_context.style->BackColour, pwr); - } - // FIXME: simplify - } else if (mystrcmp(&p, "an")) { - int val; - if (mystrtoi(&p, &val) && val) { - int v = (val - 1) / 3; // 0, 1 or 2 for vertical alignment - mp_msg(MSGT_ASS, MSGL_DBG2, "an %d\n", val); - if (v != 0) v = 3 - v; - val = ((val - 1) % 3) + 1; // horizontal alignment - val += v*4; - mp_msg(MSGT_ASS, MSGL_DBG2, "align %d\n", val); - render_context.alignment = val; - } else - render_context.alignment = render_context.style->Alignment; - } else if (mystrcmp(&p, "a")) { - int val; - if (mystrtoi(&p, &val) && val) - render_context.alignment = val; - else - render_context.alignment = render_context.style->Alignment; - } else if (mystrcmp(&p, "pos")) { - double v1, v2; - skip('('); - mystrtod(&p, &v1); - skip(','); - mystrtod(&p, &v2); - skip(')'); - mp_msg(MSGT_ASS, MSGL_DBG2, "pos(%f, %f)\n", v1, v2); - if (render_context.evt_type != EVENT_POSITIONED) { - render_context.evt_type = EVENT_POSITIONED; - render_context.detect_collisions = 0; - render_context.pos_x = v1; - render_context.pos_y = v2; - } - } else if (mystrcmp(&p, "fad")) { - int a1, a2, a3; - long long t1, t2, t3, t4; - if (*p == 'e') ++p; // either \fad or \fade - skip('('); - mystrtoi(&p, &a1); - skip(','); - mystrtoi(&p, &a2); - if (*p == ')') { - // 2-argument version (\fad, according to specs) - // a1 and a2 are fade-in and fade-out durations - t1 = 0; - t4 = render_context.event->Duration; - t2 = a1; - t3 = t4 - a2; - a1 = 0xFF; - a2 = 0; - a3 = 0xFF; - } else { - // 6-argument version (\fade) - // a1 and a2 (and a3) are opacity values - skip(','); - mystrtoi(&p, &a3); - skip(','); - mystrtoll(&p, &t1); - skip(','); - mystrtoll(&p, &t2); - skip(','); - mystrtoll(&p, &t3); - skip(','); - mystrtoll(&p, &t4); - } - skip(')'); - render_context.fade = interpolate_alpha(frame_context.time - render_context.event->Start, t1, t2, t3, t4, a1, a2, a3); - } else if (mystrcmp(&p, "org")) { - int v1, v2; - skip('('); - mystrtoi(&p, &v1); - skip(','); - mystrtoi(&p, &v2); - skip(')'); - mp_msg(MSGT_ASS, MSGL_DBG2, "org(%d, %d)\n", v1, v2); - // render_context.evt_type = EVENT_POSITIONED; - if (!render_context.have_origin) { - render_context.org_x = v1; - render_context.org_y = v2; - render_context.have_origin = 1; - render_context.detect_collisions = 0; - } - } else if (mystrcmp(&p, "t")) { - double v[3]; - int v1, v2; - double v3; - int cnt; - long long t1, t2, t, delta_t; - double k; - skip('('); - for (cnt = 0; cnt < 3; ++cnt) { - if (*p == '\\') - break; - v[cnt] = strtod(p, &p); - skip(','); - } - if (cnt == 3) { - v1 = v[0]; v2 = v[1]; v3 = v[2]; - } else if (cnt == 2) { - v1 = v[0]; v2 = v[1]; v3 = 1.; - } else if (cnt == 1) { - v1 = 0; v2 = render_context.event->Duration; v3 = v[0]; - } else { // cnt == 0 - v1 = 0; v2 = render_context.event->Duration; v3 = 1.; - } - render_context.detect_collisions = 0; - t1 = v1; - t2 = v2; - delta_t = v2 - v1; - if (v3 < 0.) - v3 = 0.; - t = frame_context.time - render_context.event->Start; // FIXME: move to render_context - if (t <= t1) - k = 0.; - else if (t >= t2) - k = 1.; - else { - assert(delta_t != 0.); - k = pow(((double)(t - t1)) / delta_t, v3); - } - while (*p == '\\') - p = parse_tag(p, k); // maybe k*pwr ? no, specs forbid nested \t's - skip_to(')'); // in case there is some unknown tag or a comment - skip(')'); - } else if (mystrcmp(&p, "clip")) { - int x0, y0, x1, y1; - int res = 1; - skip('('); - res &= mystrtoi(&p, &x0); - skip(','); - res &= mystrtoi(&p, &y0); - skip(','); - res &= mystrtoi(&p, &x1); - skip(','); - res &= mystrtoi(&p, &y1); - skip(')'); - if (res) { - render_context.clip_x0 = render_context.clip_x0 * (1-pwr) + x0 * pwr; - render_context.clip_x1 = render_context.clip_x1 * (1-pwr) + x1 * pwr; - render_context.clip_y0 = render_context.clip_y0 * (1-pwr) + y0 * pwr; - render_context.clip_y1 = render_context.clip_y1 * (1-pwr) + y1 * pwr; - } else { - render_context.clip_x0 = 0; - render_context.clip_y0 = 0; - render_context.clip_x1 = frame_context.track->PlayResX; - render_context.clip_y1 = frame_context.track->PlayResY; - } - } else if (mystrcmp(&p, "c")) { - uint32_t val; - if (!strtocolor(&p, &val)) - val = render_context.style->PrimaryColour; - mp_msg(MSGT_ASS, MSGL_DBG2, "color: %X\n", val); - change_color(&render_context.c[0], val, pwr); - } else if ((*p >= '1') && (*p <= '4') && (++p) && (mystrcmp(&p, "c") || mystrcmp(&p, "a"))) { - char n = *(p-2); - int cidx = n - '1'; - char cmd = *(p-1); - uint32_t val; - assert((n >= '1') && (n <= '4')); - if (!strtocolor(&p, &val)) - switch(n) { - case '1': val = render_context.style->PrimaryColour; break; - case '2': val = render_context.style->SecondaryColour; break; - case '3': val = render_context.style->OutlineColour; break; - case '4': val = render_context.style->BackColour; break; - default : val = 0; break; // impossible due to assert; avoid compilation warning - } - switch (cmd) { - case 'c': change_color(render_context.c + cidx, val, pwr); break; - case 'a': change_alpha(render_context.c + cidx, val >> 24, pwr); break; - default: mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_BadCommand, n, cmd); break; - } - mp_msg(MSGT_ASS, MSGL_DBG2, "single c/a at %f: %c%c = %X \n", pwr, n, cmd, render_context.c[cidx]); - } else if (mystrcmp(&p, "r")) { - reset_render_context(); - } else if (mystrcmp(&p, "be")) { - int val; - if (mystrtoi(&p, &val)) { - // Clamp to a safe upper limit, since high values need excessive CPU - val = (val < 0) ? 0 : val; - val = (val > MAX_BE) ? MAX_BE : val; - render_context.be = val; - } else - render_context.be = 0; - } else if (mystrcmp(&p, "b")) { - int b; - if (mystrtoi(&p, &b)) { - if (pwr >= .5) - render_context.bold = b; - } else - render_context.bold = render_context.style->Bold; - update_font(); - } else if (mystrcmp(&p, "i")) { - int i; - if (mystrtoi(&p, &i)) { - if (pwr >= .5) - render_context.italic = i; - } else - render_context.italic = render_context.style->Italic; - update_font(); - } else if (mystrcmp(&p, "kf") || mystrcmp(&p, "K")) { - int val = 0; - mystrtoi(&p, &val); - render_context.effect_type = EF_KARAOKE_KF; - if (render_context.effect_timing) - render_context.effect_skip_timing += render_context.effect_timing; - render_context.effect_timing = val * 10; - } else if (mystrcmp(&p, "ko")) { - int val = 0; - mystrtoi(&p, &val); - render_context.effect_type = EF_KARAOKE_KO; - if (render_context.effect_timing) - render_context.effect_skip_timing += render_context.effect_timing; - render_context.effect_timing = val * 10; - } else if (mystrcmp(&p, "k")) { - int val = 0; - mystrtoi(&p, &val); - render_context.effect_type = EF_KARAOKE; - if (render_context.effect_timing) - render_context.effect_skip_timing += render_context.effect_timing; - render_context.effect_timing = val * 10; - } else if (mystrcmp(&p, "shad")) { - int val; - if (mystrtoi(&p, &val)) - render_context.shadow = val; - else - render_context.shadow = render_context.style->Shadow; - } else if (mystrcmp(&p, "pbo")) { - int val = 0; - mystrtoi(&p, &val); // ignored - } else if (mystrcmp(&p, "p")) { - int val; - if (!mystrtoi(&p, &val)) - val = 0; - render_context.drawing_mode = !!val; - } - - return p; - -#undef skip -#undef skip_to +// the same for subtitles +static double y2scr_sub(ASS_Renderer *render_priv, double y) +{ + if (render_priv->settings.use_margins) + return y * render_priv->orig_height_nocrop / + render_priv->track->PlayResY + + FFMAX(render_priv->settings.top_margin, + 0) + FFMAX(render_priv->settings.bottom_margin, 0); + else + return y * render_priv->orig_height_nocrop / + render_priv->track->PlayResY + + FFMAX(render_priv->settings.top_margin, 0); } -/** - * \brief Get next ucs4 char from string, parsing and executing style overrides - * \param str string pointer - * \return ucs4 code of the next char - * On return str points to the unparsed part of the string - */ -static unsigned get_next_char(char** str) +static void compute_string_bbox(TextInfo *info, DBBox *bbox) { - char* p = *str; - unsigned chr; - if (*p == '{') { // '\0' goes here - p++; - while (1) { - p = parse_tag(p, 1.); - if (*p == '}') { // end of tag - p++; - if (*p == '{') { - p++; - continue; - } else - break; - } else if (*p != '\\') - mp_msg(MSGT_ASS, MSGL_V, "Unable to parse: \"%s\" \n", p); - if (*p == 0) - break; - } - } - if (*p == '\t') { - ++p; - *str = p; - return ' '; - } - if (*p == '\\') { - if ((*(p+1) == 'N') || ((*(p+1) == 'n') && (frame_context.track->WrapStyle == 2))) { - p += 2; - *str = p; - return '\n'; - } else if ((*(p+1) == 'n') || (*(p+1) == 'h')) { - p += 2; - *str = p; - return ' '; - } - } - chr = utf8_get_char((const char **)&p); - *str = p; - return chr; -} - -static void apply_transition_effects(ass_event_t* event) -{ - int v[4]; - int cnt; - char* p = event->Effect; - - if (!p || !*p) return; + int i; - cnt = 0; - while (cnt < 4 && (p = strchr(p, ';'))) { - v[cnt++] = atoi(++p); - } - - if (strncmp(event->Effect, "Banner;", 7) == 0) { - int delay; - if (cnt < 1) { - mp_msg(MSGT_ASS, MSGL_V, "Error parsing effect: %s \n", event->Effect); - return; - } - if (cnt >= 2 && v[1] == 0) // right-to-left - render_context.scroll_direction = SCROLL_RL; - else // left-to-right - render_context.scroll_direction = SCROLL_LR; - - delay = v[0]; - if (delay == 0) delay = 1; // ? - render_context.scroll_shift = (frame_context.time - render_context.event->Start) / delay; - render_context.evt_type = EVENT_HSCROLL; - return; - } + if (info->length > 0) { + bbox->xMin = 32000; + bbox->xMax = -32000; + bbox->yMin = -1 * info->lines[0].asc + d6_to_double(info->glyphs[0].pos.y); + bbox->yMax = info->height - info->lines[0].asc + + d6_to_double(info->glyphs[0].pos.y); - if (strncmp(event->Effect, "Scroll up;", 10) == 0) { - render_context.scroll_direction = SCROLL_BT; - } else if (strncmp(event->Effect, "Scroll down;", 12) == 0) { - render_context.scroll_direction = SCROLL_TB; - } else { - mp_msg(MSGT_ASS, MSGL_V, "Unknown transition effect: %s \n", event->Effect); - return; - } - // parse scroll up/down parameters - { - int delay; - int y0, y1; - if (cnt < 3) { - mp_msg(MSGT_ASS, MSGL_V, "Error parsing effect: %s \n", event->Effect); - return; - } - delay = v[2]; - if (delay == 0) delay = 1; // ? - render_context.scroll_shift = (frame_context.time - render_context.event->Start) / delay; - if (v[0] < v[1]) { - y0 = v[0]; y1 = v[1]; - } else { - y0 = v[1]; y1 = v[0]; - } - if (y1 == 0) - y1 = frame_context.track->PlayResY; // y0=y1=0 means fullscreen scrolling - render_context.clip_y0 = y0; - render_context.clip_y1 = y1; - render_context.evt_type = EVENT_VSCROLL; - render_context.detect_collisions = 0; - } - + for (i = 0; i < info->length; ++i) { + if (info->glyphs[i].skip) continue; + double s = d6_to_double(info->glyphs[i].pos.x); + double e = s + d6_to_double(info->glyphs[i].advance.x); + bbox->xMin = FFMIN(bbox->xMin, s); + bbox->xMax = FFMAX(bbox->xMax, e); + } + } else + bbox->xMin = bbox->xMax = bbox->yMin = bbox->yMax = 0.; } /** * \brief partially reset render_context to style values * Works like {\r}: resets some style overrides */ -static void reset_render_context(void) +void reset_render_context(ASS_Renderer *render_priv) { - render_context.c[0] = render_context.style->PrimaryColour; - render_context.c[1] = render_context.style->SecondaryColour; - render_context.c[2] = render_context.style->OutlineColour; - render_context.c[3] = render_context.style->BackColour; - render_context.font_size = render_context.style->FontSize; + render_priv->state.c[0] = render_priv->state.style->PrimaryColour; + render_priv->state.c[1] = render_priv->state.style->SecondaryColour; + render_priv->state.c[2] = render_priv->state.style->OutlineColour; + render_priv->state.c[3] = render_priv->state.style->BackColour; + render_priv->state.flags = + (render_priv->state.style->Underline ? DECO_UNDERLINE : 0) | + (render_priv->state.style->StrikeOut ? DECO_STRIKETHROUGH : 0); + render_priv->state.font_size = render_priv->state.style->FontSize; + + free(render_priv->state.family); + render_priv->state.family = NULL; + render_priv->state.family = strdup(render_priv->state.style->FontName); + render_priv->state.treat_family_as_pattern = + render_priv->state.style->treat_fontname_as_pattern; + render_priv->state.bold = render_priv->state.style->Bold; + render_priv->state.italic = render_priv->state.style->Italic; + update_font(render_priv); + + change_border(render_priv, -1., -1.); + render_priv->state.scale_x = render_priv->state.style->ScaleX; + render_priv->state.scale_y = render_priv->state.style->ScaleY; + render_priv->state.hspacing = render_priv->state.style->Spacing; + render_priv->state.be = 0; + render_priv->state.blur = 0.0; + render_priv->state.shadow_x = render_priv->state.style->Shadow; + render_priv->state.shadow_y = render_priv->state.style->Shadow; + render_priv->state.frx = render_priv->state.fry = 0.; + render_priv->state.frz = M_PI * render_priv->state.style->Angle / 180.; + render_priv->state.fax = render_priv->state.fay = 0.; + render_priv->state.wrap_style = render_priv->track->WrapStyle; + + // FIXME: does not reset unsupported attributes. +} + +/** + * \brief Start new event. Reset render_priv->state. + */ +static void +init_render_context(ASS_Renderer *render_priv, ASS_Event *event) +{ + render_priv->state.event = event; + render_priv->state.style = render_priv->track->styles + event->Style; + + reset_render_context(render_priv); - if (render_context.family) - free(render_context.family); - render_context.family = strdup(render_context.style->FontName); - render_context.treat_family_as_pattern = render_context.style->treat_fontname_as_pattern; - render_context.bold = render_context.style->Bold; - render_context.italic = render_context.style->Italic; - update_font(); + render_priv->state.evt_type = EVENT_NORMAL; + render_priv->state.alignment = render_priv->state.style->Alignment; + render_priv->state.pos_x = 0; + render_priv->state.pos_y = 0; + render_priv->state.org_x = 0; + render_priv->state.org_y = 0; + render_priv->state.have_origin = 0; + render_priv->state.clip_x0 = 0; + render_priv->state.clip_y0 = 0; + render_priv->state.clip_x1 = render_priv->track->PlayResX; + render_priv->state.clip_y1 = render_priv->track->PlayResY; + render_priv->state.clip_mode = 0; + render_priv->state.detect_collisions = 1; + render_priv->state.fade = 0; + render_priv->state.drawing_mode = 0; + render_priv->state.effect_type = EF_NONE; + render_priv->state.effect_timing = 0; + render_priv->state.effect_skip_timing = 0; + render_priv->state.drawing = + ass_drawing_new(render_priv->fontconfig_priv, + render_priv->state.font, + render_priv->settings.hinting, + render_priv->ftlibrary); - change_border(-1.); - render_context.scale_x = render_context.style->ScaleX; - render_context.scale_y = render_context.style->ScaleY; - render_context.hspacing = render_context.style->Spacing; - render_context.be = 0; - render_context.blur = 0.0; - render_context.shadow = render_context.style->Shadow; - render_context.frx = render_context.fry = 0.; - render_context.frz = M_PI * render_context.style->Angle / 180.; + apply_transition_effects(render_priv, event); +} + +static void free_render_context(ASS_Renderer *render_priv) +{ + free(render_priv->state.family); + ass_drawing_free(render_priv->state.drawing); + + render_priv->state.family = NULL; + render_priv->state.drawing = NULL; +} - // FIXME: does not reset unsupported attributes. +// Calculate the cbox of a series of points +static void +get_contour_cbox(FT_BBox *box, FT_Vector *points, int start, int end) +{ + box->xMin = box->yMin = INT_MAX; + box->xMax = box->yMax = INT_MIN; + int i; + + for (i = start; i < end; i++) { + box->xMin = (points[i].x < box->xMin) ? points[i].x : box->xMin; + box->xMax = (points[i].x > box->xMax) ? points[i].x : box->xMax; + box->yMin = (points[i].y < box->yMin) ? points[i].y : box->yMin; + box->yMax = (points[i].y > box->yMax) ? points[i].y : box->yMax; + } } /** - * \brief Start new event. Reset render_context. - */ -static void init_render_context(ass_event_t* event) + * \brief Fix-up stroker result for huge borders by removing the contours from + * the outline that are harmful. +*/ +static void fix_freetype_stroker(FT_OutlineGlyph glyph, int border_x, + int border_y) { - render_context.event = event; - render_context.style = frame_context.track->styles + event->Style; + int nc = glyph->outline.n_contours; + int begin, stop; + char modified = 0; + char *valid_cont; + int start = 0; + int end = -1; + FT_BBox *boxes = calloc(nc, sizeof(FT_BBox)); + int i, j; - reset_render_context(); + // Create a list of cboxes of the contours + for (i = 0; i < nc; i++) { + start = end + 1; + end = glyph->outline.contours[i]; + get_contour_cbox(&boxes[i], glyph->outline.points, start, end); + } - render_context.evt_type = EVENT_NORMAL; - render_context.alignment = render_context.style->Alignment; - render_context.pos_x = 0; - render_context.pos_y = 0; - render_context.org_x = 0; - render_context.org_y = 0; - render_context.have_origin = 0; - render_context.clip_x0 = 0; - render_context.clip_y0 = 0; - render_context.clip_x1 = frame_context.track->PlayResX; - render_context.clip_y1 = frame_context.track->PlayResY; - render_context.detect_collisions = 1; - render_context.fade = 0; - render_context.drawing_mode = 0; - render_context.effect_type = EF_NONE; - render_context.effect_timing = 0; - render_context.effect_skip_timing = 0; + // if a) contour's cbox is contained in another contours cbox + // b) contour's height or width is smaller than the border*2 + // the contour can be safely removed. + valid_cont = calloc(1, nc); + for (i = 0; i < nc; i++) { + valid_cont[i] = 1; + for (j = 0; j < nc; j++) { + if (i == j) + continue; + if (boxes[i].xMin >= boxes[j].xMin && + boxes[i].xMax <= boxes[j].xMax && + boxes[i].yMin >= boxes[j].yMin && + boxes[i].yMax <= boxes[j].yMax) { + int width = boxes[i].xMax - boxes[i].xMin; + int height = boxes[i].yMax - boxes[i].yMin; + if (width < border_x * 2 || height < border_y * 2) { + valid_cont[i] = 0; + modified = 1; + break; + } + } + } + } - apply_transition_effects(event); + // Zero-out contours that can be removed; much simpler than copying + if (modified) { + for (i = 0; i < nc; i++) { + if (valid_cont[i]) + continue; + begin = (i == 0) ? 0 : glyph->outline.contours[i - 1] + 1; + stop = glyph->outline.contours[i]; + for (j = begin; j <= stop; j++) { + glyph->outline.points[j].x = 0; + glyph->outline.points[j].y = 0; + glyph->outline.tags[j] = 0; + } + } + } + + free(boxes); + free(valid_cont); } -static void free_render_context(void) +/* + * Replace the outline of a glyph by a contour which makes up a simple + * opaque rectangle. + */ +static void draw_opaque_box(ASS_Renderer *render_priv, uint32_t ch, + FT_Glyph glyph, int sx, int sy) { + int asc = 0, desc = 0; + int i; + int adv = d16_to_d6(glyph->advance.x); + double scale_y = render_priv->state.scale_y; + double scale_x = render_priv->state.scale_x + * render_priv->font_scale_x; + FT_OutlineGlyph og = (FT_OutlineGlyph) glyph; + FT_Outline *ol; + + // to avoid gaps + sx = FFMAX(64, sx); + sy = FFMAX(64, sy); + + if (ch == -1) { + asc = render_priv->state.drawing->asc; + desc = render_priv->state.drawing->desc; + } else { + ass_font_get_asc_desc(render_priv->state.font, ch, &asc, &desc); + asc *= scale_y; + desc *= scale_y; + } + + // Emulate the WTFish behavior of VSFilter, i.e. double-scale + // the sizes of the opaque box. + adv += double_to_d6(render_priv->state.hspacing * render_priv->font_scale + * scale_x); + adv *= scale_x; + sx *= scale_x; + sy *= scale_y; + desc *= scale_y; + desc += asc * (scale_y - 1.0); + + FT_Vector points[4] = { + { .x = -sx, .y = asc + sy }, + { .x = adv + sx, .y = asc + sy }, + { .x = adv + sx, .y = -desc - sy }, + { .x = -sx, .y = -desc - sy }, + }; + + FT_Outline_Done(render_priv->ftlibrary, &og->outline); + FT_Outline_New(render_priv->ftlibrary, 4, 1, &og->outline); + + ol = &og->outline; + ol->n_points = ol->n_contours = 0; + for (i = 0; i < 4; i++) { + ol->points[ol->n_points] = points[i]; + ol->tags[ol->n_points++] = 1; + } + ol->contours[ol->n_contours++] = ol->n_points - 1; +} + +/* + * Stroke an outline glyph in x/y direction. Applies various fixups to get + * around limitations of the FreeType stroker. + */ +static void stroke_outline_glyph(ASS_Renderer *render_priv, + FT_OutlineGlyph *glyph, int sx, int sy) +{ + if (sx <= 0 && sy <= 0) + return; + + fix_freetype_stroker(*glyph, sx, sy); + + // Borders are equal; use the regular stroker + if (sx == sy && render_priv->state.stroker) { + int error; + error = FT_Glyph_StrokeBorder((FT_Glyph *) glyph, + render_priv->state.stroker, 0, 1); + if (error) + ass_msg(render_priv->library, MSGL_WARN, + "FT_Glyph_Stroke error: %d", error); + + // "Stroke" with the outline emboldener in two passes. + // The outlines look uglier, but the emboldening never adds any points + } else { + int i; + FT_Outline *ol = &(*glyph)->outline; + FT_Outline nol; + FT_Outline_New(render_priv->ftlibrary, ol->n_points, + ol->n_contours, &nol); + FT_Outline_Copy(ol, &nol); + + FT_Outline_Embolden(ol, sx * 2); + FT_Outline_Translate(ol, -sx, -sx); + FT_Outline_Embolden(&nol, sy * 2); + FT_Outline_Translate(&nol, -sy, -sy); + + for (i = 0; i < ol->n_points; i++) + ol->points[i].y = nol.points[i].y; + + FT_Outline_Done(render_priv->ftlibrary, &nol); + } } /** * \brief Get normal and outline (border) glyphs * \param symbol ucs4 char * \param info out: struct filled with extracted data - * \param advance subpixel shift vector used for cache lookup * Tries to get both glyphs from cache. * If they can't be found, gets a glyph from font face, generates outline with FT_Stroker, * and add them to cache. * The glyphs are returned in info->glyph and info->outline_glyph */ -static void get_outline_glyph(int symbol, glyph_info_t* info, FT_Vector* advance) +static void +get_outline_glyph(ASS_Renderer *render_priv, int symbol, GlyphInfo *info, + ASS_Drawing *drawing) { - int error; - glyph_hash_val_t* val; - glyph_hash_key_t key; - memset(&key, 0, sizeof(key)); - key.font = render_context.font; - key.size = render_context.font_size; - key.ch = symbol; - key.scale_x = (render_context.scale_x * 0xFFFF); - key.scale_y = (render_context.scale_y * 0xFFFF); - key.advance = *advance; - key.bold = render_context.bold; - key.italic = render_context.italic; - key.outline = render_context.border * 0xFFFF; + GlyphHashValue *val; + GlyphHashKey key; + memset(&key, 0, sizeof(key)); - memset(info, 0, sizeof(glyph_info_t)); + if (drawing->hash) { + key.scale_x = double_to_d16(render_priv->state.scale_x); + key.scale_y = double_to_d16(render_priv->state.scale_y); + key.outline.x = render_priv->state.border_x * 0xFFFF; + key.outline.y = render_priv->state.border_y * 0xFFFF; + key.border_style = render_priv->state.style->BorderStyle; + key.drawing_hash = drawing->hash; + } else { + key.font = render_priv->state.font; + key.size = render_priv->state.font_size; + key.ch = symbol; + key.bold = render_priv->state.bold; + key.italic = render_priv->state.italic; + key.scale_x = double_to_d16(render_priv->state.scale_x); + key.scale_y = double_to_d16(render_priv->state.scale_y); + key.outline.x = render_priv->state.border_x * 0xFFFF; + key.outline.y = render_priv->state.border_y * 0xFFFF; + key.flags = render_priv->state.flags; + key.border_style = render_priv->state.style->BorderStyle; + } + memset(info, 0, sizeof(GlyphInfo)); - val = cache_find_glyph(&key); - if (val) { - FT_Glyph_Copy(val->glyph, &info->glyph); - if (val->outline_glyph) - FT_Glyph_Copy(val->outline_glyph, &info->outline_glyph); - info->bbox = val->bbox_scaled; - info->advance.x = val->advance.x; - info->advance.y = val->advance.y; - } else { - glyph_hash_val_t v; - info->glyph = ass_font_get_glyph(frame_context.ass_priv->fontconfig_priv, render_context.font, symbol, global_settings->hinting); - if (!info->glyph) - return; - info->advance.x = d16_to_d6(info->glyph->advance.x); - info->advance.y = d16_to_d6(info->glyph->advance.y); - FT_Glyph_Get_CBox( info->glyph, FT_GLYPH_BBOX_PIXELS, &info->bbox); + val = cache_find_glyph(render_priv->cache.glyph_cache, &key); + if (val) { + FT_Glyph_Copy(val->glyph, &info->glyph); + if (val->outline_glyph) + FT_Glyph_Copy(val->outline_glyph, &info->outline_glyph); + info->bbox = val->bbox_scaled; + info->advance.x = val->advance.x; + info->advance.y = val->advance.y; + if (drawing->hash) { + drawing->asc = val->asc; + drawing->desc = val->desc; + } + } else { + GlyphHashValue v; + if (drawing->hash) { + if(!ass_drawing_parse(drawing, 0)) + return; + FT_Glyph_Copy((FT_Glyph) drawing->glyph, &info->glyph); + } else { + info->glyph = + ass_font_get_glyph(render_priv->fontconfig_priv, + render_priv->state.font, symbol, + render_priv->settings.hinting, + render_priv->state.flags); + } + if (!info->glyph) + return; + info->advance.x = d16_to_d6(info->glyph->advance.x); + info->advance.y = d16_to_d6(info->glyph->advance.y); + FT_Glyph_Get_CBox(info->glyph, FT_GLYPH_BBOX_SUBPIXELS, &info->bbox); - if (render_context.stroker) { - info->outline_glyph = info->glyph; - error = FT_Glyph_StrokeBorder( &(info->outline_glyph), render_context.stroker, 0 , 0 ); // don't destroy original - if (error) { - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FT_Glyph_Stroke_Error, error); - } - } + if (render_priv->state.style->BorderStyle == 3 && + (render_priv->state.border_x > 0|| + render_priv->state.border_y > 0)) { + FT_Glyph_Copy(info->glyph, &info->outline_glyph); + draw_opaque_box(render_priv, symbol, info->outline_glyph, + double_to_d6(render_priv->state.border_x * + render_priv->border_scale), + double_to_d6(render_priv->state.border_y * + render_priv->border_scale)); + } else if (render_priv->state.border_x > 0 || + render_priv->state.border_y > 0) { - memset(&v, 0, sizeof(v)); - FT_Glyph_Copy(info->glyph, &v.glyph); - if (info->outline_glyph) - FT_Glyph_Copy(info->outline_glyph, &v.outline_glyph); - v.advance = info->advance; - v.bbox_scaled = info->bbox; - cache_add_glyph(&key, &v); - } + FT_Glyph_Copy(info->glyph, &info->outline_glyph); + stroke_outline_glyph(render_priv, + (FT_OutlineGlyph *) &info->outline_glyph, + double_to_d6(render_priv->state.border_x * + render_priv->border_scale), + double_to_d6(render_priv->state.border_y * + render_priv->border_scale)); + } + + memset(&v, 0, sizeof(v)); + FT_Glyph_Copy(info->glyph, &v.glyph); + if (info->outline_glyph) + FT_Glyph_Copy(info->outline_glyph, &v.outline_glyph); + v.advance = info->advance; + v.bbox_scaled = info->bbox; + if (drawing->hash) { + v.asc = drawing->asc; + v.desc = drawing->desc; + } + cache_add_glyph(render_priv->cache.glyph_cache, &key, &v); + } } -static void transform_3d(FT_Vector shift, FT_Glyph* glyph, FT_Glyph* glyph2, double frx, double fry, double frz); +static void transform_3d(FT_Vector shift, FT_Glyph *glyph, + FT_Glyph *glyph2, double frx, double fry, + double frz, double fax, double fay, double scale, + int yshift); /** * \brief Get bitmaps for a glyph @@ -1504,50 +1197,74 @@ * After that, bitmaps are added to the cache. * They are returned in info->bm (glyph), info->bm_o (outline) and info->bm_s (shadow). */ -static void get_bitmap_glyph(glyph_info_t* info) +static void +get_bitmap_glyph(ASS_Renderer *render_priv, GlyphInfo *info) { - bitmap_hash_val_t* val; - bitmap_hash_key_t* key = &info->hash_key; + BitmapHashValue *val; + BitmapHashKey *key = &info->hash_key; + + val = cache_find_bitmap(render_priv->cache.bitmap_cache, key); - val = cache_find_bitmap(key); -/* val = 0; */ + if (val) { + info->bm = val->bm; + info->bm_o = val->bm_o; + info->bm_s = val->bm_s; + } else { + FT_Vector shift; + BitmapHashValue hash_val; + int error; + double fax_scaled, fay_scaled; + info->bm = info->bm_o = info->bm_s = 0; + if (info->glyph && info->symbol != '\n' && info->symbol != 0 + && !info->skip) { + // calculating rotation shift vector (from rotation origin to the glyph basepoint) + shift.x = info->hash_key.shift_x; + shift.y = info->hash_key.shift_y; + fax_scaled = info->fax * render_priv->font_scale_x * + render_priv->state.scale_x; + fay_scaled = info->fay * render_priv->state.scale_y; + // apply rotation + transform_3d(shift, &info->glyph, &info->outline_glyph, + info->frx, info->fry, info->frz, fax_scaled, + fay_scaled, render_priv->font_scale, info->asc); - if (val) { - info->bm = val->bm; - info->bm_o = val->bm_o; - info->bm_s = val->bm_s; - } else { - FT_Vector shift; - bitmap_hash_val_t hash_val; - int error; - info->bm = info->bm_o = info->bm_s = 0; - if (info->glyph && info->symbol != '\n' && info->symbol != 0) { - // calculating rotation shift vector (from rotation origin to the glyph basepoint) - shift.x = int_to_d6(info->hash_key.shift_x); - shift.y = int_to_d6(info->hash_key.shift_y); - // apply rotation - transform_3d(shift, &info->glyph, &info->outline_glyph, info->frx, info->fry, info->frz); + // subpixel shift + if (info->glyph) + FT_Outline_Translate( + &((FT_OutlineGlyph) info->glyph)->outline, + info->hash_key.advance.x, + -info->hash_key.advance.y); + if (info->outline_glyph) + FT_Outline_Translate( + &((FT_OutlineGlyph) info->outline_glyph)->outline, + info->hash_key.advance.x, + -info->hash_key.advance.y); - // render glyph - error = glyph_to_bitmap(ass_renderer->synth_priv, - info->glyph, info->outline_glyph, - &info->bm, &info->bm_o, - &info->bm_s, info->be, info->blur * frame_context.border_scale); - if (error) - info->symbol = 0; + // render glyph + error = glyph_to_bitmap(render_priv->library, + render_priv->synth_priv, + info->glyph, info->outline_glyph, + &info->bm, &info->bm_o, + &info->bm_s, info->be, + info->blur * render_priv->border_scale, + info->hash_key.shadow_offset, + info->hash_key.border_style); + if (error) + info->symbol = 0; - // add bitmaps to cache - hash_val.bm_o = info->bm_o; - hash_val.bm = info->bm; - hash_val.bm_s = info->bm_s; - cache_add_bitmap(&(info->hash_key), &hash_val); - } - } - // deallocate glyphs - if (info->glyph) - FT_Done_Glyph(info->glyph); - if (info->outline_glyph) - FT_Done_Glyph(info->outline_glyph); + // add bitmaps to cache + hash_val.bm_o = info->bm_o; + hash_val.bm = info->bm; + hash_val.bm_s = info->bm_s; + cache_add_bitmap(render_priv->cache.bitmap_cache, + &(info->hash_key), &hash_val); + } + } + // deallocate glyphs + if (info->glyph) + FT_Done_Glyph(info->glyph); + if (info->outline_glyph) + FT_Done_Glyph(info->outline_glyph); } /** @@ -1558,29 +1275,98 @@ * lines[].asc * lines[].desc */ -static void measure_text(void) +static void measure_text(ASS_Renderer *render_priv) +{ + TextInfo *text_info = &render_priv->text_info; + int cur_line = 0; + double max_asc = 0., max_desc = 0.; + GlyphInfo *last = NULL; + int i; + int empty_line = 1; + text_info->height = 0.; + for (i = 0; i < text_info->length + 1; ++i) { + if ((i == text_info->length) || text_info->glyphs[i].linebreak) { + if (empty_line && cur_line > 0 && last && i < text_info->length) { + max_asc = d6_to_double(last->asc) / 2.0; + max_desc = d6_to_double(last->desc) / 2.0; + } + text_info->lines[cur_line].asc = max_asc; + text_info->lines[cur_line].desc = max_desc; + text_info->height += max_asc + max_desc; + cur_line++; + max_asc = max_desc = 0.; + empty_line = 1; + } else + empty_line = 0; + if (i < text_info->length) { + GlyphInfo *cur = text_info->glyphs + i; + if (d6_to_double(cur->asc) > max_asc) + max_asc = d6_to_double(cur->asc); + if (d6_to_double(cur->desc) > max_desc) + max_desc = d6_to_double(cur->desc); + if (cur->symbol != '\n' && cur->symbol != 0) + last = cur; + } + } + text_info->height += + (text_info->n_lines - + 1) * render_priv->settings.line_spacing; +} + +/** + * Mark extra whitespace for later removal. + */ +#define IS_WHITESPACE(x) ((x->symbol == ' ' || x->symbol == '\n') \ + && !x->linebreak) +static void trim_whitespace(ASS_Renderer *render_priv) { - int cur_line = 0, max_asc = 0, max_desc = 0; - int i; - text_info.height = 0; - for (i = 0; i < text_info.length + 1; ++i) { - if ((i == text_info.length) || text_info.glyphs[i].linebreak) { - text_info.lines[cur_line].asc = max_asc; - text_info.lines[cur_line].desc = max_desc; - text_info.height += max_asc + max_desc; - cur_line ++; - max_asc = max_desc = 0; - } - if (i < text_info.length) { - glyph_info_t* cur = text_info.glyphs + i; - if (cur->asc > max_asc) - max_asc = cur->asc; - if (cur->desc > max_desc) - max_desc = cur->desc; - } - } - text_info.height += (text_info.n_lines - 1) * double_to_d6(global_settings->line_spacing); + int i, j; + GlyphInfo *cur; + TextInfo *ti = &render_priv->text_info; + + // Mark trailing spaces + i = ti->length - 1; + cur = ti->glyphs + i; + while (i && IS_WHITESPACE(cur)) { + cur->skip++; + cur = ti->glyphs + --i; + } + + // Mark leading whitespace + i = 0; + cur = ti->glyphs; + while (i < ti->length && IS_WHITESPACE(cur)) { + cur->skip++; + cur = ti->glyphs + ++i; + } + + // Mark all extraneous whitespace inbetween + for (i = 0; i < ti->length; ++i) { + cur = ti->glyphs + i; + if (cur->linebreak) { + // Mark whitespace before + j = i - 1; + cur = ti->glyphs + j; + while (j && IS_WHITESPACE(cur)) { + cur->skip++; + cur = ti->glyphs + --j; + } + // A break itself can contain a whitespace, too + cur = ti->glyphs + i; + if (cur->symbol == ' ') + cur->skip++; + // Mark whitespace after + j = i + 1; + cur = ti->glyphs + j; + while (j < ti->length && IS_WHITESPACE(cur)) { + cur->skip++; + cur = ti->glyphs + ++j; + } + i = j - 1; + } + } } +#undef IS_WHITESPACE /** * \brief rearrange text between lines @@ -1590,132 +1376,167 @@ * 2. Try moving words from the end of a line to the beginning of the next one while it reduces * the difference in lengths between this two lines. * The result may not be optimal, but usually is good enough. + * + * FIXME: implement style 0 and 3 correctly, add support for style 1 */ -static void wrap_lines_smart(int max_text_width) +static void +wrap_lines_smart(ASS_Renderer *render_priv, double max_text_width) { - int i, j; - glyph_info_t *cur, *s1, *e1, *s2, *s3, *w; - int last_space; - int break_type; - int exit; - int pen_shift_x; - int pen_shift_y; - int cur_line; + int i; + GlyphInfo *cur, *s1, *e1, *s2, *s3, *w; + int last_space; + int break_type; + int exit; + double pen_shift_x; + double pen_shift_y; + int cur_line; + TextInfo *text_info = &render_priv->text_info; - last_space = -1; - text_info.n_lines = 1; - break_type = 0; - s1 = text_info.glyphs; // current line start - for (i = 0; i < text_info.length; ++i) { - int break_at, s_offset, len; - cur = text_info.glyphs + i; - break_at = -1; - s_offset = s1->bbox.xMin + s1->pos.x; - len = (cur->bbox.xMax + cur->pos.x) - s_offset; + last_space = -1; + text_info->n_lines = 1; + break_type = 0; + s1 = text_info->glyphs; // current line start + for (i = 0; i < text_info->length; ++i) { + int break_at; + double s_offset, len; + cur = text_info->glyphs + i; + break_at = -1; + s_offset = d6_to_double(s1->bbox.xMin + s1->pos.x); + len = d6_to_double(cur->bbox.xMax + cur->pos.x) - s_offset; - if (cur->symbol == '\n') { - break_type = 2; - break_at = i; - mp_msg(MSGT_ASS, MSGL_DBG2, "forced line break at %d\n", break_at); - } + if (cur->symbol == '\n') { + break_type = 2; + break_at = i; + ass_msg(render_priv->library, MSGL_DBG2, + "forced line break at %d", break_at); + } - if ((len >= max_text_width) && (frame_context.track->WrapStyle != 2)) { - break_type = 1; - break_at = last_space; - if (break_at == -1) - break_at = i - 1; - if (break_at == -1) - break_at = 0; - mp_msg(MSGT_ASS, MSGL_DBG2, "overfill at %d\n", i); - mp_msg(MSGT_ASS, MSGL_DBG2, "line break at %d\n", break_at); - } + if ((len >= max_text_width) + && (render_priv->state.wrap_style != 2)) { + break_type = 1; + break_at = last_space; + if (break_at == -1) + break_at = i - 1; + if (break_at == -1) + break_at = 0; + ass_msg(render_priv->library, MSGL_DBG2, "overfill at %d", i); + ass_msg(render_priv->library, MSGL_DBG2, "line break at %d", + break_at); + } - if (break_at != -1) { - // need to use one more line - // marking break_at+1 as start of a new line - int lead = break_at + 1; // the first symbol of the new line - if (text_info.n_lines >= MAX_LINES) { - // to many lines ! - // no more linebreaks - for (j = lead; j < text_info.length; ++j) - text_info.glyphs[j].linebreak = 0; - break; - } - if (lead < text_info.length) - text_info.glyphs[lead].linebreak = break_type; - last_space = -1; - s1 = text_info.glyphs + lead; - s_offset = s1->bbox.xMin + s1->pos.x; - text_info.n_lines ++; - } + if (break_at != -1) { + // need to use one more line + // marking break_at+1 as start of a new line + int lead = break_at + 1; // the first symbol of the new line + if (text_info->n_lines >= text_info->max_lines) { + // Raise maximum number of lines + text_info->max_lines *= 2; + text_info->lines = realloc(text_info->lines, + sizeof(LineInfo) * + text_info->max_lines); + } + if (lead < text_info->length) + text_info->glyphs[lead].linebreak = break_type; + last_space = -1; + s1 = text_info->glyphs + lead; + s_offset = d6_to_double(s1->bbox.xMin + s1->pos.x); + text_info->n_lines++; + } - if (cur->symbol == ' ') - last_space = i; + if (cur->symbol == ' ') + last_space = i; - // make sure the hard linebreak is not forgotten when - // there was a new soft linebreak just inserted - if (cur->symbol == '\n' && break_type == 1) - i--; - } + // make sure the hard linebreak is not forgotten when + // there was a new soft linebreak just inserted + if (cur->symbol == '\n' && break_type == 1) + i--; + } #define DIFF(x,y) (((x) < (y)) ? (y - x) : (x - y)) - exit = 0; - while (!exit) { - exit = 1; - w = s3 = text_info.glyphs; - s1 = s2 = 0; - for (i = 0; i <= text_info.length; ++i) { - cur = text_info.glyphs + i; - if ((i == text_info.length) || cur->linebreak) { - s1 = s2; - s2 = s3; - s3 = cur; - if (s1 && (s2->linebreak == 1)) { // have at least 2 lines, and linebreak is 'soft' - int l1, l2, l1_new, l2_new; + exit = 0; + while (!exit && render_priv->state.wrap_style != 1) { + exit = 1; + w = s3 = text_info->glyphs; + s1 = s2 = 0; + for (i = 0; i <= text_info->length; ++i) { + cur = text_info->glyphs + i; + if ((i == text_info->length) || cur->linebreak) { + s1 = s2; + s2 = s3; + s3 = cur; + if (s1 && (s2->linebreak == 1)) { // have at least 2 lines, and linebreak is 'soft' + double l1, l2, l1_new, l2_new; - w = s2; - do { --w; } while ((w > s1) && (w->symbol == ' ')); - while ((w > s1) && (w->symbol != ' ')) { --w; } - e1 = w; - while ((e1 > s1) && (e1->symbol == ' ')) { --e1; } - if (w->symbol == ' ') ++w; - - l1 = ((s2-1)->bbox.xMax + (s2-1)->pos.x) - (s1->bbox.xMin + s1->pos.x); - l2 = ((s3-1)->bbox.xMax + (s3-1)->pos.x) - (s2->bbox.xMin + s2->pos.x); - l1_new = (e1->bbox.xMax + e1->pos.x) - (s1->bbox.xMin + s1->pos.x); - l2_new = ((s3-1)->bbox.xMax + (s3-1)->pos.x) - (w->bbox.xMin + w->pos.x); + w = s2; + do { + --w; + } while ((w > s1) && (w->symbol == ' ')); + while ((w > s1) && (w->symbol != ' ')) { + --w; + } + e1 = w; + while ((e1 > s1) && (e1->symbol == ' ')) { + --e1; + } + if (w->symbol == ' ') + ++w; - if (DIFF(l1_new, l2_new) < DIFF(l1, l2)) { - w->linebreak = 1; - s2->linebreak = 0; - exit = 0; - } - } - } - if (i == text_info.length) - break; - } + l1 = d6_to_double(((s2 - 1)->bbox.xMax + (s2 - 1)->pos.x) - + (s1->bbox.xMin + s1->pos.x)); + l2 = d6_to_double(((s3 - 1)->bbox.xMax + (s3 - 1)->pos.x) - + (s2->bbox.xMin + s2->pos.x)); + l1_new = d6_to_double( + (e1->bbox.xMax + e1->pos.x) - + (s1->bbox.xMin + s1->pos.x)); + l2_new = d6_to_double( + ((s3 - 1)->bbox.xMax + (s3 - 1)->pos.x) - + (w->bbox.xMin + w->pos.x)); - } - assert(text_info.n_lines >= 1); + if (DIFF(l1_new, l2_new) < DIFF(l1, l2)) { + w->linebreak = 1; + s2->linebreak = 0; + exit = 0; + } + } + } + if (i == text_info->length) + break; + } + + } + assert(text_info->n_lines >= 1); #undef DIFF - measure_text(); + measure_text(render_priv); + trim_whitespace(render_priv); + + pen_shift_x = 0.; + pen_shift_y = 0.; + cur_line = 1; + + i = 0; + cur = text_info->glyphs + i; + while (i < text_info->length && cur->skip) + cur = text_info->glyphs + ++i; + pen_shift_x = d6_to_double(-cur->pos.x); - pen_shift_x = 0; - pen_shift_y = 0; - cur_line = 1; - for (i = 0; i < text_info.length; ++i) { - cur = text_info.glyphs + i; - if (cur->linebreak) { - int height = text_info.lines[cur_line - 1].desc + text_info.lines[cur_line].asc; - cur_line ++; - pen_shift_x = - cur->pos.x; - pen_shift_y += d6_to_int(height + double_to_d6(global_settings->line_spacing)); - mp_msg(MSGT_ASS, MSGL_DBG2, "shifting from %d to %d by (%d, %d)\n", i, text_info.length - 1, pen_shift_x, pen_shift_y); - } - cur->pos.x += pen_shift_x; - cur->pos.y += pen_shift_y; - } + for (i = 0; i < text_info->length; ++i) { + cur = text_info->glyphs + i; + if (cur->linebreak) { + while (i < text_info->length && cur->skip && cur->symbol != '\n') + cur = text_info->glyphs + ++i; + double height = + text_info->lines[cur_line - 1].desc + + text_info->lines[cur_line].asc; + cur_line++; + pen_shift_x = d6_to_double(-cur->pos.x); + pen_shift_y += height + render_priv->settings.line_spacing; + ass_msg(render_priv->library, MSGL_DBG2, + "shifting from %d to %d by (%f, %f)", i, + text_info->length - 1, pen_shift_x, pen_shift_y); + } + cur->pos.x += double_to_d6(pen_shift_x); + cur->pos.y += double_to_d6(pen_shift_y); + } } /** @@ -1729,60 +1550,63 @@ * 2. sets effect_timing for all glyphs to x coordinate of the border line between the left and right karaoke parts * (left part is filled with PrimaryColour, right one - with SecondaryColour). */ -static void process_karaoke_effects(void) +static void process_karaoke_effects(ASS_Renderer *render_priv) { - glyph_info_t *cur, *cur2; - glyph_info_t *s1, *e1; // start and end of the current word - glyph_info_t *s2; // start of the next word - int i; - int timing; // current timing - int tm_start, tm_end; // timings at start and end of the current word - int tm_current; - double dt; - int x; - int x_start, x_end; + GlyphInfo *cur, *cur2; + GlyphInfo *s1, *e1; // start and end of the current word + GlyphInfo *s2; // start of the next word + int i; + int timing; // current timing + int tm_start, tm_end; // timings at start and end of the current word + int tm_current; + double dt; + int x; + int x_start, x_end; - tm_current = frame_context.time - render_context.event->Start; - timing = 0; - s1 = s2 = 0; - for (i = 0; i <= text_info.length; ++i) { - cur = text_info.glyphs + i; - if ((i == text_info.length) || (cur->effect_type != EF_NONE)) { - s1 = s2; - s2 = cur; - if (s1) { - e1 = s2 - 1; - tm_start = timing + s1->effect_skip_timing; - tm_end = tm_start + s1->effect_timing; - timing = tm_end; - x_start = 1000000; - x_end = -1000000; - for (cur2 = s1; cur2 <= e1; ++cur2) { - x_start = FFMIN(x_start, cur2->bbox.xMin + cur2->pos.x); - x_end = FFMAX(x_end, cur2->bbox.xMax + cur2->pos.x); - } + tm_current = render_priv->time - render_priv->state.event->Start; + timing = 0; + s1 = s2 = 0; + for (i = 0; i <= render_priv->text_info.length; ++i) { + cur = render_priv->text_info.glyphs + i; + if ((i == render_priv->text_info.length) + || (cur->effect_type != EF_NONE)) { + s1 = s2; + s2 = cur; + if (s1) { + e1 = s2 - 1; + tm_start = timing + s1->effect_skip_timing; + tm_end = tm_start + s1->effect_timing; + timing = tm_end; + x_start = 1000000; + x_end = -1000000; + for (cur2 = s1; cur2 <= e1; ++cur2) { + x_start = FFMIN(x_start, d6_to_int(cur2->bbox.xMin + cur2->pos.x)); + x_end = FFMAX(x_end, d6_to_int(cur2->bbox.xMax + cur2->pos.x)); + } - dt = (tm_current - tm_start); - if ((s1->effect_type == EF_KARAOKE) || (s1->effect_type == EF_KARAOKE_KO)) { - if (dt > 0) - x = x_end + 1; - else - x = x_start; - } else if (s1->effect_type == EF_KARAOKE_KF) { - dt /= (tm_end - tm_start); - x = x_start + (x_end - x_start) * dt; - } else { - mp_msg(MSGT_ASS, MSGL_ERR, MSGTR_LIBASS_UnknownEffectType_InternalError); - continue; - } + dt = (tm_current - tm_start); + if ((s1->effect_type == EF_KARAOKE) + || (s1->effect_type == EF_KARAOKE_KO)) { + if (dt > 0) + x = x_end + 1; + else + x = x_start; + } else if (s1->effect_type == EF_KARAOKE_KF) { + dt /= (tm_end - tm_start); + x = x_start + (x_end - x_start) * dt; + } else { + ass_msg(render_priv->library, MSGL_ERR, + "Unknown effect type"); + continue; + } - for (cur2 = s1; cur2 <= e1; ++cur2) { - cur2->effect_type = s1->effect_type; - cur2->effect_timing = x - cur2->pos.x; - } - } - } - } + for (cur2 = s1; cur2 <= e1; ++cur2) { + cur2->effect_type = s1->effect_type; + cur2->effect_timing = x - d6_to_int(cur2->pos.x); + } + } + } + } } /** @@ -1791,34 +1615,34 @@ * \param alignment alignment * \param bx, by out: base point coordinates */ -static void get_base_point(FT_BBox bbox, int alignment, int* bx, int* by) +static void get_base_point(DBBox *bbox, int alignment, double *bx, double *by) { - const int halign = alignment & 3; - const int valign = alignment & 12; - if (bx) - switch(halign) { - case HALIGN_LEFT: - *bx = bbox.xMin; - break; - case HALIGN_CENTER: - *bx = (bbox.xMax + bbox.xMin) / 2; - break; - case HALIGN_RIGHT: - *bx = bbox.xMax; - break; - } - if (by) - switch(valign) { - case VALIGN_TOP: - *by = bbox.yMin; - break; - case VALIGN_CENTER: - *by = (bbox.yMax + bbox.yMin) / 2; - break; - case VALIGN_SUB: - *by = bbox.yMax; - break; - } + const int halign = alignment & 3; + const int valign = alignment & 12; + if (bx) + switch (halign) { + case HALIGN_LEFT: + *bx = bbox->xMin; + break; + case HALIGN_CENTER: + *bx = (bbox->xMax + bbox->xMin) / 2.0; + break; + case HALIGN_RIGHT: + *bx = bbox->xMax; + break; + } + if (by) + switch (valign) { + case VALIGN_TOP: + *by = bbox->yMin; + break; + case VALIGN_CENTER: + *by = (bbox->yMax + bbox->yMin) / 2.0; + break; + case VALIGN_SUB: + *by = bbox->yMax; + break; + } } /** @@ -1826,42 +1650,47 @@ * Applies rotations given by frx, fry and frz and projects the points back * onto the screen plane. */ -static void transform_3d_points(FT_Vector shift, FT_Glyph glyph, double frx, double fry, double frz) { - double sx = sin(frx); - double sy = sin(fry); - double sz = sin(frz); - double cx = cos(frx); - double cy = cos(fry); - double cz = cos(frz); - FT_Outline *outline = &((FT_OutlineGlyph) glyph)->outline; - FT_Vector* p = outline->points; - double x, y, z, xx, yy, zz; - int i; - - for (i=0; i<outline->n_points; i++) { - x = p[i].x + shift.x; - y = p[i].y + shift.y; - z = 0.; +static void +transform_3d_points(FT_Vector shift, FT_Glyph glyph, double frx, double fry, + double frz, double fax, double fay, double scale, + int yshift) +{ + double sx = sin(frx); + double sy = sin(fry); + double sz = sin(frz); + double cx = cos(frx); + double cy = cos(fry); + double cz = cos(frz); + FT_Outline *outline = &((FT_OutlineGlyph) glyph)->outline; + FT_Vector *p = outline->points; + double x, y, z, xx, yy, zz; + int i, dist; - xx = x*cz + y*sz; - yy = -(x*sz - y*cz); - zz = z; + dist = 20000 * scale; + for (i = 0; i < outline->n_points; i++) { + x = (double) p[i].x + shift.x + (fax * (yshift - p[i].y)); + y = (double) p[i].y + shift.y + (-fay * p[i].x); + z = 0.; - x = xx; - y = yy*cx + zz*sx; - z = yy*sx - zz*cx; + xx = x * cz + y * sz; + yy = -(x * sz - y * cz); + zz = z; - xx = x*cy + z*sy; - yy = y; - zz = x*sy - z*cy; + x = xx; + y = yy * cx + zz * sx; + z = yy * sx - zz * cx; - zz = FFMAX(zz, -19000); + xx = x * cy + z * sy; + yy = y; + zz = x * sy - z * cy; - x = (xx * 20000) / (zz + 20000); - y = (yy * 20000) / (zz + 20000); - p[i].x = x - shift.x + 0.5; - p[i].y = y - shift.y + 0.5; - } + zz = FFMAX(zz, 1000 - dist); + + x = (xx * dist) / (zz + dist); + y = (yy * dist) / (zz + dist); + p[i].x = x - shift.x + 0.5; + p[i].y = y - shift.y + 0.5; + } } /** @@ -1874,17 +1703,22 @@ * \param frz z-axis rotation angle * Rotates both glyphs by frx, fry and frz. Shift vector is added before rotation and subtracted after it. */ -static void transform_3d(FT_Vector shift, FT_Glyph* glyph, FT_Glyph* glyph2, double frx, double fry, double frz) +static void +transform_3d(FT_Vector shift, FT_Glyph *glyph, FT_Glyph *glyph2, + double frx, double fry, double frz, double fax, double fay, + double scale, int yshift) { - frx = - frx; - frz = - frz; - if (frx != 0. || fry != 0. || frz != 0.) { - if (glyph && *glyph) - transform_3d_points(shift, *glyph, frx, fry, frz); + frx = -frx; + frz = -frz; + if (frx != 0. || fry != 0. || frz != 0. || fax != 0. || fay != 0.) { + if (glyph && *glyph) + transform_3d_points(shift, *glyph, frx, fry, frz, + fax, fay, scale, yshift); - if (glyph2 && *glyph2) - transform_3d_points(shift, *glyph2, frx, fry, frz); - } + if (glyph2 && *glyph2) + transform_3d_points(shift, *glyph2, frx, fry, frz, + fax, fay, scale, yshift); + } } @@ -1892,622 +1726,836 @@ * \brief Main ass rendering function, glues everything together * \param event event to render * \param event_images struct containing resulting images, will also be initialized - * Process event, appending resulting ass_image_t's to images_root. + * Process event, appending resulting ASS_Image's to images_root. */ -static int ass_render_event(ass_event_t* event, event_images_t* event_images) +static int +ass_render_event(ASS_Renderer *render_priv, ASS_Event *event, + EventImages *event_images) { - char* p; - FT_UInt previous; - FT_UInt num_glyphs; - FT_Vector pen; - unsigned code; - FT_BBox bbox; - int i, j; - FT_Vector shift; - int MarginL, MarginR, MarginV; - int last_break; - int alignment, halign, valign; - int device_x = 0, device_y = 0; + char *p; + FT_UInt previous; + FT_UInt num_glyphs; + FT_Vector pen; + unsigned code; + DBBox bbox; + int i, j; + int MarginL, MarginR, MarginV; + int last_break; + int alignment, halign, valign; + int kern = render_priv->track->Kerning; + double device_x = 0; + double device_y = 0; + TextInfo *text_info = &render_priv->text_info; + ASS_Drawing *drawing; - if (event->Style >= frame_context.track->n_styles) { - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_NoStyleFound); - return 1; - } - if (!event->Text) { - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_EmptyEvent); - return 1; - } + if (event->Style >= render_priv->track->n_styles) { + ass_msg(render_priv->library, MSGL_WARN, "No style found"); + return 1; + } + if (!event->Text) { + ass_msg(render_priv->library, MSGL_WARN, "Empty event"); + return 1; + } - init_render_context(event); + init_render_context(render_priv, event); - text_info.length = 0; - pen.x = 0; - pen.y = 0; - previous = 0; - num_glyphs = 0; - p = event->Text; - // Event parsing. - while (1) { - // get next char, executing style override - // this affects render_context - do { - code = get_next_char(&p); - } while (code && render_context.drawing_mode); // skip everything in drawing mode + drawing = render_priv->state.drawing; + text_info->length = 0; + pen.x = 0; + pen.y = 0; + previous = 0; + num_glyphs = 0; + p = event->Text; + // Event parsing. + while (1) { + // get next char, executing style override + // this affects render_context + do { + code = get_next_char(render_priv, &p); + if (render_priv->state.drawing_mode && code) + ass_drawing_add_char(drawing, (char) code); + } while (code && render_priv->state.drawing_mode); // skip everything in drawing mode - // face could have been changed in get_next_char - if (!render_context.font) { - free_render_context(); - return 1; - } - - if (code == 0) - break; + // Parse drawing + if (drawing->i) { + drawing->scale_x = render_priv->state.scale_x * + render_priv->font_scale_x * + render_priv->font_scale; + drawing->scale_y = render_priv->state.scale_y * + render_priv->font_scale; + ass_drawing_hash(drawing); + p--; + code = -1; + } - if (text_info.length >= MAX_GLYPHS) { - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_MAX_GLYPHS_Reached, - (int)(event - frame_context.track->events), event->Start, event->Duration, event->Text); - break; - } + // face could have been changed in get_next_char + if (!render_priv->state.font) { + free_render_context(render_priv); + return 1; + } - if ( previous && code ) { - FT_Vector delta; - delta = ass_font_get_kerning(render_context.font, previous, code); - pen.x += delta.x * render_context.scale_x; - pen.y += delta.y * render_context.scale_y; - } + if (code == 0) + break; + + if (text_info->length >= text_info->max_glyphs) { + // Raise maximum number of glyphs + text_info->max_glyphs *= 2; + text_info->glyphs = + realloc(text_info->glyphs, + sizeof(GlyphInfo) * text_info->max_glyphs); + } - shift.x = pen.x & SUBPIXEL_MASK; - shift.y = pen.y & SUBPIXEL_MASK; + // Add kerning to pen + if (kern && previous && code && !drawing->hash) { + FT_Vector delta; + delta = + ass_font_get_kerning(render_priv->state.font, previous, + code); + pen.x += delta.x * render_priv->state.scale_x + * render_priv->font_scale_x; + pen.y += delta.y * render_priv->state.scale_y + * render_priv->font_scale_x; + } - if (render_context.evt_type == EVENT_POSITIONED) { - shift.x += double_to_d6(x2scr_pos(render_context.pos_x)) & SUBPIXEL_MASK; - shift.y -= double_to_d6(y2scr_pos(render_context.pos_y)) & SUBPIXEL_MASK; - } + ass_font_set_transform(render_priv->state.font, + render_priv->state.scale_x * + render_priv->font_scale_x, + render_priv->state.scale_y, NULL); - ass_font_set_transform(render_context.font, - render_context.scale_x * frame_context.font_scale_x, - render_context.scale_y, - &shift ); + get_outline_glyph(render_priv, code, + text_info->glyphs + text_info->length, drawing); - get_outline_glyph(code, text_info.glyphs + text_info.length, &shift); - - text_info.glyphs[text_info.length].pos.x = pen.x >> 6; - text_info.glyphs[text_info.length].pos.y = pen.y >> 6; + // Add additional space after italic to non-italic style changes + if (text_info->length && + text_info->glyphs[text_info->length - 1].hash_key.italic && + !render_priv->state.italic) { + int back = text_info->length - 1; + GlyphInfo *og = &text_info->glyphs[back]; + while (back && og->bbox.xMax - og->bbox.xMin == 0 + && og->hash_key.italic) + og = &text_info->glyphs[--back]; + if (og->bbox.xMax > og->advance.x) { + // The FreeType oblique slants by 6/16 + pen.x += og->bbox.yMax * 0.375; + } + } - pen.x += text_info.glyphs[text_info.length].advance.x; - pen.x += double_to_d6(render_context.hspacing); - pen.y += text_info.glyphs[text_info.length].advance.y; + text_info->glyphs[text_info->length].pos.x = pen.x; + text_info->glyphs[text_info->length].pos.y = pen.y; - previous = code; + pen.x += text_info->glyphs[text_info->length].advance.x; + pen.x += double_to_d6(render_priv->state.hspacing * + render_priv->font_scale + * render_priv->state.scale_x); + pen.y += text_info->glyphs[text_info->length].advance.y; + pen.y += (render_priv->state.fay * render_priv->state.scale_y) * + text_info->glyphs[text_info->length].advance.x; + + previous = code; - text_info.glyphs[text_info.length].symbol = code; - text_info.glyphs[text_info.length].linebreak = 0; - for (i = 0; i < 4; ++i) { - uint32_t clr = render_context.c[i]; - change_alpha(&clr, mult_alpha(_a(clr), render_context.fade), 1.); - text_info.glyphs[text_info.length].c[i] = clr; - } - text_info.glyphs[text_info.length].effect_type = render_context.effect_type; - text_info.glyphs[text_info.length].effect_timing = render_context.effect_timing; - text_info.glyphs[text_info.length].effect_skip_timing = render_context.effect_skip_timing; - text_info.glyphs[text_info.length].be = render_context.be; - text_info.glyphs[text_info.length].blur = render_context.blur; - text_info.glyphs[text_info.length].shadow = render_context.shadow; - text_info.glyphs[text_info.length].frx = render_context.frx; - text_info.glyphs[text_info.length].fry = render_context.fry; - text_info.glyphs[text_info.length].frz = render_context.frz; - ass_font_get_asc_desc(render_context.font, code, - &text_info.glyphs[text_info.length].asc, - &text_info.glyphs[text_info.length].desc); - text_info.glyphs[text_info.length].asc *= render_context.scale_y; - text_info.glyphs[text_info.length].desc *= render_context.scale_y; + text_info->glyphs[text_info->length].symbol = code; + text_info->glyphs[text_info->length].linebreak = 0; + for (i = 0; i < 4; ++i) { + uint32_t clr = render_priv->state.c[i]; + change_alpha(&clr, + mult_alpha(_a(clr), render_priv->state.fade), 1.); + text_info->glyphs[text_info->length].c[i] = clr; + } + text_info->glyphs[text_info->length].effect_type = + render_priv->state.effect_type; + text_info->glyphs[text_info->length].effect_timing = + render_priv->state.effect_timing; + text_info->glyphs[text_info->length].effect_skip_timing = + render_priv->state.effect_skip_timing; + text_info->glyphs[text_info->length].be = render_priv->state.be; + text_info->glyphs[text_info->length].blur = render_priv->state.blur; + text_info->glyphs[text_info->length].shadow_x = + render_priv->state.shadow_x; + text_info->glyphs[text_info->length].shadow_y = + render_priv->state.shadow_y; + text_info->glyphs[text_info->length].frx = render_priv->state.frx; + text_info->glyphs[text_info->length].fry = render_priv->state.fry; + text_info->glyphs[text_info->length].frz = render_priv->state.frz; + text_info->glyphs[text_info->length].fax = render_priv->state.fax; + text_info->glyphs[text_info->length].fay = render_priv->state.fay; + if (drawing->hash) { + text_info->glyphs[text_info->length].asc = drawing->asc; + text_info->glyphs[text_info->length].desc = drawing->desc; + } else { + ass_font_get_asc_desc(render_priv->state.font, code, + &text_info->glyphs[text_info->length].asc, + &text_info->glyphs[text_info->length].desc); - // fill bitmap_hash_key - text_info.glyphs[text_info.length].hash_key.font = render_context.font; - text_info.glyphs[text_info.length].hash_key.size = render_context.font_size; - text_info.glyphs[text_info.length].hash_key.outline = render_context.border * 0xFFFF; - text_info.glyphs[text_info.length].hash_key.scale_x = render_context.scale_x * 0xFFFF; - text_info.glyphs[text_info.length].hash_key.scale_y = render_context.scale_y * 0xFFFF; - text_info.glyphs[text_info.length].hash_key.frx = render_context.frx * 0xFFFF; - text_info.glyphs[text_info.length].hash_key.fry = render_context.fry * 0xFFFF; - text_info.glyphs[text_info.length].hash_key.frz = render_context.frz * 0xFFFF; - text_info.glyphs[text_info.length].hash_key.bold = render_context.bold; - text_info.glyphs[text_info.length].hash_key.italic = render_context.italic; - text_info.glyphs[text_info.length].hash_key.ch = code; - text_info.glyphs[text_info.length].hash_key.advance = shift; - text_info.glyphs[text_info.length].hash_key.be = render_context.be; - text_info.glyphs[text_info.length].hash_key.blur = render_context.blur; - - text_info.length++; + text_info->glyphs[text_info->length].asc *= + render_priv->state.scale_y; + text_info->glyphs[text_info->length].desc *= + render_priv->state.scale_y; + } - render_context.effect_type = EF_NONE; - render_context.effect_timing = 0; - render_context.effect_skip_timing = 0; - } - - if (text_info.length == 0) { - // no valid symbols in the event; this can be smth like {comment} - free_render_context(); - return 1; - } + // fill bitmap_hash_key + if (!drawing->hash) { + text_info->glyphs[text_info->length].hash_key.font = + render_priv->state.font; + text_info->glyphs[text_info->length].hash_key.size = + render_priv->state.font_size; + text_info->glyphs[text_info->length].hash_key.bold = + render_priv->state.bold; + text_info->glyphs[text_info->length].hash_key.italic = + render_priv->state.italic; + } else + text_info->glyphs[text_info->length].hash_key.drawing_hash = + drawing->hash; + text_info->glyphs[text_info->length].hash_key.ch = code; + text_info->glyphs[text_info->length].hash_key.outline.x = + double_to_d16(render_priv->state.border_x); + text_info->glyphs[text_info->length].hash_key.outline.y = + double_to_d16(render_priv->state.border_y); + text_info->glyphs[text_info->length].hash_key.scale_x = + double_to_d16(render_priv->state.scale_x); + text_info->glyphs[text_info->length].hash_key.scale_y = + double_to_d16(render_priv->state.scale_y); + text_info->glyphs[text_info->length].hash_key.frx = + rot_key(render_priv->state.frx); + text_info->glyphs[text_info->length].hash_key.fry = + rot_key(render_priv->state.fry); + text_info->glyphs[text_info->length].hash_key.frz = + rot_key(render_priv->state.frz); + text_info->glyphs[text_info->length].hash_key.fax = + double_to_d16(render_priv->state.fax); + text_info->glyphs[text_info->length].hash_key.fay = + double_to_d16(render_priv->state.fay); + text_info->glyphs[text_info->length].hash_key.advance.x = pen.x; + text_info->glyphs[text_info->length].hash_key.advance.y = pen.y; + text_info->glyphs[text_info->length].hash_key.be = + render_priv->state.be; + text_info->glyphs[text_info->length].hash_key.blur = + render_priv->state.blur; + text_info->glyphs[text_info->length].hash_key.border_style = + render_priv->state.style->BorderStyle; + text_info->glyphs[text_info->length].hash_key.shadow_offset.x = + double_to_d6( + render_priv->state.shadow_x * render_priv->border_scale - + (int) (render_priv->state.shadow_x * + render_priv->border_scale)); + text_info->glyphs[text_info->length].hash_key.shadow_offset.y = + double_to_d6( + render_priv->state.shadow_y * render_priv->border_scale - + (int) (render_priv->state.shadow_y * + render_priv->border_scale)); + text_info->glyphs[text_info->length].hash_key.flags = + render_priv->state.flags; - // depends on glyph x coordinates being monotonous, so it should be done before line wrap - process_karaoke_effects(); + text_info->length++; - // alignments - alignment = render_context.alignment; - halign = alignment & 3; - valign = alignment & 12; - - MarginL = (event->MarginL) ? event->MarginL : render_context.style->MarginL; - MarginR = (event->MarginR) ? event->MarginR : render_context.style->MarginR; - MarginV = (event->MarginV) ? event->MarginV : render_context.style->MarginV; - - if (render_context.evt_type != EVENT_HSCROLL) { - int max_text_width; + render_priv->state.effect_type = EF_NONE; + render_priv->state.effect_timing = 0; + render_priv->state.effect_skip_timing = 0; - // calculate max length of a line - max_text_width = x2scr(frame_context.track->PlayResX - MarginR) - x2scr(MarginL); - - // rearrange text in several lines - wrap_lines_smart(max_text_width); + if (drawing->hash) { + ass_drawing_free(drawing); + drawing = render_priv->state.drawing = + ass_drawing_new(render_priv->fontconfig_priv, + render_priv->state.font, + render_priv->settings.hinting, + render_priv->ftlibrary); + } + } - // align text - last_break = -1; - for (i = 1; i < text_info.length + 1; ++i) { // (text_info.length + 1) is the end of the last line - if ((i == text_info.length) || text_info.glyphs[i].linebreak) { - int width, shift = 0; - glyph_info_t* first_glyph = text_info.glyphs + last_break + 1; - glyph_info_t* last_glyph = text_info.glyphs + i - 1; - while ((last_glyph > first_glyph) && ((last_glyph->symbol == '\n') || (last_glyph->symbol == 0))) - last_glyph --; + if (text_info->length == 0) { + // no valid symbols in the event; this can be smth like {comment} + free_render_context(render_priv); + return 1; + } + // depends on glyph x coordinates being monotonous, so it should be done before line wrap + process_karaoke_effects(render_priv); - width = last_glyph->pos.x + d6_to_int(last_glyph->advance.x) - first_glyph->pos.x; - if (halign == HALIGN_LEFT) { // left aligned, no action - shift = 0; - } else if (halign == HALIGN_RIGHT) { // right aligned - shift = max_text_width - width; - } else if (halign == HALIGN_CENTER) { // centered - shift = (max_text_width - width) / 2; - } - for (j = last_break + 1; j < i; ++j) { - text_info.glyphs[j].pos.x += shift; - } - last_break = i - 1; - } - } - } else { // render_context.evt_type == EVENT_HSCROLL - measure_text(); - } + // alignments + alignment = render_priv->state.alignment; + halign = alignment & 3; + valign = alignment & 12; + + MarginL = + (event->MarginL) ? event->MarginL : render_priv->state.style-> + MarginL; + MarginR = + (event->MarginR) ? event->MarginR : render_priv->state.style-> + MarginR; + MarginV = + (event->MarginV) ? event->MarginV : render_priv->state.style-> + MarginV; + + if (render_priv->state.evt_type != EVENT_HSCROLL) { + double max_text_width; + + // calculate max length of a line + max_text_width = + x2scr(render_priv, + render_priv->track->PlayResX - MarginR) - + x2scr(render_priv, MarginL); - // determing text bounding box - compute_string_bbox(&text_info, &bbox); - - // determine device coordinates for text + // rearrange text in several lines + wrap_lines_smart(render_priv, max_text_width); - // x coordinate for everything except positioned events - if (render_context.evt_type == EVENT_NORMAL || - render_context.evt_type == EVENT_VSCROLL) { - device_x = x2scr(MarginL); - } else if (render_context.evt_type == EVENT_HSCROLL) { - if (render_context.scroll_direction == SCROLL_RL) - device_x = x2scr(frame_context.track->PlayResX - render_context.scroll_shift); - else if (render_context.scroll_direction == SCROLL_LR) - device_x = x2scr(render_context.scroll_shift) - (bbox.xMax - bbox.xMin); - } + // align text + last_break = -1; + for (i = 1; i < text_info->length + 1; ++i) { // (text_info->length + 1) is the end of the last line + if ((i == text_info->length) + || text_info->glyphs[i].linebreak) { + double width, shift = 0; + GlyphInfo *first_glyph = + text_info->glyphs + last_break + 1; + GlyphInfo *last_glyph = text_info->glyphs + i - 1; + + while (first_glyph < last_glyph && first_glyph->skip) + first_glyph++; + + while ((last_glyph > first_glyph) + && ((last_glyph->symbol == '\n') + || (last_glyph->symbol == 0) + || (last_glyph->skip))) + last_glyph--; - // y coordinate for everything except positioned events - if (render_context.evt_type == EVENT_NORMAL || - render_context.evt_type == EVENT_HSCROLL) { - if (valign == VALIGN_TOP) { // toptitle - device_y = y2scr_top(MarginV) + d6_to_int(text_info.lines[0].asc); - } else if (valign == VALIGN_CENTER) { // midtitle - int scr_y = y2scr(frame_context.track->PlayResY / 2); - device_y = scr_y - (bbox.yMax - bbox.yMin) / 2; - } else { // subtitle - int scr_y; - if (valign != VALIGN_SUB) - mp_msg(MSGT_ASS, MSGL_V, "Invalid valign, supposing 0 (subtitle)\n"); - scr_y = y2scr_sub(frame_context.track->PlayResY - MarginV); - device_y = scr_y; - device_y -= d6_to_int(text_info.height); - device_y += d6_to_int(text_info.lines[0].asc); - } - } else if (render_context.evt_type == EVENT_VSCROLL) { - if (render_context.scroll_direction == SCROLL_TB) - device_y = y2scr(render_context.clip_y0 + render_context.scroll_shift) - (bbox.yMax - bbox.yMin); - else if (render_context.scroll_direction == SCROLL_BT) - device_y = y2scr(render_context.clip_y1 - render_context.scroll_shift); - } + width = d6_to_double( + last_glyph->pos.x + last_glyph->advance.x - + first_glyph->pos.x); + if (halign == HALIGN_LEFT) { // left aligned, no action + shift = 0; + } else if (halign == HALIGN_RIGHT) { // right aligned + shift = max_text_width - width; + } else if (halign == HALIGN_CENTER) { // centered + shift = (max_text_width - width) / 2.0; + } + for (j = last_break + 1; j < i; ++j) { + text_info->glyphs[j].pos.x += double_to_d6(shift); + } + last_break = i - 1; + } + } + } else { // render_priv->state.evt_type == EVENT_HSCROLL + measure_text(render_priv); + } + + // determing text bounding box + compute_string_bbox(text_info, &bbox); + + // determine device coordinates for text - // positioned events are totally different - if (render_context.evt_type == EVENT_POSITIONED) { - int base_x = 0; - int base_y = 0; - mp_msg(MSGT_ASS, MSGL_DBG2, "positioned event at %f, %f\n", render_context.pos_x, render_context.pos_y); - get_base_point(bbox, alignment, &base_x, &base_y); - device_x = x2scr_pos(render_context.pos_x) - base_x; - device_y = y2scr_pos(render_context.pos_y) - base_y; - } - - // fix clip coordinates (they depend on alignment) - if (render_context.evt_type == EVENT_NORMAL || - render_context.evt_type == EVENT_HSCROLL || - render_context.evt_type == EVENT_VSCROLL) { - render_context.clip_x0 = x2scr(render_context.clip_x0); - render_context.clip_x1 = x2scr(render_context.clip_x1); - if (valign == VALIGN_TOP) { - render_context.clip_y0 = y2scr_top(render_context.clip_y0); - render_context.clip_y1 = y2scr_top(render_context.clip_y1); - } else if (valign == VALIGN_CENTER) { - render_context.clip_y0 = y2scr(render_context.clip_y0); - render_context.clip_y1 = y2scr(render_context.clip_y1); - } else if (valign == VALIGN_SUB) { - render_context.clip_y0 = y2scr_sub(render_context.clip_y0); - render_context.clip_y1 = y2scr_sub(render_context.clip_y1); - } - } else if (render_context.evt_type == EVENT_POSITIONED) { - render_context.clip_x0 = x2scr_pos(render_context.clip_x0); - render_context.clip_x1 = x2scr_pos(render_context.clip_x1); - render_context.clip_y0 = y2scr_pos(render_context.clip_y0); - render_context.clip_y1 = y2scr_pos(render_context.clip_y1); - } - - render_context.clip_x0 = FFMIN(FFMAX(render_context.clip_x0, 0), frame_context.width); - render_context.clip_x1 = FFMIN(FFMAX(render_context.clip_x1, 0), frame_context.width); - render_context.clip_y0 = FFMIN(FFMAX(render_context.clip_y0, 0), frame_context.height); - render_context.clip_y1 = FFMIN(FFMAX(render_context.clip_y1, 0), frame_context.height); + // x coordinate for everything except positioned events + if (render_priv->state.evt_type == EVENT_NORMAL || + render_priv->state.evt_type == EVENT_VSCROLL) { + device_x = x2scr(render_priv, MarginL); + } else if (render_priv->state.evt_type == EVENT_HSCROLL) { + if (render_priv->state.scroll_direction == SCROLL_RL) + device_x = + x2scr(render_priv, + render_priv->track->PlayResX - + render_priv->state.scroll_shift); + else if (render_priv->state.scroll_direction == SCROLL_LR) + device_x = + x2scr(render_priv, + render_priv->state.scroll_shift) - (bbox.xMax - + bbox.xMin); + } + // y coordinate for everything except positioned events + if (render_priv->state.evt_type == EVENT_NORMAL || + render_priv->state.evt_type == EVENT_HSCROLL) { + if (valign == VALIGN_TOP) { // toptitle + device_y = + y2scr_top(render_priv, + MarginV) + text_info->lines[0].asc; + } else if (valign == VALIGN_CENTER) { // midtitle + double scr_y = + y2scr(render_priv, render_priv->track->PlayResY / 2.0); + device_y = scr_y - (bbox.yMax + bbox.yMin) / 2.0; + } else { // subtitle + double scr_y; + if (valign != VALIGN_SUB) + ass_msg(render_priv->library, MSGL_V, + "Invalid valign, supposing 0 (subtitle)"); + scr_y = + y2scr_sub(render_priv, + render_priv->track->PlayResY - MarginV); + device_y = scr_y; + device_y -= text_info->height; + device_y += text_info->lines[0].asc; + } + } else if (render_priv->state.evt_type == EVENT_VSCROLL) { + if (render_priv->state.scroll_direction == SCROLL_TB) + device_y = + y2scr(render_priv, + render_priv->state.clip_y0 + + render_priv->state.scroll_shift) - (bbox.yMax - + bbox.yMin); + else if (render_priv->state.scroll_direction == SCROLL_BT) + device_y = + y2scr(render_priv, + render_priv->state.clip_y1 - + render_priv->state.scroll_shift); + } + // positioned events are totally different + if (render_priv->state.evt_type == EVENT_POSITIONED) { + double base_x = 0; + double base_y = 0; + ass_msg(render_priv->library, MSGL_DBG2, "positioned event at %f, %f", + render_priv->state.pos_x, render_priv->state.pos_y); + get_base_point(&bbox, alignment, &base_x, &base_y); + device_x = + x2scr_pos(render_priv, render_priv->state.pos_x) - base_x; + device_y = + y2scr_pos(render_priv, render_priv->state.pos_y) - base_y; + } + // fix clip coordinates (they depend on alignment) + if (render_priv->state.evt_type == EVENT_NORMAL || + render_priv->state.evt_type == EVENT_HSCROLL || + render_priv->state.evt_type == EVENT_VSCROLL) { + render_priv->state.clip_x0 = + x2scr(render_priv, render_priv->state.clip_x0); + render_priv->state.clip_x1 = + x2scr(render_priv, render_priv->state.clip_x1); + if (valign == VALIGN_TOP) { + render_priv->state.clip_y0 = + y2scr_top(render_priv, render_priv->state.clip_y0); + render_priv->state.clip_y1 = + y2scr_top(render_priv, render_priv->state.clip_y1); + } else if (valign == VALIGN_CENTER) { + render_priv->state.clip_y0 = + y2scr(render_priv, render_priv->state.clip_y0); + render_priv->state.clip_y1 = + y2scr(render_priv, render_priv->state.clip_y1); + } else if (valign == VALIGN_SUB) { + render_priv->state.clip_y0 = + y2scr_sub(render_priv, render_priv->state.clip_y0); + render_priv->state.clip_y1 = + y2scr_sub(render_priv, render_priv->state.clip_y1); + } + } else if (render_priv->state.evt_type == EVENT_POSITIONED) { + render_priv->state.clip_x0 = + x2scr_pos(render_priv, render_priv->state.clip_x0); + render_priv->state.clip_x1 = + x2scr_pos(render_priv, render_priv->state.clip_x1); + render_priv->state.clip_y0 = + y2scr_pos(render_priv, render_priv->state.clip_y0); + render_priv->state.clip_y1 = + y2scr_pos(render_priv, render_priv->state.clip_y1); + } + // calculate rotation parameters + { + DVector center; - // calculate rotation parameters - { - FT_Vector center; + if (render_priv->state.have_origin) { + center.x = x2scr(render_priv, render_priv->state.org_x); + center.y = y2scr(render_priv, render_priv->state.org_y); + } else { + double bx = 0., by = 0.; + get_base_point(&bbox, alignment, &bx, &by); + center.x = device_x + bx; + center.y = device_y + by; + } + + for (i = 0; i < text_info->length; ++i) { + GlyphInfo *info = text_info->glyphs + i; - if (render_context.have_origin) { - center.x = x2scr(render_context.org_x); - center.y = y2scr(render_context.org_y); - } else { - int bx = 0, by = 0; - get_base_point(bbox, alignment, &bx, &by); - center.x = device_x + bx; - center.y = device_y + by; - } - - for (i = 0; i < text_info.length; ++i) { - glyph_info_t* info = text_info.glyphs + i; + if (info->hash_key.frx || info->hash_key.fry + || info->hash_key.frz || info->hash_key.fax + || info->hash_key.fay) { + info->hash_key.shift_x = info->pos.x + double_to_d6(device_x - center.x); + info->hash_key.shift_y = + -(info->pos.y + double_to_d6(device_y - center.y)); + } else { + info->hash_key.shift_x = 0; + info->hash_key.shift_y = 0; + } + } + } - if (info->hash_key.frx || info->hash_key.fry || info->hash_key.frz) { - info->hash_key.shift_x = info->pos.x + device_x - center.x; - info->hash_key.shift_y = - (info->pos.y + device_y - center.y); - } else { - info->hash_key.shift_x = 0; - info->hash_key.shift_y = 0; - } - } - } + // convert glyphs to bitmaps + for (i = 0; i < text_info->length; ++i) { + GlyphInfo *g = text_info->glyphs + i; + g->hash_key.advance.x = + double_to_d6(device_x - (int) device_x + + d6_to_double(g->pos.x & SUBPIXEL_MASK)) & ~SUBPIXEL_ACCURACY; + g->hash_key.advance.y = + double_to_d6(device_y - (int) device_y + + d6_to_double(g->pos.y & SUBPIXEL_MASK)) & ~SUBPIXEL_ACCURACY; + get_bitmap_glyph(render_priv, text_info->glyphs + i); + } - // convert glyphs to bitmaps - for (i = 0; i < text_info.length; ++i) - get_bitmap_glyph(text_info.glyphs + i); + memset(event_images, 0, sizeof(*event_images)); + event_images->top = device_y - text_info->lines[0].asc; + event_images->height = text_info->height; + event_images->left = device_x + bbox.xMin + 0.5; + event_images->width = bbox.xMax - bbox.xMin + 0.5; + event_images->detect_collisions = render_priv->state.detect_collisions; + event_images->shift_direction = (valign == VALIGN_TOP) ? 1 : -1; + event_images->event = event; + event_images->imgs = render_text(render_priv, (int) device_x, (int) device_y); - memset(event_images, 0, sizeof(*event_images)); - event_images->top = device_y - d6_to_int(text_info.lines[0].asc); - event_images->height = d6_to_int(text_info.height); - event_images->detect_collisions = render_context.detect_collisions; - event_images->shift_direction = (valign == VALIGN_TOP) ? 1 : -1; - event_images->event = event; - event_images->imgs = render_text(&text_info, device_x, device_y); + free_render_context(render_priv); - free_render_context(); - - return 0; + return 0; } /** * \brief deallocate image list * \param img list pointer */ -void ass_free_images(ass_image_t* img) +static void ass_free_images(ASS_Image *img) { - while (img) { - ass_image_t* next = img->next; - free(img); - img = next; - } + while (img) { + ASS_Image *next = img->next; + free(img); + img = next; + } } -static void ass_reconfigure(ass_renderer_t* priv) +static void ass_reconfigure(ASS_Renderer *priv) { - priv->render_id = ++last_render_id; - ass_glyph_cache_reset(); - ass_bitmap_cache_reset(); - ass_composite_cache_reset(); - ass_free_images(priv->prev_images_root); - priv->prev_images_root = 0; + priv->render_id++; + priv->cache.glyph_cache = + ass_glyph_cache_reset(priv->cache.glyph_cache); + priv->cache.bitmap_cache = + ass_bitmap_cache_reset(priv->cache.bitmap_cache); + priv->cache.composite_cache = + ass_composite_cache_reset(priv->cache.composite_cache); + ass_free_images(priv->prev_images_root); + priv->prev_images_root = 0; } -void ass_set_frame_size(ass_renderer_t* priv, int w, int h) +void ass_set_frame_size(ASS_Renderer *priv, int w, int h) { - if (priv->settings.frame_width != w || priv->settings.frame_height != h) { - priv->settings.frame_width = w; - priv->settings.frame_height = h; - if (priv->settings.aspect == 0.) - priv->settings.aspect = ((double)w) / h; - ass_reconfigure(priv); - } + if (priv->settings.frame_width != w || priv->settings.frame_height != h) { + priv->settings.frame_width = w; + priv->settings.frame_height = h; + if (priv->settings.aspect == 0.) { + priv->settings.aspect = ((double) w) / h; + priv->settings.storage_aspect = ((double) w) / h; + } + ass_reconfigure(priv); + } } -void ass_set_margins(ass_renderer_t* priv, int t, int b, int l, int r) +void ass_set_margins(ASS_Renderer *priv, int t, int b, int l, int r) { - if (priv->settings.left_margin != l || - priv->settings.right_margin != r || - priv->settings.top_margin != t || - priv->settings.bottom_margin != b) { - priv->settings.left_margin = l; - priv->settings.right_margin = r; - priv->settings.top_margin = t; - priv->settings.bottom_margin = b; - ass_reconfigure(priv); - } -} - -void ass_set_use_margins(ass_renderer_t* priv, int use) -{ - priv->settings.use_margins = use; + if (priv->settings.left_margin != l || + priv->settings.right_margin != r || + priv->settings.top_margin != t + || priv->settings.bottom_margin != b) { + priv->settings.left_margin = l; + priv->settings.right_margin = r; + priv->settings.top_margin = t; + priv->settings.bottom_margin = b; + ass_reconfigure(priv); + } } -void ass_set_aspect_ratio(ass_renderer_t* priv, double ar) +void ass_set_use_margins(ASS_Renderer *priv, int use) { - if (priv->settings.aspect != ar) { - priv->settings.aspect = ar; - ass_reconfigure(priv); - } + priv->settings.use_margins = use; } -void ass_set_font_scale(ass_renderer_t* priv, double font_scale) +void ass_set_aspect_ratio(ASS_Renderer *priv, double dar, double sar) { - if (priv->settings.font_size_coeff != font_scale) { - priv->settings.font_size_coeff = font_scale; - ass_reconfigure(priv); - } + if (priv->settings.aspect != dar || priv->settings.storage_aspect != sar) { + priv->settings.aspect = dar; + priv->settings.storage_aspect = sar; + ass_reconfigure(priv); + } } -void ass_set_hinting(ass_renderer_t* priv, ass_hinting_t ht) +void ass_set_font_scale(ASS_Renderer *priv, double font_scale) { - if (priv->settings.hinting != ht) { - priv->settings.hinting = ht; - ass_reconfigure(priv); - } + if (priv->settings.font_size_coeff != font_scale) { + priv->settings.font_size_coeff = font_scale; + ass_reconfigure(priv); + } } -void ass_set_line_spacing(ass_renderer_t* priv, double line_spacing) +void ass_set_hinting(ASS_Renderer *priv, ASS_Hinting ht) { - priv->settings.line_spacing = line_spacing; + if (priv->settings.hinting != ht) { + priv->settings.hinting = ht; + ass_reconfigure(priv); + } +} + +void ass_set_line_spacing(ASS_Renderer *priv, double line_spacing) +{ + priv->settings.line_spacing = line_spacing; } -static int ass_set_fonts_(ass_renderer_t* priv, const char* default_font, const char* default_family, int fc) +void ass_set_fonts(ASS_Renderer *priv, const char *default_font, + const char *default_family, int fc, const char *config, + int update) { - if (priv->settings.default_font) - free(priv->settings.default_font); - if (priv->settings.default_family) - free(priv->settings.default_family); + free(priv->settings.default_font); + free(priv->settings.default_family); + priv->settings.default_font = default_font ? strdup(default_font) : 0; + priv->settings.default_family = + default_family ? strdup(default_family) : 0; - priv->settings.default_font = default_font ? strdup(default_font) : 0; - priv->settings.default_family = default_family ? strdup(default_family) : 0; - - if (priv->fontconfig_priv) - fontconfig_done(priv->fontconfig_priv); - priv->fontconfig_priv = fontconfig_init(priv->library, priv->ftlibrary, default_family, default_font, fc); - - return !!priv->fontconfig_priv; + if (priv->fontconfig_priv) + fontconfig_done(priv->fontconfig_priv); + priv->fontconfig_priv = + fontconfig_init(priv->library, priv->ftlibrary, default_family, + default_font, fc, config, update); } -int ass_set_fonts(ass_renderer_t* priv, const char* default_font, const char* default_family) +int ass_fonts_update(ASS_Renderer *render_priv) { - return ass_set_fonts_(priv, default_font, default_family, 1); -} - -int ass_set_fonts_nofc(ass_renderer_t* priv, const char* default_font, const char* default_family) -{ - return ass_set_fonts_(priv, default_font, default_family, 0); + return fontconfig_update(render_priv->fontconfig_priv); } /** * \brief Start a new frame */ -static int ass_start_frame(ass_renderer_t *priv, ass_track_t* track, long long now) +static int +ass_start_frame(ASS_Renderer *render_priv, ASS_Track *track, + long long now) { - ass_renderer = priv; - global_settings = &priv->settings; + ASS_Settings *settings_priv = &render_priv->settings; + CacheStore *cache = &render_priv->cache; + + if (!render_priv->settings.frame_width + && !render_priv->settings.frame_height) + return 1; // library not initialized + + if (render_priv->library != track->library) + return 1; + + free_list_clear(render_priv); + + if (track->n_events == 0) + return 1; // nothing to do - if (!priv->settings.frame_width && !priv->settings.frame_height) - return 1; // library not initialized - - if (track->n_events == 0) - return 1; // nothing to do + render_priv->width = settings_priv->frame_width; + render_priv->height = settings_priv->frame_height; + render_priv->orig_width = + settings_priv->frame_width - settings_priv->left_margin - + settings_priv->right_margin; + render_priv->orig_height = + settings_priv->frame_height - settings_priv->top_margin - + settings_priv->bottom_margin; + render_priv->orig_width_nocrop = + settings_priv->frame_width - FFMAX(settings_priv->left_margin, + 0) - + FFMAX(settings_priv->right_margin, 0); + render_priv->orig_height_nocrop = + settings_priv->frame_height - FFMAX(settings_priv->top_margin, + 0) - + FFMAX(settings_priv->bottom_margin, 0); + render_priv->track = track; + render_priv->time = now; - frame_context.ass_priv = priv; - frame_context.width = global_settings->frame_width; - frame_context.height = global_settings->frame_height; - frame_context.orig_width = global_settings->frame_width - global_settings->left_margin - global_settings->right_margin; - frame_context.orig_height = global_settings->frame_height - global_settings->top_margin - global_settings->bottom_margin; - frame_context.orig_width_nocrop = global_settings->frame_width - - FFMAX(global_settings->left_margin, 0) - - FFMAX(global_settings->right_margin, 0); - frame_context.orig_height_nocrop = global_settings->frame_height - - FFMAX(global_settings->top_margin, 0) - - FFMAX(global_settings->bottom_margin, 0); - frame_context.track = track; - frame_context.time = now; + ass_lazy_track_init(render_priv); + + render_priv->font_scale = settings_priv->font_size_coeff * + render_priv->orig_height / render_priv->track->PlayResY; + if (render_priv->track->ScaledBorderAndShadow) + render_priv->border_scale = + ((double) render_priv->orig_height) / + render_priv->track->PlayResY; + else + render_priv->border_scale = 1.; + + // PAR correction + render_priv->font_scale_x = render_priv->settings.aspect / + render_priv->settings.storage_aspect; + + render_priv->prev_images_root = render_priv->images_root; + render_priv->images_root = 0; - ass_lazy_track_init(); + if (cache->bitmap_cache->cache_size > cache->bitmap_max_size) { + ass_msg(render_priv->library, MSGL_V, + "Hitting hard bitmap cache limit (was: %ld bytes), " + "resetting.", (long) cache->bitmap_cache->cache_size); + cache->bitmap_cache = ass_bitmap_cache_reset(cache->bitmap_cache); + cache->composite_cache = ass_composite_cache_reset( + cache->composite_cache); + ass_free_images(render_priv->prev_images_root); + render_priv->prev_images_root = 0; + } - frame_context.font_scale = global_settings->font_size_coeff * - frame_context.orig_height / frame_context.track->PlayResY; - if (frame_context.track->ScaledBorderAndShadow) - frame_context.border_scale = ((double)frame_context.orig_height) / frame_context.track->PlayResY; - else - frame_context.border_scale = 1.; + if (cache->glyph_cache->count > cache->glyph_max) { + ass_msg(render_priv->library, MSGL_V, + "Hitting hard glyph cache limit (was: %ld glyphs), resetting.", + (long) cache->glyph_cache->count); + cache->glyph_cache = ass_glyph_cache_reset(cache->glyph_cache); + } - frame_context.font_scale_x = 1.; - - priv->prev_images_root = priv->images_root; - priv->images_root = 0; - - return 0; + return 0; } -static int cmp_event_layer(const void* p1, const void* p2) +static int cmp_event_layer(const void *p1, const void *p2) { - ass_event_t* e1 = ((event_images_t*)p1)->event; - ass_event_t* e2 = ((event_images_t*)p2)->event; - if (e1->Layer < e2->Layer) - return -1; - if (e1->Layer > e2->Layer) - return 1; - if (e1->ReadOrder < e2->ReadOrder) - return -1; - if (e1->ReadOrder > e2->ReadOrder) - return 1; - return 0; + ASS_Event *e1 = ((EventImages *) p1)->event; + ASS_Event *e2 = ((EventImages *) p2)->event; + if (e1->Layer < e2->Layer) + return -1; + if (e1->Layer > e2->Layer) + return 1; + if (e1->ReadOrder < e2->ReadOrder) + return -1; + if (e1->ReadOrder > e2->ReadOrder) + return 1; + return 0; } -#define MAX_EVENTS 100 - -static render_priv_t* get_render_priv(ass_event_t* event) +static ASS_RenderPriv *get_render_priv(ASS_Renderer *render_priv, + ASS_Event *event) { - if (!event->render_priv) - event->render_priv = calloc(1, sizeof(render_priv_t)); - // FIXME: check render_id - if (ass_renderer->render_id != event->render_priv->render_id) { - memset(event->render_priv, 0, sizeof(render_priv_t)); - event->render_priv->render_id = ass_renderer->render_id; - } - return event->render_priv; + if (!event->render_priv) + event->render_priv = calloc(1, sizeof(ASS_RenderPriv)); + if (render_priv->render_id != event->render_priv->render_id) { + memset(event->render_priv, 0, sizeof(ASS_RenderPriv)); + event->render_priv->render_id = render_priv->render_id; + } + + return event->render_priv; } -typedef struct segment_s { - int a, b; // top and height -} segment_t; - -static int overlap(segment_t* s1, segment_t* s2) +static int overlap(Segment *s1, Segment *s2) { - if (s1->a >= s2->b || s2->a >= s1->b) - return 0; - return 1; + if (s1->a >= s2->b || s2->a >= s1->b || + s1->ha >= s2->hb || s2->ha >= s1->hb) + return 0; + return 1; } -static int cmp_segment(const void* p1, const void* p2) +static int cmp_segment(const void *p1, const void *p2) { - return ((segment_t*)p1)->a - ((segment_t*)p2)->a; + return ((Segment *) p1)->a - ((Segment *) p2)->a; } -static void shift_event(event_images_t* ei, int shift) +static void +shift_event(ASS_Renderer *render_priv, EventImages *ei, int shift) { - ass_image_t* cur = ei->imgs; - while (cur) { - cur->dst_y += shift; - // clip top and bottom - if (cur->dst_y < 0) { - int clip = - cur->dst_y; - cur->h -= clip; - cur->bitmap += clip * cur->stride; - cur->dst_y = 0; - } - if (cur->dst_y + cur->h >= frame_context.height) { - int clip = cur->dst_y + cur->h - frame_context.height; - cur->h -= clip; - } - if (cur->h <= 0) { - cur->h = 0; - cur->dst_y = 0; - } - cur = cur->next; - } - ei->top += shift; + ASS_Image *cur = ei->imgs; + while (cur) { + cur->dst_y += shift; + // clip top and bottom + if (cur->dst_y < 0) { + int clip = -cur->dst_y; + cur->h -= clip; + cur->bitmap += clip * cur->stride; + cur->dst_y = 0; + } + if (cur->dst_y + cur->h >= render_priv->height) { + int clip = cur->dst_y + cur->h - render_priv->height; + cur->h -= clip; + } + if (cur->h <= 0) { + cur->h = 0; + cur->dst_y = 0; + } + cur = cur->next; + } + ei->top += shift; } // dir: 1 - move down // -1 - move up -static int fit_segment(segment_t* s, segment_t* fixed, int* cnt, int dir) +static int fit_segment(Segment *s, Segment *fixed, int *cnt, int dir) { - int i; - int shift = 0; + int i; + int shift = 0; - if (dir == 1) // move down - for (i = 0; i < *cnt; ++i) { - if (s->b + shift <= fixed[i].a || s->a + shift >= fixed[i].b) - continue; - shift = fixed[i].b - s->a; - } - else // dir == -1, move up - for (i = *cnt-1; i >= 0; --i) { - if (s->b + shift <= fixed[i].a || s->a + shift >= fixed[i].b) - continue; - shift = fixed[i].a - s->b; - } + if (dir == 1) // move down + for (i = 0; i < *cnt; ++i) { + if (s->b + shift <= fixed[i].a || s->a + shift >= fixed[i].b || + s->hb <= fixed[i].ha || s->ha >= fixed[i].hb) + continue; + shift = fixed[i].b - s->a; + } else // dir == -1, move up + for (i = *cnt - 1; i >= 0; --i) { + if (s->b + shift <= fixed[i].a || s->a + shift >= fixed[i].b || + s->hb <= fixed[i].ha || s->ha >= fixed[i].hb) + continue; + shift = fixed[i].a - s->b; + } - fixed[*cnt].a = s->a + shift; - fixed[*cnt].b = s->b + shift; - (*cnt)++; - qsort(fixed, *cnt, sizeof(segment_t), cmp_segment); + fixed[*cnt].a = s->a + shift; + fixed[*cnt].b = s->b + shift; + fixed[*cnt].ha = s->ha; + fixed[*cnt].hb = s->hb; + (*cnt)++; + qsort(fixed, *cnt, sizeof(Segment), cmp_segment); - return shift; + return shift; } -static void fix_collisions(event_images_t* imgs, int cnt) +static void +fix_collisions(ASS_Renderer *render_priv, EventImages *imgs, int cnt) { - segment_t used[MAX_EVENTS]; - int cnt_used = 0; - int i, j; + Segment *used = malloc(cnt * sizeof(*used)); + int cnt_used = 0; + int i, j; - // fill used[] with fixed events - for (i = 0; i < cnt; ++i) { - render_priv_t* priv; - if (!imgs[i].detect_collisions) continue; - priv = get_render_priv(imgs[i].event); - if (priv->height > 0) { // it's a fixed event - segment_t s; - s.a = priv->top; - s.b = priv->top + priv->height; - if (priv->height != imgs[i].height) { // no, it's not - mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_EventHeightHasChanged); - priv->top = 0; - priv->height = 0; - } - for (j = 0; j < cnt_used; ++j) - if (overlap(&s, used + j)) { // no, it's not - priv->top = 0; - priv->height = 0; - } - if (priv->height > 0) { // still a fixed event - used[cnt_used].a = priv->top; - used[cnt_used].b = priv->top + priv->height; - cnt_used ++; - shift_event(imgs + i, priv->top - imgs[i].top); - } - } - } - qsort(used, cnt_used, sizeof(segment_t), cmp_segment); + // fill used[] with fixed events + for (i = 0; i < cnt; ++i) { + ASS_RenderPriv *priv; + if (!imgs[i].detect_collisions) + continue; + priv = get_render_priv(render_priv, imgs[i].event); + if (priv->height > 0) { // it's a fixed event + Segment s; + s.a = priv->top; + s.b = priv->top + priv->height; + s.ha = priv->left; + s.hb = priv->left + priv->width; + if (priv->height != imgs[i].height) { // no, it's not + ass_msg(render_priv->library, MSGL_WARN, + "Warning! Event height has changed"); + priv->top = 0; + priv->height = 0; + priv->left = 0; + priv->width = 0; + } + for (j = 0; j < cnt_used; ++j) + if (overlap(&s, used + j)) { // no, it's not + priv->top = 0; + priv->height = 0; + priv->left = 0; + priv->width = 0; + } + if (priv->height > 0) { // still a fixed event + used[cnt_used].a = priv->top; + used[cnt_used].b = priv->top + priv->height; + used[cnt_used].ha = priv->left; + used[cnt_used].hb = priv->left + priv->width; + cnt_used++; + shift_event(render_priv, imgs + i, priv->top - imgs[i].top); + } + } + } + qsort(used, cnt_used, sizeof(Segment), cmp_segment); - // try to fit other events in free spaces - for (i = 0; i < cnt; ++i) { - render_priv_t* priv; - if (!imgs[i].detect_collisions) continue; - priv = get_render_priv(imgs[i].event); - if (priv->height == 0) { // not a fixed event - int shift; - segment_t s; - s.a = imgs[i].top; - s.b = imgs[i].top + imgs[i].height; - shift = fit_segment(&s, used, &cnt_used, imgs[i].shift_direction); - if (shift) shift_event(imgs + i, shift); - // make it fixed - priv->top = imgs[i].top; - priv->height = imgs[i].height; - } + // try to fit other events in free spaces + for (i = 0; i < cnt; ++i) { + ASS_RenderPriv *priv; + if (!imgs[i].detect_collisions) + continue; + priv = get_render_priv(render_priv, imgs[i].event); + if (priv->height == 0) { // not a fixed event + int shift; + Segment s; + s.a = imgs[i].top; + s.b = imgs[i].top + imgs[i].height; + s.ha = imgs[i].left; + s.hb = imgs[i].left + imgs[i].width; + shift = fit_segment(&s, used, &cnt_used, imgs[i].shift_direction); + if (shift) + shift_event(render_priv, imgs + i, shift); + // make it fixed + priv->top = imgs[i].top; + priv->height = imgs[i].height; + priv->left = imgs[i].left; + priv->width = imgs[i].width; + } - } + } + + free(used); } /** @@ -2516,17 +2564,23 @@ * \param i2 second image * \return 0 if identical, 1 if different positions, 2 if different content */ -int ass_image_compare(ass_image_t *i1, ass_image_t *i2) +static int ass_image_compare(ASS_Image *i1, ASS_Image *i2) { - if (i1->w != i2->w) return 2; - if (i1->h != i2->h) return 2; - if (i1->stride != i2->stride) return 2; - if (i1->color != i2->color) return 2; - if (i1->bitmap != i2->bitmap) - return 2; - if (i1->dst_x != i2->dst_x) return 1; - if (i1->dst_y != i2->dst_y) return 1; - return 0; + if (i1->w != i2->w) + return 2; + if (i1->h != i2->h) + return 2; + if (i1->stride != i2->stride) + return 2; + if (i1->color != i2->color) + return 2; + if (i1->bitmap != i2->bitmap) + return 2; + if (i1->dst_x != i2->dst_x) + return 1; + if (i1->dst_y != i2->dst_y) + return 1; + return 0; } /** @@ -2534,35 +2588,36 @@ * \param priv library handle * \return 0 if identical, 1 if different positions, 2 if different content */ -int ass_detect_change(ass_renderer_t *priv) +static int ass_detect_change(ASS_Renderer *priv) { - ass_image_t* img, *img2; - int diff; + ASS_Image *img, *img2; + int diff; - img = priv->prev_images_root; - img2 = priv->images_root; - diff = 0; - while (img && diff < 2) { - ass_image_t* next, *next2; - next = img->next; - if (img2) { - int d = ass_image_compare(img, img2); - if (d > diff) diff = d; - next2 = img2->next; - } else { - // previous list is shorter - diff = 2; - break; - } - img = next; - img2 = next2; - } + img = priv->prev_images_root; + img2 = priv->images_root; + diff = 0; + while (img && diff < 2) { + ASS_Image *next, *next2; + next = img->next; + if (img2) { + int d = ass_image_compare(img, img2); + if (d > diff) + diff = d; + next2 = img2->next; + } else { + // previous list is shorter + diff = 2; + break; + } + img = next; + img2 = next2; + } - // is the previous list longer? - if (img2) - diff = 2; + // is the previous list longer? + if (img2) + diff = 2; - return diff; + return diff; } /** @@ -2574,62 +2629,66 @@ * 0 if identical, 1 if different positions, 2 if different content. * Can be NULL, in that case no detection is performed. */ -ass_image_t* ass_render_frame(ass_renderer_t *priv, ass_track_t* track, long long now, int* detect_change) +ASS_Image *ass_render_frame(ASS_Renderer *priv, ASS_Track *track, + long long now, int *detect_change) { - int i, cnt, rc; - event_images_t* last; - ass_image_t** tail; + int i, cnt, rc; + EventImages *last; + ASS_Image **tail; - // init frame - rc = ass_start_frame(priv, track, now); - if (rc != 0) - return 0; + // init frame + rc = ass_start_frame(priv, track, now); + if (rc != 0) + return 0; - // render events separately - cnt = 0; - for (i = 0; i < track->n_events; ++i) { - ass_event_t* event = track->events + i; - if ( (event->Start <= now) && (now < (event->Start + event->Duration)) ) { - if (cnt >= priv->eimg_size) { - priv->eimg_size += 100; - priv->eimg = realloc(priv->eimg, priv->eimg_size * sizeof(event_images_t)); - } - rc = ass_render_event(event, priv->eimg + cnt); - if (!rc) ++cnt; - } - } - - // sort by layer - qsort(priv->eimg, cnt, sizeof(event_images_t), cmp_event_layer); + // render events separately + cnt = 0; + for (i = 0; i < track->n_events; ++i) { + ASS_Event *event = track->events + i; + if ((event->Start <= now) + && (now < (event->Start + event->Duration))) { + if (cnt >= priv->eimg_size) { + priv->eimg_size += 100; + priv->eimg = + realloc(priv->eimg, + priv->eimg_size * sizeof(EventImages)); + } + rc = ass_render_event(priv, event, priv->eimg + cnt); + if (!rc) + ++cnt; + } + } - // call fix_collisions for each group of events with the same layer - last = priv->eimg; - for (i = 1; i < cnt; ++i) - if (last->event->Layer != priv->eimg[i].event->Layer) { - fix_collisions(last, priv->eimg + i - last); - last = priv->eimg + i; - } - if (cnt > 0) - fix_collisions(last, priv->eimg + cnt - last); + // sort by layer + qsort(priv->eimg, cnt, sizeof(EventImages), cmp_event_layer); + + // call fix_collisions for each group of events with the same layer + last = priv->eimg; + for (i = 1; i < cnt; ++i) + if (last->event->Layer != priv->eimg[i].event->Layer) { + fix_collisions(priv, last, priv->eimg + i - last); + last = priv->eimg + i; + } + if (cnt > 0) + fix_collisions(priv, last, priv->eimg + cnt - last); - // concat lists - tail = &ass_renderer->images_root; - for (i = 0; i < cnt; ++i) { - ass_image_t* cur = priv->eimg[i].imgs; - while (cur) { - *tail = cur; - tail = &cur->next; - cur = cur->next; - } - } + // concat lists + tail = &priv->images_root; + for (i = 0; i < cnt; ++i) { + ASS_Image *cur = priv->eimg[i].imgs; + while (cur) { + *tail = cur; + tail = &cur->next; + cur = cur->next; + } + } - if (detect_change) - *detect_change = ass_detect_change(priv); + if (detect_change) + *detect_change = ass_detect_change(priv); - // free the previous image list - ass_free_images(priv->prev_images_root); - priv->prev_images_root = 0; + // free the previous image list + ass_free_images(priv->prev_images_root); + priv->prev_images_root = 0; - return ass_renderer->images_root; + return priv->images_root; } -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libass/ass_render.h Fri Jan 08 18:35:44 2010 +0000 @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com> + * Copyright (C) 2009 Grigori Goronzy <greg@geekmind.org> + * + * This file is part of libass. + * + * libass is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * libass is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with libass; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef LIBASS_RENDER_H +#define LIBASS_RENDER_H + +#include <inttypes.h> +#include <ft2build.h> +#include FT_FREETYPE_H +#include FT_STROKER_H +#include FT_GLYPH_H +#include FT_SYNTHESIS_H + +#include "ass.h" +#include "ass_font.h" +#include "ass_bitmap.h" +#include "ass_cache.h" +#include "ass_utils.h" +#include "ass_fontconfig.h" +#include "ass_library.h" +#include "ass_drawing.h" + +typedef struct { + double xMin; + double xMax; + double yMin; + double yMax; +} DBBox; + +typedef struct { + double x; + double y; +} DVector; + +typedef struct free_list { + void *object; + struct free_list *next; +} FreeList; + +typedef struct { + int frame_width; + int frame_height; + double font_size_coeff; // font size multiplier + double line_spacing; // additional line spacing (in frame pixels) + int top_margin; // height of top margin. Everything except toptitles is shifted down by top_margin. + int bottom_margin; // height of bottom margin. (frame_height - top_margin - bottom_margin) is original video height. + int left_margin; + int right_margin; + int use_margins; // 0 - place all subtitles inside original frame + // 1 - use margins for placing toptitles and subtitles + double aspect; // frame aspect ratio, d_width / d_height. + double storage_aspect; // pixel ratio of the source image + ASS_Hinting hinting; + + char *default_font; + char *default_family; +} ASS_Settings; + +// a rendered event +typedef struct { + ASS_Image *imgs; + int top, height, left, width; + int detect_collisions; + int shift_direction; + ASS_Event *event; +} EventImages; + +typedef enum { + EF_NONE = 0, + EF_KARAOKE, + EF_KARAOKE_KF, + EF_KARAOKE_KO +} Effect; + +// describes a glyph +// GlyphInfo and TextInfo are used for text centering and word-wrapping operations +typedef struct { + unsigned symbol; + unsigned skip; // skip glyph when layouting text + FT_Glyph glyph; + FT_Glyph outline_glyph; + Bitmap *bm; // glyph bitmap + Bitmap *bm_o; // outline bitmap + Bitmap *bm_s; // shadow bitmap + FT_BBox bbox; + FT_Vector pos; + char linebreak; // the first (leading) glyph of some line ? + uint32_t c[4]; // colors + FT_Vector advance; // 26.6 + Effect effect_type; + int effect_timing; // time duration of current karaoke word + // after process_karaoke_effects: distance in pixels from the glyph origin. + // part of the glyph to the left of it is displayed in a different color. + int effect_skip_timing; // delay after the end of last karaoke word + int asc, desc; // font max ascender and descender + int be; // blur edges + double blur; // gaussian blur + double shadow_x; + double shadow_y; + double frx, fry, frz; // rotation + double fax, fay; // text shearing + + BitmapHashKey hash_key; +} GlyphInfo; + +typedef struct { + double asc, desc; +} LineInfo; + +typedef struct { + GlyphInfo *glyphs; + int length; + LineInfo *lines; + int n_lines; + double height; + int max_glyphs; + int max_lines; +} TextInfo; + +// Renderer state. +// Values like current font face, color, screen position, clipping and so on are stored here. +typedef struct { + ASS_Event *event; + ASS_Style *style; + + ASS_Font *font; + char *font_path; + double font_size; + int flags; // decoration flags (underline/strike-through) + + FT_Stroker stroker; + int alignment; // alignment overrides go here; if zero, style value will be used + double frx, fry, frz; + double fax, fay; // text shearing + enum { + EVENT_NORMAL, // "normal" top-, sub- or mid- title + EVENT_POSITIONED, // happens after pos(,), margins are ignored + EVENT_HSCROLL, // "Banner" transition effect, text_width is unlimited + EVENT_VSCROLL // "Scroll up", "Scroll down" transition effects + } evt_type; + double pos_x, pos_y; // position + double org_x, org_y; // origin + char have_origin; // origin is explicitly defined; if 0, get_base_point() is used + double scale_x, scale_y; + double hspacing; // distance between letters, in pixels + double border_x; // outline width + double border_y; + uint32_t c[4]; // colors(Primary, Secondary, so on) in RGBA + int clip_x0, clip_y0, clip_x1, clip_y1; + char clip_mode; // 1 = iclip + char detect_collisions; + uint32_t fade; // alpha from \fad + char be; // blur edges + double blur; // gaussian blur + double shadow_x; + double shadow_y; + int drawing_mode; // not implemented; when != 0 text is discarded, except for style override tags + ASS_Drawing *drawing; // current drawing + ASS_Drawing *clip_drawing; // clip vector + int clip_drawing_mode; // 0 = regular clip, 1 = inverse clip + + Effect effect_type; + int effect_timing; + int effect_skip_timing; + + enum { + SCROLL_LR, // left-to-right + SCROLL_RL, + SCROLL_TB, // top-to-bottom + SCROLL_BT + } scroll_direction; // for EVENT_HSCROLL, EVENT_VSCROLL + int scroll_shift; + + // face properties + char *family; + unsigned bold; + unsigned italic; + int treat_family_as_pattern; + int wrap_style; +} RenderContext; + +typedef struct { + Hashmap *font_cache; + Hashmap *glyph_cache; + Hashmap *bitmap_cache; + Hashmap *composite_cache; + size_t glyph_max; + size_t bitmap_max_size; +} CacheStore; + +struct ass_renderer { + ASS_Library *library; + FT_Library ftlibrary; + FCInstance *fontconfig_priv; + ASS_Settings settings; + int render_id; + ASS_SynthPriv *synth_priv; + + ASS_Image *images_root; // rendering result is stored here + ASS_Image *prev_images_root; + + EventImages *eimg; // temporary buffer for sorting rendered events + int eimg_size; // allocated buffer size + + // frame-global data + int width, height; // screen dimensions + int orig_height; // frame height ( = screen height - margins ) + int orig_width; // frame width ( = screen width - margins ) + int orig_height_nocrop; // frame height ( = screen height - margins + cropheight) + int orig_width_nocrop; // frame width ( = screen width - margins + cropwidth) + ASS_Track *track; + long long time; // frame's timestamp, ms + double font_scale; + double font_scale_x; // x scale applied to all glyphs to preserve text aspect ratio + double border_scale; + + RenderContext state; + TextInfo text_info; + CacheStore cache; + + FreeList *free_head; + FreeList *free_tail; +}; + +typedef struct render_priv { + int top, height, left, width; + int render_id; +} RenderPriv; + +typedef struct { + int x0; + int y0; + int x1; + int y1; +} Rect; + +typedef struct { + int a, b; // top and height + int ha, hb; // left and width +} Segment; + +void reset_render_context(ASS_Renderer *render_priv); + +#endif /* LIBASS_RENDER_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libass/ass_strtod.c Fri Jan 08 18:35:44 2010 +0000 @@ -0,0 +1,247 @@ +/* + * Copyright (c) 1988-1993 The Regents of the University of California. + * Copyright (c) 1994 Sun Microsystems, Inc. + * + * Permission to use, copy, modify, and distribute this + * software and its documentation for any purpose and without + * fee is hereby granted, provided that the above copyright + * notice appear in all copies. The University of California + * makes no representations about the suitability of this + * software for any purpose. It is provided "as is" without + * express or implied warranty. + * + */ + +#include <stdlib.h> +#include <ctype.h> +#include <errno.h> + +static int maxExponent = 511; /* Largest possible base 10 exponent. Any + * exponent larger than this will already + * produce underflow or overflow, so there's + * no need to worry about additional digits. + */ + +static double powersOf10[] = { /* Table giving binary powers of 10. Entry */ + 10., /* is 10^2^i. Used to convert decimal */ + 100., /* exponents into floating-point numbers. */ + 1.0e4, + 1.0e8, + 1.0e16, + 1.0e32, + 1.0e64, + 1.0e128, + 1.0e256 +}; + +/* + *---------------------------------------------------------------------- + * + * strtod -- + * + * This procedure converts a floating-point number from an ASCII + * decimal representation to internal double-precision format. + * + * Results: + * The return value is the double-precision floating-point + * representation of the characters in string. If endPtr isn't + * NULL, then *endPtr is filled in with the address of the + * next character after the last one that was part of the + * floating-point number. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +double +ass_strtod(string, endPtr) + const char *string; /* A decimal ASCII floating-point number, + * optionally preceded by white space. + * Must have form "-I.FE-X", where I is the + * integer part of the mantissa, F is the + * fractional part of the mantissa, and X + * is the exponent. Either of the signs + * may be "+", "-", or omitted. Either I + * or F may be omitted, or both. The decimal + * point isn't necessary unless F is present. + * The "E" may actually be an "e". E and X + * may both be omitted (but not just one). + */ + char **endPtr; /* If non-NULL, store terminating character's + * address here. */ +{ + int sign, expSign = 0; + double fraction, dblExp, *d; + register const char *p; + register int c; + int exp = 0; /* Exponent read from "EX" field. */ + int fracExp = 0; /* Exponent that derives from the fractional + * part. Under normal circumstatnces, it is + * the negative of the number of digits in F. + * However, if I is very long, the last digits + * of I get dropped (otherwise a long I with a + * large negative exponent could cause an + * unnecessary overflow on I alone). In this + * case, fracExp is incremented one for each + * dropped digit. */ + int mantSize; /* Number of digits in mantissa. */ + int decPt; /* Number of mantissa digits BEFORE decimal + * point. */ + const char *pExp; /* Temporarily holds location of exponent + * in string. */ + + /* + * Strip off leading blanks and check for a sign. + */ + + p = string; + while (isspace(*p)) { + p += 1; + } + if (*p == '-') { + sign = 1; + p += 1; + } else { + if (*p == '+') { + p += 1; + } + sign = 0; + } + + /* + * Count the number of digits in the mantissa (including the decimal + * point), and also locate the decimal point. + */ + + decPt = -1; + for (mantSize = 0; ; mantSize += 1) + { + c = *p; + if (!isdigit(c)) { + if ((c != '.') || (decPt >= 0)) { + break; + } + decPt = mantSize; + } + p += 1; + } + + /* + * Now suck up the digits in the mantissa. Use two integers to + * collect 9 digits each (this is faster than using floating-point). + * If the mantissa has more than 18 digits, ignore the extras, since + * they can't affect the value anyway. + */ + + pExp = p; + p -= mantSize; + if (decPt < 0) { + decPt = mantSize; + } else { + mantSize -= 1; /* One of the digits was the point. */ + } + if (mantSize > 18) { + fracExp = decPt - 18; + mantSize = 18; + } else { + fracExp = decPt - mantSize; + } + if (mantSize == 0) { + fraction = 0.0; + p = string; + goto done; + } else { + int frac1, frac2; + frac1 = 0; + for ( ; mantSize > 9; mantSize -= 1) + { + c = *p; + p += 1; + if (c == '.') { + c = *p; + p += 1; + } + frac1 = 10*frac1 + (c - '0'); + } + frac2 = 0; + for (; mantSize > 0; mantSize -= 1) + { + c = *p; + p += 1; + if (c == '.') { + c = *p; + p += 1; + } + frac2 = 10*frac2 + (c - '0'); + } + fraction = (1.0e9 * frac1) + frac2; + } + + /* + * Skim off the exponent. + */ + + p = pExp; + if ((*p == 'E') || (*p == 'e')) { + p += 1; + if (*p == '-') { + expSign = 1; + p += 1; + } else { + if (*p == '+') { + p += 1; + } + expSign = 0; + } + while (isdigit(*p)) { + exp = exp * 10 + (*p - '0'); + p += 1; + } + } + if (expSign) { + exp = fracExp - exp; + } else { + exp = fracExp + exp; + } + + /* + * Generate a floating-point number that represents the exponent. + * Do this by processing the exponent one bit at a time to combine + * many powers of 2 of 10. Then combine the exponent with the + * fraction. + */ + + if (exp < 0) { + expSign = 1; + exp = -exp; + } else { + expSign = 0; + } + if (exp > maxExponent) { + exp = maxExponent; + errno = ERANGE; + } + dblExp = 1.0; + for (d = powersOf10; exp != 0; exp >>= 1, d += 1) { + if (exp & 01) { + dblExp *= *d; + } + } + if (expSign) { + fraction /= dblExp; + } else { + fraction *= dblExp; + } + +done: + if (endPtr != NULL) { + *endPtr = (char *) p; + } + + if (sign) { + return -fraction; + } + return fraction; +}
--- a/libass/ass_types.h Fri Jan 08 18:19:10 2010 +0000 +++ b/libass/ass_types.h Fri Jan 08 18:35:44 2010 +0000 @@ -1,5 +1,3 @@ -// -*- c-basic-offset: 8; indent-tabs-mode: t -*- -// vim:ts=8:sw=8:noet:ai: /* * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com> * @@ -32,88 +30,96 @@ #define HALIGN_CENTER 2 #define HALIGN_RIGHT 3 -/// ass Style: line -typedef struct ass_style_s { - char* Name; - char* FontName; - double FontSize; - uint32_t PrimaryColour; - uint32_t SecondaryColour; - uint32_t OutlineColour; - uint32_t BackColour; - int Bold; - int Italic; - int Underline; - int StrikeOut; - double ScaleX; - double ScaleY; - double Spacing; - int Angle; - int BorderStyle; - double Outline; - double Shadow; - int Alignment; - int MarginL; - int MarginR; - int MarginV; -// int AlphaLevel; - int Encoding; - int treat_fontname_as_pattern; -} ass_style_t; +/* Opaque objects internally used by libass. Contents are private. */ +typedef struct ass_renderer ASS_Renderer; +typedef struct render_priv ASS_RenderPriv; +typedef struct parser_priv ASS_ParserPriv; +typedef struct ass_library ASS_Library; -typedef struct render_priv_s render_priv_t; +/* ASS Style: line */ +typedef struct ass_style { + char *Name; + char *FontName; + double FontSize; + uint32_t PrimaryColour; + uint32_t SecondaryColour; + uint32_t OutlineColour; + uint32_t BackColour; + int Bold; + int Italic; + int Underline; + int StrikeOut; + double ScaleX; + double ScaleY; + double Spacing; + int Angle; + int BorderStyle; + double Outline; + double Shadow; + int Alignment; + int MarginL; + int MarginR; + int MarginV; + int Encoding; + int treat_fontname_as_pattern; +} ASS_Style; -/// ass_event_t corresponds to a single Dialogue line -/// Text is stored as-is, style overrides will be parsed later -typedef struct ass_event_s { - long long Start; // ms - long long Duration; // ms +/* + * ASS_Event corresponds to a single Dialogue line; + * text is stored as-is, style overrides will be parsed later. + */ +typedef struct ass_event { + long long Start; // ms + long long Duration; // ms - int ReadOrder; - int Layer; - int Style; - char* Name; - int MarginL; - int MarginR; - int MarginV; - char* Effect; - char* Text; + int ReadOrder; + int Layer; + int Style; + char *Name; + int MarginL; + int MarginR; + int MarginV; + char *Effect; + char *Text; - render_priv_t* render_priv; -} ass_event_t; - -typedef struct parser_priv_s parser_priv_t; - -typedef struct ass_library_s ass_library_t; + ASS_RenderPriv *render_priv; +} ASS_Event; -/// ass track represent either an external script or a matroska subtitle stream (no real difference between them) -/// it can be used in rendering after the headers are parsed (i.e. events format line read) -typedef struct ass_track_s { - int n_styles; // amount used - int max_styles; // amount allocated - int n_events; - int max_events; - ass_style_t* styles; // array of styles, max_styles length, n_styles used - ass_event_t* events; // the same as styles +/* + * ass track represent either an external script or a matroska subtitle stream + * (no real difference between them); it can be used in rendering after the + * headers are parsed (i.e. events format line read). + */ +typedef struct ass_track { + int n_styles; // amount used + int max_styles; // amount allocated + int n_events; + int max_events; + ASS_Style *styles; // array of styles, max_styles length, n_styles used + ASS_Event *events; // the same as styles - char* style_format; // style format line (everything after "Format: ") - char* event_format; // event format line - - enum {TRACK_TYPE_UNKNOWN = 0, TRACK_TYPE_ASS, TRACK_TYPE_SSA} track_type; + char *style_format; // style format line (everything after "Format: ") + char *event_format; // event format line - // script header fields - int PlayResX; - int PlayResY; - double Timer; - int WrapStyle; - char ScaledBorderAndShadow; + enum { + TRACK_TYPE_UNKNOWN = 0, + TRACK_TYPE_ASS, + TRACK_TYPE_SSA + } track_type; - - int default_style; // index of default style - char* name; // file name in case of external subs, 0 for streams + // Script header fields + int PlayResX; + int PlayResY; + double Timer; + int WrapStyle; + int ScaledBorderAndShadow; + int Kerning; - ass_library_t* library; - parser_priv_t* parser_priv; -} ass_track_t; + int default_style; // index of default style + char *name; // file name in case of external subs, 0 for streams + + ASS_Library *library; + ASS_ParserPriv *parser_priv; +} ASS_Track; #endif /* LIBASS_TYPES_H */
--- a/libass/ass_utils.c Fri Jan 08 18:19:10 2010 +0000 +++ b/libass/ass_utils.c Fri Jan 08 18:35:44 2010 +0000 @@ -1,5 +1,3 @@ -// -*- c-basic-offset: 8; indent-tabs-mode: t -*- -// vim:ts=8:sw=8:noet:ai: /* * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com> * @@ -23,113 +21,187 @@ #include "config.h" #include <stdlib.h> +#include <stdio.h> #include <inttypes.h> #include <ft2build.h> #include FT_GLYPH_H -#include "mputils.h" +#include "ass_library.h" +#include "ass.h" #include "ass_utils.h" -int mystrtoi(char** p, int* res) +int mystrtoi(char **p, int *res) { - // NOTE: base argument is ignored, but not used in libass anyway - double temp_res; - char* start = *p; - temp_res = strtod(*p, p); - *res = (int) (temp_res + 0.5); - if (*p != start) return 1; - else return 0; + double temp_res; + char *start = *p; + temp_res = ass_strtod(*p, p); + *res = (int) (temp_res + (temp_res > 0 ? 0.5 : -0.5)); + if (*p != start) + return 1; + else + return 0; } -int mystrtoll(char** p, long long* res) +int mystrtoll(char **p, long long *res) { - double temp_res; - char* start = *p; - temp_res = strtod(*p, p); - *res = (long long) (temp_res + 0.5); - if (*p != start) return 1; - else return 0; + double temp_res; + char *start = *p; + temp_res = ass_strtod(*p, p); + *res = (int) (temp_res + (temp_res > 0 ? 0.5 : -0.5)); + if (*p != start) + return 1; + else + return 0; } -int mystrtou32(char** p, int base, uint32_t* res) +int mystrtou32(char **p, int base, uint32_t *res) { - char* start = *p; - *res = strtoll(*p, p, base); - if (*p != start) return 1; - else return 0; + char *start = *p; + *res = strtoll(*p, p, base); + if (*p != start) + return 1; + else + return 0; } -int mystrtod(char** p, double* res) +int mystrtod(char **p, double *res) { - char* start = *p; - *res = strtod(*p, p); - if (*p != start) return 1; - else return 0; + char *start = *p; + *res = ass_strtod(*p, p); + if (*p != start) + return 1; + else + return 0; } -int strtocolor(char** q, uint32_t* res) +int strtocolor(ASS_Library *library, char **q, uint32_t *res, int hex) { - uint32_t color = 0; - int result; - char* p = *q; + uint32_t color = 0; + int result; + char *p = *q; + int base = hex ? 16 : 10; - if (*p == '&') ++p; - else mp_msg(MSGT_ASS, MSGL_DBG2, "suspicious color format: \"%s\"\n", p); + if (*p == '&') + ++p; + else + ass_msg(library, MSGL_DBG2, "suspicious color format: \"%s\"\n", p); - if (*p == 'H' || *p == 'h') { - ++p; - result = mystrtou32(&p, 16, &color); - } else { - result = mystrtou32(&p, 0, &color); - } + if (*p == 'H' || *p == 'h') { + ++p; + result = mystrtou32(&p, 16, &color); + } else { + result = mystrtou32(&p, base, &color); + } - { - unsigned char* tmp = (unsigned char*)(&color); - unsigned char b; - b = tmp[0]; tmp[0] = tmp[3]; tmp[3] = b; - b = tmp[1]; tmp[1] = tmp[2]; tmp[2] = b; - } - if (*p == '&') ++p; - *q = p; + { + unsigned char *tmp = (unsigned char *) (&color); + unsigned char b; + b = tmp[0]; + tmp[0] = tmp[3]; + tmp[3] = b; + b = tmp[1]; + tmp[1] = tmp[2]; + tmp[2] = b; + } + if (*p == '&') + ++p; + *q = p; - *res = color; - return result; + *res = color; + return result; } // Return a boolean value for a string -char parse_bool(char* str) { - while (*str == ' ' || *str == '\t') - str++; - if (!strncasecmp(str, "yes", 3)) - return 1; - else if (strtol(str, NULL, 10) > 0) - return 1; - return 0; +char parse_bool(char *str) +{ + while (*str == ' ' || *str == '\t') + str++; + if (!strncasecmp(str, "yes", 3)) + return 1; + else if (strtol(str, NULL, 10) > 0) + return 1; + return 0; +} + +void ass_msg(ASS_Library *priv, int lvl, char *fmt, ...) +{ + va_list va; + va_start(va, fmt); + priv->msg_callback(lvl, fmt, va, priv->msg_callback_data); + va_end(va); } -#if 0 -static void sprint_tag(uint32_t tag, char* dst) +unsigned ass_utf8_get_char(char **str) { - dst[0] = (tag >> 24) & 0xFF; - dst[1] = (tag >> 16) & 0xFF; - dst[2] = (tag >> 8) & 0xFF; - dst[3] = tag & 0xFF; - dst[4] = 0; + uint8_t *strp = (uint8_t *) * str; + unsigned c = *strp++; + unsigned mask = 0x80; + int len = -1; + while (c & mask) { + mask >>= 1; + len++; + } + if (len <= 0 || len > 4) + goto no_utf8; + c &= mask - 1; + while ((*strp & 0xc0) == 0x80) { + if (len-- <= 0) + goto no_utf8; + c = (c << 6) | (*strp++ & 0x3f); + } + if (len) + goto no_utf8; + *str = (char *) strp; + return c; + + no_utf8: + strp = (uint8_t *) * str; + c = *strp++; + *str = (char *) strp; + return c; } -void dump_glyph(FT_Glyph g) +#ifdef CONFIG_ENCA +void *ass_guess_buffer_cp(ASS_Library *library, unsigned char *buffer, + int buflen, char *preferred_language, + char *fallback) { - char tag[5]; - int i; - FT_OutlineGlyph og = (FT_OutlineGlyph)g; - FT_Outline* o = &(og->outline); - sprint_tag(g->format, tag); - printf("glyph: %p \n", g); - printf("format: %s \n", tag); - printf("outline: %p \n", o); - printf("contours: %d, points: %d, points ptr: %p \n", o->n_contours, o->n_points, o->points); - for (i = 0; i < o->n_points; ++i) { - printf(" point %f, %f \n", d6_to_double(o->points[i].x), d6_to_double(o->points[i].y)); - } + const char **languages; + size_t langcnt; + EncaAnalyser analyser; + EncaEncoding encoding; + char *detected_sub_cp = NULL; + int i; + + languages = enca_get_languages(&langcnt); + ass_msg(library, MSGL_V, "ENCA supported languages"); + for (i = 0; i < langcnt; i++) { + ass_msg(library, MSGL_V, "lang %s", languages[i]); + } + + for (i = 0; i < langcnt; i++) { + const char *tmp; + + if (strcasecmp(languages[i], preferred_language) != 0) + continue; + analyser = enca_analyser_alloc(languages[i]); + encoding = enca_analyse_const(analyser, buffer, buflen); + tmp = enca_charset_name(encoding.charset, ENCA_NAME_STYLE_ICONV); + if (tmp && encoding.charset != ENCA_CS_UNKNOWN) { + detected_sub_cp = strdup(tmp); + ass_msg(library, MSGL_INFO, "ENCA detected charset: %s", tmp); + } + enca_analyser_free(analyser); + } + + free(languages); + + if (!detected_sub_cp) { + detected_sub_cp = strdup(fallback); + ass_msg(library, MSGL_INFO, + "ENCA detection failed: fallback to %s", fallback); + } + + return detected_sub_cp; } #endif
--- a/libass/ass_utils.h Fri Jan 08 18:19:10 2010 +0000 +++ b/libass/ass_utils.h Fri Jan 08 18:35:44 2010 +0000 @@ -1,5 +1,3 @@ -// -*- c-basic-offset: 8; indent-tabs-mode: t -*- -// vim:ts=8:sw=8:noet:ai: /* * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com> * @@ -23,44 +21,127 @@ #ifndef LIBASS_UTILS_H #define LIBASS_UTILS_H +#include <stdio.h> +#include <stdarg.h> #include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> -int mystrtoi(char** p, int* res); -int mystrtoll(char** p, long long* res); -int mystrtou32(char** p, int base, uint32_t* res); -int mystrtod(char** p, double* res); -int strtocolor(char** q, uint32_t* res); -char parse_bool(char* str); +#ifdef CONFIG_ENCA +#include <enca.h> +#endif + +#include "ass.h" + +#define MSGL_FATAL 0 +#define MSGL_ERR 1 +#define MSGL_WARN 2 +#define MSGL_INFO 4 +#define MSGL_V 6 +#define MSGL_DBG2 7 -static inline int d6_to_int(int x) { - return (x + 32) >> 6; -} -static inline int d16_to_int(int x) { - return (x + 32768) >> 16; -} -static inline int int_to_d6(int x) { - return x << 6; +#define FFMAX(a,b) ((a) > (b) ? (a) : (b)) +#define FFMIN(a,b) ((a) > (b) ? (b) : (a)) +#define FFMINMAX(c,a,b) FFMIN(FFMAX(c, a), b) + +int mystrtoi(char **p, int *res); +int mystrtoll(char **p, long long *res); +int mystrtou32(char **p, int base, uint32_t *res); +int mystrtod(char **p, double *res); +int strtocolor(ASS_Library *library, char **q, uint32_t *res, int hex); +char parse_bool(char *str); +unsigned ass_utf8_get_char(char **str); +void ass_msg(ASS_Library *priv, int lvl, char *fmt, ...); +#ifdef CONFIG_ENCA +void *ass_guess_buffer_cp(ASS_Library *library, unsigned char *buffer, + int buflen, char *preferred_language, + char *fallback); +#endif + +/* defined in ass_strtod.c */ +double ass_strtod(const char *string, char **endPtr); + +static inline int d6_to_int(int x) +{ + return (x + 32) >> 6; } -static inline int int_to_d16(int x) { - return x << 16; +static inline int d16_to_int(int x) +{ + return (x + 32768) >> 16; +} +static inline int int_to_d6(int x) +{ + return x << 6; } -static inline int d16_to_d6(int x) { - return (x + 512) >> 10; +static inline int int_to_d16(int x) +{ + return x << 16; } -static inline int d6_to_d16(int x) { - return x << 10; +static inline int d16_to_d6(int x) +{ + return (x + 512) >> 10; +} +static inline int d6_to_d16(int x) +{ + return x << 10; } -static inline double d6_to_double(int x) { - return x / 64.; +static inline double d6_to_double(int x) +{ + return x / 64.; } -static inline int double_to_d6(double x) { - return (int)(x * 64); +static inline int double_to_d6(double x) +{ + return (int) (x * 64); +} +static inline double d16_to_double(int x) +{ + return ((double) x) / 0x10000; } -static inline double d16_to_double(int x) { - return ((double)x) / 0x10000; +static inline int double_to_d16(double x) +{ + return (int) (x * 0x10000); } -static inline int double_to_d16(double x) { - return (int)(x * 0x10000); +static inline double d22_to_double(int x) +{ + return ((double) x) / 0x400000; +} +static inline int double_to_d22(double x) +{ + return (int) (x * 0x400000); } -#endif /* LIBASS_UTILS_H */ +// Calculate cache key for a rotational angle in degrees +static inline int rot_key(double a) +{ + const int m = double_to_d22(360.0); + return double_to_d22(a) % m; +} + +#define FNV1_32A_INIT (unsigned)0x811c9dc5 + +static inline unsigned fnv_32a_buf(void *buf, size_t len, unsigned hval) +{ + unsigned char *bp = buf; + unsigned char *be = bp + len; + while (bp < be) { + hval ^= (unsigned) *bp++; + hval += + (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + + (hval << 24); + } + return hval; +} +static inline unsigned fnv_32a_str(char *str, unsigned hval) +{ + unsigned char *s = (unsigned char *) str; + while (*s) { + hval ^= (unsigned) *s++; + hval += + (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + + (hval << 24); + } + return hval; +} + +#endif /* LIBASS_UTILS_H */