[MPlayer-cvslog] r30242 - in trunk: 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_drawi...
greg
subversion at mplayerhq.hu
Fri Jan 8 19:35:44 CET 2010
Author: greg
Date: Fri Jan 8 19:35:44 2010
New Revision: 30242
Log:
Update internal libass copy to commit 8db4a5
Added:
trunk/libass/ass_cache_template.h
trunk/libass/ass_drawing.c
trunk/libass/ass_drawing.h
trunk/libass/ass_parse.c
trunk/libass/ass_parse.h
trunk/libass/ass_render.h
trunk/libass/ass_strtod.c
Modified:
trunk/Makefile
trunk/libass/ass.c
trunk/libass/ass.h
trunk/libass/ass_bitmap.c
trunk/libass/ass_bitmap.h
trunk/libass/ass_cache.c
trunk/libass/ass_cache.h
trunk/libass/ass_font.c
trunk/libass/ass_font.h
trunk/libass/ass_fontconfig.c
trunk/libass/ass_fontconfig.h
trunk/libass/ass_library.c
trunk/libass/ass_library.h
trunk/libass/ass_render.c
trunk/libass/ass_types.h
trunk/libass/ass_utils.c
trunk/libass/ass_utils.h
Modified: trunk/Makefile
==============================================================================
--- trunk/Makefile Fri Jan 8 19:19:10 2010 (r30241)
+++ trunk/Makefile Fri Jan 8 19:35:44 2010 (r30242)
@@ -122,10 +122,13 @@ SRCS_COMMON-$(LIBASS) +=
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 \
Modified: trunk/libass/ass.c
==============================================================================
--- trunk/libass/ass.c Fri Jan 8 19:19:10 2010 (r30241)
+++ trunk/libass/ass.c Fri Jan 8 19:35:44 2010 (r30242)
@@ -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 at 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;
+typedef enum {
+ PST_UNKNOWN = 0,
+ PST_INFO,
+ PST_STYLES,
+ PST_EVENTS,
+ PST_FONTS
+} ParserState;
-struct parser_priv_s {
- parser_state_t state;
- char* fontname;
- char* fontdata;
- int fontdata_size;
- int fontdata_used;
+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 @@ static void rskip_spaces(char** str, cha
* 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 @@ static int numpad2align(int val) {
#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) \
+ } else if (strcasecmp(tname, #name) == 0) { \
+ target->name = string2color(track->library, token); \
+ ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
-#define COLORVAL(name) ANYVAL(name,string2color)
#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 @@ static char* next_token(char** str) {
* \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* format;
- char* q; // 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");
- }
+ char *token;
+ char *tname;
+ char *p = str;
+ int i;
+ ASS_Event *target = event;
- q = format = strdup(track->event_format);
+ char *format = strdup(track->event_format);
+ char *q = format; // format scanning pointer
- 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 @@ static int process_event_tail(ass_track_
* \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 @@ void process_force_style(ass_track_t* tr
* \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");
- }
+ 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);
+ q = format = strdup(track->style_format);
- mp_msg(MSGT_ASS, MSGL_V, "[%p] Style: %s\n", track, str);
+ ass_msg(track->library, MSGL_V, "[%p] Style: %s", track, str);
- sid = ass_alloc_style(track);
+ 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_t* track, char *str)
+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);
- }
- return 0;
+ 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_events_line(ass_track_t* track, char *str)
+static void event_format_fallback(ASS_Track *track)
{
- 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;
+ 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");
+}
- str += 9;
- skip_spaces(&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);
+ 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;
- eid = ass_alloc_event(track);
- event = track->events + eid;
+ str += 9;
+ skip_spaces(&str);
- process_event_tail(track, event, str, 0);
- } else {
- mp_msg(MSGT_ASS, MSGL_V, "Not understood: %s \n", str);
- }
- return 0;
+ eid = ass_alloc_event(track);
+ event = track->events + eid;
+
+ // 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 @@ static int process_fonts_line(ass_track_
* \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 @@ static int process_text(ass_track_t* tra
* \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 @@ void ass_process_data(ass_track_t* track
* \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 @@ static int check_duplicate_event(ass_tra
* \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;
+ eid = ass_alloc_event(track);
+ event = track->events + eid;
- p = str;
+ p = str;
- do {
- NEXT(p, token);
- event->ReadOrder = atoi(token);
- if (check_duplicate_event(track, event->ReadOrder))
- break;
+ do {
+ NEXT(p, token);
+ event->ReadOrder = atoi(token);
+ if (check_duplicate_event(track, event->ReadOrder))
+ break;
- NEXT(p, token);
- event->Layer = atoi(token);
+ NEXT(p, token);
+ event->Layer = atoi(token);
- process_event_tail(track, event, p, 3);
+ process_event_tail(track, event, p, 3);
- event->Start = timecode;
- event->Duration = duration;
+ 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 @@ void ass_process_chunk(ass_track_t* trac
* \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;
+ {
+ 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;
+ outbuf = malloc(osize);
+ ip = data;
+ op = outbuf;
- 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;
- }
+ 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;
+ }
- if (icdsc != (iconv_t)(-1)) {
- (void)iconv_close(icdsc);
- icdsc = (iconv_t)(-1);
- mp_msg(MSGT_ASS,MSGL_V,"LIBSUB: closed iconv descriptor.\n");
- }
+ if (icdsc != (iconv_t) (-1)) {
+ (void) iconv_close(icdsc);
+ icdsc = (iconv_t) (-1);
+ ass_msg(library, MSGL_V, "Closed iconv descriptor");
+ }
- return outbuf;
+ return outbuf;
}
-#endif // ICONV
+#endif // ICONV
/**
* \brief read file contents into newly allocated buffer
@@ -916,86 +995,91 @@ static char* sub_recode(char* data, size
* \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 @@ static ass_track_t* parse_memory(ass_lib
* \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 @@ char* read_file_recode(char* fname, char
* \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;
}
-
Modified: trunk/libass/ass.h
==============================================================================
--- trunk/libass/ass.h Fri Jan 8 19:19:10 2010 (r30241)
+++ trunk/libass/ass.h Fri Jan 8 19:35:44 2010 (r30242)
@@ -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 at 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 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
+/*
+ * 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_s* next; // linked list
-} ass_image_t;
+ struct ass_image *next; // Next image, or NULL
+} ASS_Image;
-/// Hinting type
-typedef enum {ASS_HINTING_NONE = 0,
- ASS_HINTING_LIGHT,
- ASS_HINTING_NORMAL,
- ASS_HINTING_NATIVE
-} ass_hinting_t;
+/*
+ * 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;
/**
- * \brief initialize the library
+ * \brief Initialize the library.
* \return library handle or NULL if failed
*/
-ass_library_t* ass_library_init(void);
+ASS_Library *ass_library_init(void);
/**
- * \brief finalize the library
+ * \brief Finalize the library
* \param priv library handle
*/
-void ass_library_done(ass_library_t*);
+void ass_library_done(ASS_Library *priv);
/**
- * \brief set private font directory
+ * \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
*/
-void ass_set_fonts_dir(ass_library_t* priv, const char* fonts_dir);
+void ass_set_fonts_dir(ASS_Library *priv, const char *fonts_dir);
-void ass_set_extract_fonts(ass_library_t* priv, int extract);
+/**
+ * \brief Whether fonts should be extracted from track data.
+ * \param priv library handle
+ * \param extract whether to extract fonts
+ */
+void ass_set_extract_fonts(ASS_Library *priv, int extract);
-void ass_set_style_overrides(ass_library_t* priv, char** list);
+/**
+ * \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_style_overrides(ASS_Library *priv, char **list);
/**
- * \brief initialize the renderer
+ * \brief Explicitly process style overrides for a track.
+ * \param track track handle
+ */
+void ass_process_force_style(ASS_Track *track);
+
+/**
+ * \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);
-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 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);
/**
- * \brief set font lookup defaults
+ * \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
*/
-int ass_set_fonts(ass_renderer_t* priv, const char* default_font, const char* default_family);
+void ass_set_margins(ASS_Renderer *priv, int t, int b, int l, int r);
/**
- * \brief set font lookup defaults, don't use fontconfig even if it is available
+ * \brief Whether margins should be used for placing regular events.
+ * \param priv renderer handle
+ * \param use whether to use the margins
*/
-int ass_set_fonts_nofc(ass_renderer_t* priv, const char* default_font, const char* default_family);
+void ass_set_use_margins(ASS_Renderer *priv, int use);
/**
- * \brief render a frame, producing a list of ass_image_t
- * \param priv library
+ * \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)
+ */
+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.
+ * \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.
+ */
+void ass_set_fonts(ASS_Renderer *priv, const char *default_font,
+ const char *default_family, int fc, const char *config,
+ int update);
+
+/**
+ * \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 @@ void ass_free_event(ass_track_t* track,
* \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 */
Modified: trunk/libass/ass_bitmap.c
==============================================================================
--- trunk/libass/ass_bitmap.c Fri Jan 8 19:19:10 2010 (r30241)
+++ trunk/libass/ass_bitmap.c Fri Jan 8 19:35:44 2010 (r30242)
@@ -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 at 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;
+ 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;
- bitmap_t* bm_s = copy_bitmap(bm_o);
+ 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* 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 (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;
+ }
+}
- 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;
- }
+/**
+ * \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, b;
- assert(bm_s);
- return bm_s;
+ // 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;
+ }
+ }
+ }
+
+ // 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));
+
+ 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);
+
+ 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);
+
+ 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(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);
+ if (outline_glyph) {
+ *bm_o = glyph_to_bitmap_internal(library, outline_glyph, bord);
+ if (!*bm_o) {
+ return 1;
+ }
+ }
- 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 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);
+ }
- assert(bm_s);
- return 0;
-}
+ // 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);
+ }
+ // 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;
+}
Modified: trunk/libass/ass_bitmap.h
==============================================================================
--- trunk/libass/ass_bitmap.h Fri Jan 8 19:19:10 2010 (r30241)
+++ trunk/libass/ass_bitmap.h Fri Jan 8 19:35:44 2010 (r30242)
@@ -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 at 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 @@ typedef struct bitmap_s {
* \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 */
Modified: trunk/libass/ass_cache.c
==============================================================================
--- trunk/libass/ass_cache.c Fri Jan 8 19:19:10 2010 (r30241)
+++ trunk/libass/ass_cache.c Fri Jan 8 19:35:44 2010 (r30242)
@@ -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 at 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;
-};
-
-#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;
-}
-
-static unsigned hashmap_hash(void* buf, size_t len)
+static unsigned hashmap_hash(void *buf, size_t len)
{
- return fnv_32a_buf(buf, len, FNV1_32A_INIT);
+ return fnv_32a_buf(buf, len, FNV1_32A_INIT);
}
-static int hashmap_key_compare(void* a, void* b, size_t size)
+static int hashmap_key_compare(void *a, void *b, size_t size)
{
- return memcmp(a, b, size) == 0;
+ return memcmp(a, b, size) == 0;
}
-static void hashmap_item_dtor(void* key, size_t key_size, void* value, size_t value_size)
+static void hashmap_item_dtor(void *key, size_t key_size, void *value,
+ size_t value_size)
{
- free(key);
- free(value);
+ free(key);
+ free(value);
}
-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 *hashmap_init(ASS_Library *library, size_t key_size,
+ size_t value_size, int nbuckets,
+ HashmapItemDtor item_dtor,
+ HashmapKeyCompare key_compare,
+ HashmapHash 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;
+ 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;
}
-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 @@ void* cache_add_bitmap(bitmap_hash_key_t
* \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 @@ void* cache_add_glyph(glyph_hash_key_t*
* \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 @@ void* cache_add_composite(composite_hash
* \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);
+}
Modified: trunk/libass/ass_cache.h
==============================================================================
--- trunk/libass/ass_cache.h Fri Jan 8 19:19:10 2010 (r30241)
+++ trunk/libass/ass_cache.h Fri Jan 8 19:35:44 2010 (r30242)
@@ -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 at 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);
-
-
-// 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
-
- 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
+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);
- FT_Vector advance; // subpixel shift vector
-} bitmap_hash_key_t;
+typedef struct hashmap_item {
+ void *key;
+ void *value;
+ struct hashmap_item *next;
+} HashmapItem;
+typedef HashmapItem *hashmap_item_p;
-typedef struct bitmap_hash_val_s {
- bitmap_t* bm; // the actual bitmaps
- bitmap_t* bm_o;
- bitmap_t* bm_s;
-} bitmap_hash_val_t;
+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;
-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);
+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);
+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 *);
-// 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;
+// Create definitions for bitmap_hash_key and glyph_hash_key
+#define CREATE_STRUCT_DEFINITIONS
+#include "ass_cache_template.h"
-typedef struct composite_hash_val_s {
- unsigned char* a;
- unsigned char* b;
-} composite_hash_val_t;
+typedef struct {
+ Bitmap *bm; // the actual bitmaps
+ Bitmap *bm_o;
+ Bitmap *bm_s;
+} BitmapHashValue;
-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_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);
-// 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 {
+ unsigned char *a;
+ unsigned char *b;
+} CompositeHashValue;
-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;
+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);
-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);
-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);
+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;
-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);
+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);
-#endif /* LIBASS_CACHE_H */
+#endif /* LIBASS_CACHE_H */
Added: trunk/libass/ass_cache_template.h
==============================================================================
--- /dev/null 00:00:00 1970 (empty, because file is newly added)
+++ trunk/libass/ass_cache_template.h Fri Jan 8 19:35:44 2010 (r30242)
@@ -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
Added: trunk/libass/ass_drawing.c
==============================================================================
--- /dev/null 00:00:00 1970 (empty, because file is newly added)
+++ trunk/libass/ass_drawing.c Fri Jan 8 19:35:44 2010 (r30242)
@@ -0,0 +1,495 @@
+/*
+ * Copyright (C) 2009 Grigori Goronzy <greg at 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;
+}
Added: trunk/libass/ass_drawing.h
==============================================================================
--- /dev/null 00:00:00 1970 (empty, because file is newly added)
+++ trunk/libass/ass_drawing.h Fri Jan 8 19:35:44 2010 (r30242)
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2009 Grigori Goronzy <greg at 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 */
Modified: trunk/libass/ass_font.c
==============================================================================
--- trunk/libass/ass_font.c Fri Jan 8 19:19:10 2010 (r30241)
+++ trunk/libass/ass_font.c Fri Jan 8 19:35:44 2010 (r30242)
@@ -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 at 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 @@ void ass_font_set_size(ass_font_t* font,
* \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];
- if (FT_Get_Char_Index(face, ch)) {
- *asc = face->size->metrics.ascender;
- *desc = - face->size->metrics.descender;
- return;
- }
- }
+ 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;
+ *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)
+{
+ 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},
+ };
+
+ 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;
- }
+ 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;
- }
+ 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);
+ }
-#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;
- }
+ 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;
+ }
- return glyph;
+ 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);
}
Modified: trunk/libass/ass_font.h
==============================================================================
--- trunk/libass/ass_font.h Fri Jan 8 19:19:10 2010 (r30241)
+++ trunk/libass/ass_font.h Fri Jan 8 19:35:44 2010 (r30242)
@@ -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 at 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
-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 {
+ char *family;
+ unsigned bold;
+ unsigned italic;
+ int treat_family_as_pattern;
+} ASS_FontDesc;
-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);
+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;
-#endif /* LIBASS_FONT_H */
+// 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 */
Modified: trunk/libass/ass_fontconfig.c
==============================================================================
--- trunk/libass/ass_fontconfig.c Fri Jan 8 19:19:10 2010 (r30241)
+++ trunk/libass/ass_fontconfig.c Fri Jan 8 19:35:44 2010 (r30242)
@@ -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 at 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 @@ struct fc_instance_s {
* \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;
- 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" : "");
+ 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" : "");
- 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,77 +231,51 @@ static char* _select_font(fc_instance_t*
* \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* 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;
-}
-
-#if (FC_VERSION < 20402)
-static char* validate_fname(char* name)
+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* 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;
+ 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;
}
-#endif
/**
* \brief Process memory font.
@@ -317,87 +286,55 @@ static char* validate_fname(char* name)
* 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;
-
- 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);
- }
-
- fname = validate_fname((char*)name);
-
- snprintf(buf, 1000, "%s/%s", fonts_dir, fname);
- free(fname);
-
- fp = fopen(buf, "wb");
- if (!fp) return;
-
- fwrite(data, data_size, 1, fp);
- fclose(fp);
+ int rc;
+ const char *name = library->fontdata[idx].name;
+ const char *data = library->fontdata[idx].data;
+ int data_size = library->fontdata[idx].size;
-#else // (FC_VERSION >= 20402)
- FT_Face face;
- FcPattern* pattern;
- FcFontSet* fset;
- FcBool res;
- int face_index, num_faces = 1;
+ 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;
+ 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;
- 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;
- }
+ 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;
+ }
- 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;
- }
+ 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;
+ }
- res = FcFontSetAdd(fset, pattern);
- if (!res) {
- mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FunctionCallFailed, "FcFontSetAdd");
- FT_Done_Face(face);
- return;
- }
+ res = FcFontSetAdd(fset, pattern);
+ if (!res) {
+ ass_msg(library, MSGL_WARN, "%s failed", "FcFontSetAdd");
+ FT_Done_Face(face);
+ return;
+ }
- FT_Done_Face(face);
- }
-#endif
+ FT_Done_Face(face);
+ }
}
/**
@@ -406,113 +343,119 @@ static void process_fontdata(fc_instance
* \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;
-
- if (!fc) {
- mp_msg(MSGT_ASS, MSGL_WARN,
- MSGTR_LIBASS_FontconfigDisabledDefaultFontWillBeUsed);
- goto exit;
- }
-
- rc = FcInit();
- assert(rc);
+ int rc;
+ FCInstance *priv = calloc(1, sizeof(FCInstance));
+ const char *dir = library->fonts_dir;
+ int i;
- priv->config = FcConfigGetCurrent();
- if (!priv->config) {
- mp_msg(MSGT_ASS, MSGL_FATAL, MSGTR_LIBASS_FcInitLoadConfigAndFontsFailed);
- goto exit;
- }
+ if (!fc) {
+ ass_msg(library, MSGL_WARN,
+ "Fontconfig disabled, only default font will be used.");
+ goto exit;
+ }
- for (i = 0; i < library->num_fontdata; ++i)
- process_fontdata(priv, library, ftlibrary, i);
+ 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);
+ }
- 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;
- }
+ if (!rc || !priv->config) {
+ ass_msg(library, MSGL_FATAL,
+ "No valid fontconfig configuration found!");
+ FcConfigDestroy(priv->config);
+ goto exit;
+ }
- 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;
- }
+ for (i = 0; i < library->num_fontdata; ++i)
+ process_fontdata(priv, library, ftlibrary, i);
- rc = FcDirSave(fcs, fss, (const FcChar8 *)dir);
- if (!rc) {
- mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FcDirSave);
- goto ErrorFontCache;
- }
- ErrorFontCache:
- ;
- }
- }
+ if (dir) {
+ ass_msg(library, MSGL_INFO, "Updating font cache");
- rc = FcConfigAppFontAddDir(priv->config, (const FcChar8*)dir);
- if (!rc) {
- mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FcConfigAppFontAddDirFailed);
- }
- }
+ rc = FcConfigAppFontAddDir(priv->config, (const FcChar8 *) dir);
+ if (!rc) {
+ ass_msg(library, MSGL_WARN, "%s failed", "FcConfigAppFontAddDir");
+ }
+ }
- priv->family_default = family ? strdup(family) : NULL;
+ priv->family_default = family ? strdup(family) : NULL;
exit:
- priv->path_default = path ? strdup(path) : NULL;
- priv->index_default = 0;
+ 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);
+}
-char* fontconfig_select(fc_instance_t* priv, const char* family, int treat_family_as_pattern,
- unsigned bold, unsigned italic, int* index, uint32_t code)
+#else /* CONFIG_FONTCONFIG */
+
+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;
}
-#endif
-
-void fontconfig_done(fc_instance_t* priv)
+int fontconfig_update(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);
+ // Do nothing
+ return 1;
}
+#endif
+void fontconfig_done(FCInstance *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);
+}
Modified: trunk/libass/ass_fontconfig.h
==============================================================================
--- trunk/libass/ass_fontconfig.h Fri Jan 8 19:19:10 2010 (r30241)
+++ trunk/libass/ass_fontconfig.h Fri Jan 8 19:35:44 2010 (r30242)
@@ -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 at 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 */
Modified: trunk/libass/ass_library.c
==============================================================================
--- trunk/libass/ass_library.c Fri Jan 8 19:19:10 2010 (r30241)
+++ trunk/libass/ass_library.c Fri Jan 8 19:35:44 2010 (r30242)
@@ -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 at 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"
+static void ass_msg_handler(int level, const char *fmt, va_list va, void *data)
+{
+ if (level > MSGL_INFO)
+ return;
+ fprintf(stderr, "[ass] ");
+ vfprintf(stderr, fmt, va);
+ fprintf(stderr, "\n");
+}
-ass_library_t* ass_library_init(void)
+ASS_Library *ass_library_init(void)
{
- return calloc(1, sizeof(ass_library_t));
+ ASS_Library* lib = calloc(1, sizeof(*lib));
+ lib->msg_callback = ass_msg_handler;
+
+ return lib;
}
-void ass_library_done(ass_library_t* priv)
+void ass_library_done(ASS_Library *priv)
{
- if (priv) {
- ass_set_fonts_dir(priv, NULL);
- ass_set_style_overrides(priv, NULL);
- ass_clear_fonts(priv);
- free(priv);
- }
+ if (priv) {
+ ass_set_fonts_dir(priv, NULL);
+ ass_set_style_overrides(priv, NULL);
+ ass_clear_fonts(priv);
+ free(priv);
+ }
}
-void ass_set_fonts_dir(ass_library_t* priv, const char* fonts_dir)
+void ass_set_fonts_dir(ASS_Library *priv, const char *fonts_dir)
{
- if (priv->fonts_dir)
- free(priv->fonts_dir);
+ if (priv->fonts_dir)
+ free(priv->fonts_dir);
- priv->fonts_dir = fonts_dir ? strdup(fonts_dir) : 0;
+ priv->fonts_dir = fonts_dir ? strdup(fonts_dir) : 0;
}
-void ass_set_extract_fonts(ass_library_t* priv, int extract)
+void ass_set_extract_fonts(ASS_Library *priv, int extract)
{
- priv->extract_fonts = !!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;
+ if (!list)
+ return;
- for (p = list, cnt = 0; *p; ++p, ++cnt) {}
+ 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;
+ 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_t* priv, char* name, char* data, int size)
+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));
+ 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].name = strdup(name);
- priv->fontdata[idx].data = malloc(size);
- memcpy(priv->fontdata[idx].data, data, size);
+ priv->fontdata[idx].data = malloc(size);
+ memcpy(priv->fontdata[idx].data, data, size);
- priv->fontdata[idx].size = size;
+ priv->fontdata[idx].size = size;
- priv->num_fontdata ++;
+ priv->num_fontdata++;
}
-void ass_clear_fonts(ass_library_t* priv)
+void ass_clear_fonts(ASS_Library *priv)
{
- 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;
+ 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;
+}
+
+/*
+ * 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)
+{
+ if (msg_cb) {
+ priv->msg_callback = msg_cb;
+ priv->msg_callback_data = data;
+ }
}
Modified: trunk/libass/ass_library.h
==============================================================================
--- trunk/libass/ass_library.h Fri Jan 8 19:19:10 2010 (r30241)
+++ trunk/libass/ass_library.h Fri Jan 8 19:35:44 2010 (r30242)
@@ -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 at 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>
-struct ass_library_s {
- char* fonts_dir;
- int extract_fonts;
- char** style_overrides;
+typedef struct {
+ char *name;
+ char *data;
+ int size;
+} ASS_Fontdata;
- ass_fontdata_t* fontdata;
- int num_fontdata;
+struct ass_library {
+ char *fonts_dir;
+ int extract_fonts;
+ char **style_overrides;
+
+ 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 */
Added: trunk/libass/ass_parse.c
==============================================================================
--- /dev/null 00:00:00 1970 (empty, because file is newly added)
+++ trunk/libass/ass_parse.c Fri Jan 8 19:35:44 2010 (r30242)
@@ -0,0 +1,926 @@
+/*
+ * Copyright (C) 2009 Grigori Goronzy <greg at 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;
+}
Added: trunk/libass/ass_parse.h
==============================================================================
--- /dev/null 00:00:00 1970 (empty, because file is newly added)
+++ trunk/libass/ass_parse.h Fri Jan 8 19:35:44 2010 (r30242)
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2009 Grigori Goronzy <greg at 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 */
Modified: trunk/libass/ass_render.c
==============================================================================
--- trunk/libass/ass_render.c Fri Jan 8 19:19:10 2010 (r30241)
+++ trunk/libass/ass_render.c Fri Jan 8 19:35:44 2010 (r30242)
@@ -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 at 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"
+#include "ass_drawing.h"
+#include "ass_render.h"
+#include "ass_parse.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;
-
- 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;
-
-// 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;
+#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
-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;
+ 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);
+ }
+ }
+}
-// 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_Renderer *ass_renderer_init(ASS_Library *library)
+{
+ int error;
+ FT_Library ft;
+ ASS_Renderer *priv = 0;
+ int vmajor, vminor, vpatch;
- ass_font_t* font;
- char* font_path;
- double font_size;
+ error = FT_Init_FreeType(&ft);
+ if (error) {
+ ass_msg(library, MSGL_FATAL, "%s failed", "FT_Init_FreeType");
+ goto ass_init_exit;
+ }
- 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
+ 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);
- effect_t effect_type;
- int effect_timing;
- int effect_skip_timing;
+ priv = calloc(1, sizeof(ASS_Renderer));
+ if (!priv) {
+ FT_Done_FreeType(ft);
+ goto ass_init_exit;
+ }
- 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;
+ priv->synth_priv = ass_synth_init(BLUR_MAX_RADIUS);
- // face properties
- char* family;
- unsigned bold;
- unsigned italic;
- int treat_family_as_pattern;
+ priv->library = library;
+ priv->ftlibrary = ft;
+ // images_root and related stuff is zero-filled in calloc
-} render_context_t;
+ 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;
-// 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;
+ 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));
-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;
+ ass_init_exit:
+ if (priv)
+ ass_msg(library, MSGL_INFO, "Init");
+ else
+ ass_msg(library, MSGL_ERR, "Init failed");
-struct render_priv_s {
- int top, height;
- int render_id;
-};
+ return priv;
+}
-static void ass_lazy_track_init(void)
+void ass_set_cache_limits(ASS_Renderer *render_priv, int glyph_max,
+ int bitmap_max)
{
- 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);
- }
- }
+ 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;
}
-ass_renderer_t* ass_renderer_init(ass_library_t* library)
+static void free_list_clear(ASS_Renderer *render_priv)
{
- 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));
+ 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;
+ }
+}
- error = FT_Init_FreeType( &ft );
- if ( error ) {
- mp_msg(MSGT_ASS, MSGL_FATAL, MSGTR_LIBASS_FT_Init_FreeTypeFailed);
- goto ass_init_exit;
- }
+static void ass_free_images(ASS_Image *img);
- 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);
+void ass_renderer_done(ASS_Renderer *render_priv)
+{
+ 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);
- priv = calloc(1, sizeof(ass_renderer_t));
- if (!priv) {
- FT_Done_FreeType(ft);
- goto ass_init_exit;
- }
+ ass_free_images(render_priv->images_root);
+ ass_free_images(render_priv->prev_images_root);
- priv->synth_priv = ass_synth_init(BLUR_MAX_RADIUS);
+ 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);
- priv->library = library;
- priv->ftlibrary = ft;
- // images_root and related stuff is zero-filled in calloc
+ free(render_priv->settings.default_font);
+ free(render_priv->settings.default_family);
- ass_font_cache_init();
- ass_bitmap_cache_init();
- ass_composite_cache_init();
- ass_glyph_cache_init();
+ free_list_clear(render_priv);
+ free(render_priv);
+}
- text_info.glyphs = calloc(MAX_GLYPHS, sizeof(glyph_info_t));
+/**
+ * \brief Create a new ASS_Image
+ * Parameters are the same as ASS_Image fields.
+ */
+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 *img = calloc(1, sizeof(ASS_Image));
-ass_init_exit:
- if (priv) mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_Init);
- else mp_msg(MSGT_ASS, MSGL_ERR, MSGTR_LIBASS_InitFailed);
+ 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 priv;
+ return img;
}
-void ass_renderer_done(ass_renderer_t* 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);
-}
+static double x2scr_pos(ASS_Renderer *render_priv, double x);
+static double y2scr_pos(ASS_Renderer *render_priv, double y);
-/**
- * \brief Create a new ass_image_t
- * Parameters are the same as ass_image_t fields.
+/*
+ * \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_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 **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)
{
- ass_image_t* img = calloc(1, sizeof(ass_image_t));
+ int i, j, x0, y0, x1, y1, cx0, cy0, cx1, cy1, sx, sy, zx, zy;
+ Rect r[4];
+ ASS_Image *img;
- assert(dst_x >= 0);
- assert(dst_y >= 0);
- assert(dst_x + bitmap_w <= frame_context.width);
- assert(dst_y + bitmap_h <= frame_context.height);
+ dst_x += bm->left;
+ dst_y += bm->top;
- 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;
+ // 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);
- return img;
+ 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++;
+
+ // 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;
+ }
+
+ // 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 @@ static ass_image_t* my_draw_bitmap(unsig
* \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;
- 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;
+ if (!drawing)
+ return;
- 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;
+ // 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;
- 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;
- }
+ assert(clip_bm->bitmap.pitch >= 0);
- 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;
+ // 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;
- pen_x = dst_x + info->pos.x;
- pen_y = dst_y + info->pos.y;
- bm = info->bm_o;
+ 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;
- 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;
+ // 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;
- pen_x = dst_x + info->pos.x;
- pen_y = dst_y + info->pos.y;
- bm = info->bm;
+ if (render_priv->state.clip_drawing_mode) {
+ // Inverse clip
+ if (ax + aw < bx || ay + ah < by || ax > bx + bw ||
+ ay > by + bh) {
+ continue;
+ }
- 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);
- }
+ // Allocate new buffer and add to free list
+ nbuffer = malloc(as * ah);
+ free_list_add(render_priv, nbuffer);
- *tail = 0;
- return head;
-}
+ // 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;
+ }
-/**
- * \brief Mapping between script and screen coordinates
- */
-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;
+ // Allocate new buffer and add to free list
+ nbuffer = calloc(as, ah);
+ free_list_add(render_priv, nbuffer);
+
+ // 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;
+ }
+
+ // 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 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;
-}
+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;
-// 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);
-}
+ 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;
-static void compute_string_bbox( text_info_t* info, FT_BBox *abbox ) {
- FT_BBox bbox;
- int i;
+ 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;
- 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;
+ 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);
- 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;
+ last_tail = here_tail;
+ }
- /* return string bbox */
- *abbox = bbox;
-}
+ 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;
-/**
- * \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;
-}
+ 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);
-static void change_font_size(double sz)
-{
- double size = sz * frame_context.font_scale;
+ last_tail = here_tail;
+ }
+ }
- if (size < 1)
- size = 1;
- else if (size > frame_context.height * 2)
- size = frame_context.height * 2;
+ 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;
- ass_font_set_size(render_context.font, size);
+ pen_x = dst_x + (info->pos.x >> 6);
+ pen_y = dst_y + (info->pos.y >> 6);
+ bm = info->bm;
- render_context.font_size = sz;
+ 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 Change current font, using setting from render_context.
+ * \brief Mapping between script and screen coordinates
*/
-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);
+}
+static double x2scr_pos(ASS_Renderer *render_priv, double x)
+{
+ return x * render_priv->orig_width / render_priv->track->PlayResX +
+ render_priv->settings.left_margin;
}
/**
- * \brief Change border width
- * negative value resets border to style value
+ * \brief Mapping between script and screen coordinates
*/
-static void change_border(double border)
+static double y2scr(ASS_Renderer *render_priv, double y)
{
- 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;
- }
+ 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)
+{
+ return y * render_priv->orig_height / render_priv->track->PlayResY +
+ render_priv->settings.top_margin;
}
-#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)
+// the same for toptitles
+static double y2scr_top(ASS_Renderer *render_priv, double y)
{
- (*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);
+ 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);
}
-// like change_color, but for alpha component only
-static void change_alpha(uint32_t* var, uint32_t new, double pwr)
+// the same for subtitles
+static double y2scr_sub(ASS_Renderer *render_priv, double y)
{
- *var = (_r(*var) << 24) + (_g(*var) << 16) + (_b(*var) << 8) + (_a(*var) * (1 - pwr) + _a(new) * pwr);
+ 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 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)
+static void compute_string_bbox(TextInfo *info, DBBox *bbox)
{
- return 0xFF - (0xFF - a) * (0xFF - b) / 0xFF;
+ int i;
+
+ 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);
+
+ 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 Calculate alpha value by piecewise linear function
- * Used for \fad, \fade implementation.
+ * \brief partially reset render_context to style values
+ * Works like {\r}: resets some style overrides
*/
-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)
+void reset_render_context(ASS_Renderer *render_priv)
{
- 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;
- }
+ 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;
- return a;
-}
+ 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);
-static void reset_render_context(void);
+ 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 Parse style override tag.
- * \param p string to parse
- * \param pwr multiplier for some tag effects (comes from \t tags)
+ * \brief Start new event. Reset render_priv->state.
*/
-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;
+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;
- // 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;
- }
+ reset_render_context(render_priv);
- return p;
+ 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);
-#undef skip
-#undef skip_to
+ apply_transition_effects(render_priv, event);
}
-/**
- * \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 free_render_context(ASS_Renderer *render_priv)
{
- 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;
+ free(render_priv->state.family);
+ ass_drawing_free(render_priv->state.drawing);
+
+ render_priv->state.family = NULL;
+ render_priv->state.drawing = NULL;
}
-static void apply_transition_effects(ass_event_t* event)
+// Calculate the cbox of a series of points
+static void
+get_contour_cbox(FT_BBox *box, FT_Vector *points, int start, int end)
{
- int v[4];
- int cnt;
- char* p = event->Effect;
+ box->xMin = box->yMin = INT_MAX;
+ box->xMax = box->yMax = INT_MIN;
+ int i;
- if (!p || !*p) return;
+ 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;
+ }
+}
- cnt = 0;
- while (cnt < 4 && (p = strchr(p, ';'))) {
- v[cnt++] = atoi(++p);
- }
+/**
+ * \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)
+{
+ 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;
- 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;
+ // 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);
+ }
- 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 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;
+ }
+ }
+ }
+ }
- 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;
- }
+ // 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);
}
-/**
- * \brief partially reset render_context to style values
- * Works like {\r}: resets some style overrides
+/*
+ * Replace the outline of a glyph by a contour which makes up a simple
+ * opaque rectangle.
*/
-static void reset_render_context(void)
+static void draw_opaque_box(ASS_Renderer *render_priv, uint32_t ch,
+ FT_Glyph glyph, int sx, int sy)
{
- 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;
+ 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;
- 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();
+ // to avoid gaps
+ sx = FFMAX(64, sx);
+ sy = FFMAX(64, sy);
- 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.;
+ 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;
+ }
- // FIXME: does not reset unsupported attributes.
+ // 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;
}
-/**
- * \brief Start new event. Reset render_context.
+/*
+ * Stroke an outline glyph in x/y direction. Applies various fixups to get
+ * around limitations of the FreeType stroker.
*/
-static void init_render_context(ass_event_t* event)
+static void stroke_outline_glyph(ASS_Renderer *render_priv,
+ FT_OutlineGlyph *glyph, int sx, int sy)
{
- render_context.event = event;
- render_context.style = frame_context.track->styles + event->Style;
+ if (sx <= 0 && sy <= 0)
+ return;
- reset_render_context();
+ fix_freetype_stroker(*glyph, sx, sy);
- 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;
+ // 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);
- apply_transition_effects(event);
-}
+ // "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);
-static void free_render_context(void)
-{
+ 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 @@ static void transform_3d(FT_Vector shift
* 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(key);
-/* val = 0; */
+ val = cache_find_bitmap(render_priv->cache.bitmap_cache, key);
- 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);
+ 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);
- // 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;
+ // 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);
- // 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);
+ // 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(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,31 +1275,100 @@ static void get_bitmap_glyph(glyph_info_
* lines[].asc
* lines[].desc
*/
-static void measure_text(void)
+static void measure_text(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);
+ 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 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
* \param max_text_width maximal text line width in pixels
* The algo is similar to the one in libvo/sub.c:
@@ -1590,132 +1376,167 @@ static void measure_text(void)
* 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;
+ 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);
+ 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));
- if (DIFF(l1_new, l2_new) < DIFF(l1, l2)) {
- w->linebreak = 1;
- s2->linebreak = 0;
- exit = 0;
- }
- }
- }
- if (i == text_info.length)
- break;
- }
+ 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);
+ }
+ 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;
- 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;
- }
+ 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);
+
+ 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 @@ static void wrap_lines_smart(int max_tex
* 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 @@ static void process_karaoke_effects(void
* \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 @@ static void get_base_point(FT_BBox bbox,
* 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;
+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;
- for (i=0; i<outline->n_points; i++) {
- x = p[i].x + shift.x;
- y = p[i].y + shift.y;
- z = 0.;
+ 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.;
- xx = x*cz + y*sz;
- yy = -(x*sz - y*cz);
- zz = z;
+ xx = x * cz + y * sz;
+ yy = -(x * sz - y * cz);
+ zz = z;
- x = xx;
- y = yy*cx + zz*sx;
- z = yy*sx - zz*cx;
+ x = xx;
+ y = yy * cx + zz * sx;
+ z = yy * sx - zz * cx;
- xx = x*cy + z*sy;
- yy = y;
- zz = x*sy - z*cy;
+ xx = x * cy + z * sy;
+ yy = y;
+ zz = x * sy - z * cy;
- zz = FFMAX(zz, -19000);
+ zz = FFMAX(zz, 1000 - dist);
- 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;
- }
+ 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 @@ static void transform_3d_points(FT_Vecto
* \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 @@ static void transform_3d(FT_Vector shift
* \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;
-
- 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;
- }
-
- init_render_context(event);
+ 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;
- 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
+ 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;
+ }
- // face could have been changed in get_next_char
- if (!render_context.font) {
- free_render_context();
- return 1;
- }
+ init_render_context(render_priv, event);
- if (code == 0)
- break;
+ 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
- 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;
- }
+ // 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 ( 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;
- }
+ // face could have been changed in get_next_char
+ if (!render_priv->state.font) {
+ free_render_context(render_priv);
+ return 1;
+ }
- shift.x = pen.x & SUBPIXEL_MASK;
- shift.y = pen.y & SUBPIXEL_MASK;
+ if (code == 0)
+ break;
- 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;
- }
+ 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);
+ }
- ass_font_set_transform(render_context.font,
- render_context.scale_x * frame_context.font_scale_x,
- render_context.scale_y,
- &shift );
+ // 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;
+ }
- get_outline_glyph(code, text_info.glyphs + text_info.length, &shift);
+ ass_font_set_transform(render_priv->state.font,
+ render_priv->state.scale_x *
+ render_priv->font_scale_x,
+ render_priv->state.scale_y, NULL);
- text_info.glyphs[text_info.length].pos.x = pen.x >> 6;
- text_info.glyphs[text_info.length].pos.y = pen.y >> 6;
+ get_outline_glyph(render_priv, code,
+ text_info->glyphs + text_info->length, drawing);
- 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;
+ // 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;
+ }
+ }
- previous = code;
+ text_info->glyphs[text_info->length].pos.x = pen.x;
+ text_info->glyphs[text_info->length].pos.y = pen.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_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;
+ 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;
- // 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;
+ previous = code;
- text_info.length++;
+ 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);
- render_context.effect_type = EF_NONE;
- render_context.effect_timing = 0;
- render_context.effect_skip_timing = 0;
- }
+ text_info->glyphs[text_info->length].asc *=
+ render_priv->state.scale_y;
+ text_info->glyphs[text_info->length].desc *=
+ render_priv->state.scale_y;
+ }
- 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;
+ render_priv->state.effect_type = EF_NONE;
+ render_priv->state.effect_timing = 0;
+ render_priv->state.effect_skip_timing = 0;
- 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 (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);
+ }
+ }
- if (render_context.evt_type != EVENT_HSCROLL) {
- int max_text_width;
- // calculate max length of a line
- max_text_width = x2scr(frame_context.track->PlayResX - MarginR) - x2scr(MarginL);
+ 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);
- // rearrange text in several lines
- wrap_lines_smart(max_text_width);
+ // alignments
+ alignment = render_priv->state.alignment;
+ halign = alignment & 3;
+ valign = alignment & 12;
- // 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;
+ 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;
- while ((last_glyph > first_glyph) && ((last_glyph->symbol == '\n') || (last_glyph->symbol == 0)))
- last_glyph --;
+ if (render_priv->state.evt_type != EVENT_HSCROLL) {
+ double max_text_width;
- 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();
- }
+ // 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);
+ // rearrange text in several lines
+ wrap_lines_smart(render_priv, max_text_width);
- // determine device coordinates for text
+ // 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;
- // 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);
- }
+ while (first_glyph < last_glyph && first_glyph->skip)
+ first_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);
- }
+ while ((last_glyph > first_glyph)
+ && ((last_glyph->symbol == '\n')
+ || (last_glyph->symbol == 0)
+ || (last_glyph->skip)))
+ last_glyph--;
- // 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;
- }
+ 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);
+ }
- // 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);
- }
+ // determing text bounding box
+ compute_string_bbox(text_info, &bbox);
- 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);
+ // determine device coordinates for text
- // calculate rotation parameters
- {
- FT_Vector center;
+ // 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;
- 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;
- }
+ 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) {
- glyph_info_t* info = text_info.glyphs + i;
+ for (i = 0; i < text_info->length; ++i) {
+ GlyphInfo *info = text_info->glyphs + i;
- 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;
- }
- }
- }
+ 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;
+ }
+ }
+ }
- // convert glyphs to bitmaps
- for (i = 0; i < text_info.length; ++i)
- get_bitmap_glyph(text_info.glyphs + i);
+ // 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);
+ }
- 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);
+ 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);
- free_render_context();
+ free_render_context(render_priv);
- 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);
- }
+ 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)
+void ass_set_use_margins(ASS_Renderer *priv, int use)
{
- priv->settings.use_margins = use;
+ priv->settings.use_margins = use;
}
-void ass_set_aspect_ratio(ass_renderer_t* priv, double ar)
+void ass_set_aspect_ratio(ASS_Renderer *priv, double dar, double sar)
{
- if (priv->settings.aspect != ar) {
- priv->settings.aspect = ar;
- 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_font_scale(ass_renderer_t* priv, double font_scale)
+void ass_set_font_scale(ASS_Renderer *priv, double font_scale)
{
- if (priv->settings.font_size_coeff != font_scale) {
- priv->settings.font_size_coeff = font_scale;
- ass_reconfigure(priv);
- }
+ if (priv->settings.font_size_coeff != font_scale) {
+ priv->settings.font_size_coeff = font_scale;
+ ass_reconfigure(priv);
+ }
}
-void ass_set_hinting(ass_renderer_t* priv, ass_hinting_t ht)
+void ass_set_hinting(ASS_Renderer *priv, ASS_Hinting ht)
{
- if (priv->settings.hinting != ht) {
- priv->settings.hinting = ht;
- ass_reconfigure(priv);
- }
+ if (priv->settings.hinting != ht) {
+ priv->settings.hinting = ht;
+ ass_reconfigure(priv);
+ }
}
-void ass_set_line_spacing(ass_renderer_t* priv, double line_spacing)
+void ass_set_line_spacing(ASS_Renderer *priv, double line_spacing)
{
- priv->settings.line_spacing = 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);
-
- 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;
-}
+ 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;
-int ass_set_fonts(ass_renderer_t* priv, const char* default_font, const char* default_family)
-{
- return ass_set_fonts_(priv, default_font, default_family, 1);
+ 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_nofc(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, 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 (!priv->settings.frame_width && !priv->settings.frame_height)
- return 1; // library not initialized
+ if (!render_priv->settings.frame_width
+ && !render_priv->settings.frame_height)
+ return 1; // library not initialized
- if (track->n_events == 0)
- return 1; // nothing to do
+ if (render_priv->library != track->library)
+ return 1;
- 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;
+ free_list_clear(render_priv);
- ass_lazy_track_init();
+ if (track->n_events == 0)
+ return 1; // nothing to do
- 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.;
+ 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.font_scale_x = 1.;
+ ass_lazy_track_init(render_priv);
- priv->prev_images_root = priv->images_root;
- priv->images_root = 0;
+ 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.;
- return 0;
+ // 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;
+
+ 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;
+ }
+
+ 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);
+ }
+
+ 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;
+ }
-typedef struct segment_s {
- int a, b; // top and height
-} segment_t;
+ return event->render_priv;
+}
-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 @@ static void fix_collisions(event_images_
* \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 @@ int ass_image_compare(ass_image_t *i1, a
* \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 @@ int ass_detect_change(ass_renderer_t *pr
* 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;
- }
- }
+ // 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;
+ }
+ }
- // sort by layer
- qsort(priv->eimg, cnt, sizeof(event_images_t), cmp_event_layer);
+ // 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(last, priv->eimg + i - last);
- last = priv->eimg + i;
- }
- if (cnt > 0)
- fix_collisions(last, priv->eimg + cnt - last);
+ // 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;
}
-
Added: trunk/libass/ass_render.h
==============================================================================
--- /dev/null 00:00:00 1970 (empty, because file is newly added)
+++ trunk/libass/ass_render.h Fri Jan 8 19:35:44 2010 (r30242)
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov at gmail.com>
+ * Copyright (C) 2009 Grigori Goronzy <greg at 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 */
Added: trunk/libass/ass_strtod.c
==============================================================================
--- /dev/null 00:00:00 1970 (empty, because file is newly added)
+++ trunk/libass/ass_strtod.c Fri Jan 8 19:35:44 2010 (r30242)
@@ -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;
+}
Modified: trunk/libass/ass_types.h
==============================================================================
--- trunk/libass/ass_types.h Fri Jan 8 19:19:10 2010 (r30241)
+++ trunk/libass/ass_types.h Fri Jan 8 19:35:44 2010 (r30242)
@@ -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 at 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;
-
-typedef struct render_priv_s render_priv_t;
-
-/// 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
-
- int ReadOrder;
- int Layer;
- int Style;
- char* Name;
- int MarginL;
- int MarginR;
- int MarginV;
- char* Effect;
- char* Text;
+/* 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;
- render_priv_t* render_priv;
-} ass_event_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;
-typedef struct parser_priv_s parser_priv_t;
+/*
+ * 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
-typedef struct ass_library_s ass_library_t;
+ int ReadOrder;
+ int Layer;
+ int Style;
+ char *Name;
+ int MarginL;
+ int MarginR;
+ int MarginV;
+ char *Effect;
+ char *Text;
-/// 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_RenderPriv *render_priv;
+} ASS_Event;
- char* style_format; // style format line (everything after "Format: ")
- char* event_format; // event format line
+/*
+ * 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
- 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;
+ // Script header fields
+ int PlayResX;
+ int PlayResY;
+ double Timer;
+ int WrapStyle;
+ int ScaledBorderAndShadow;
+ int Kerning;
- int default_style; // index of default style
- char* name; // file name in case of external subs, 0 for streams
+ int default_style; // index of default style
+ char *name; // file name in case of external subs, 0 for streams
- ass_library_t* library;
- parser_priv_t* parser_priv;
-} ass_track_t;
+ ASS_Library *library;
+ ASS_ParserPriv *parser_priv;
+} ASS_Track;
#endif /* LIBASS_TYPES_H */
Modified: trunk/libass/ass_utils.c
==============================================================================
--- trunk/libass/ass_utils.c Fri Jan 8 19:19:10 2010 (r30241)
+++ trunk/libass/ass_utils.c Fri Jan 8 19:35:44 2010 (r30242)
@@ -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 at 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;
}
-#if 0
-static void sprint_tag(uint32_t tag, char* dst)
+void ass_msg(ASS_Library *priv, int lvl, char *fmt, ...)
{
- dst[0] = (tag >> 24) & 0xFF;
- dst[1] = (tag >> 16) & 0xFF;
- dst[2] = (tag >> 8) & 0xFF;
- dst[3] = tag & 0xFF;
- dst[4] = 0;
+ va_list va;
+ va_start(va, fmt);
+ priv->msg_callback(lvl, fmt, va, priv->msg_callback_data);
+ va_end(va);
}
-void dump_glyph(FT_Glyph g)
+unsigned ass_utf8_get_char(char **str)
{
- 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));
- }
+ 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;
+}
+
+#ifdef CONFIG_ENCA
+void *ass_guess_buffer_cp(ASS_Library *library, unsigned char *buffer,
+ int buflen, char *preferred_language,
+ char *fallback)
+{
+ 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
Modified: trunk/libass/ass_utils.h
==============================================================================
--- trunk/libass/ass_utils.h Fri Jan 8 19:19:10 2010 (r30241)
+++ trunk/libass/ass_utils.h Fri Jan 8 19:35:44 2010 (r30242)
@@ -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 at 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
-static inline int d6_to_int(int x) {
- return (x + 32) >> 6;
+#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
+
+#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 d16_to_int(int x) {
- return (x + 32768) >> 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 int_to_d6(int x)
+{
+ return x << 6;
}
-static inline int int_to_d16(int x) {
- return x << 16;
+static inline int int_to_d16(int x)
+{
+ return x << 16;
}
-static inline int d16_to_d6(int x) {
- return (x + 512) >> 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 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 */
More information about the MPlayer-cvslog
mailing list