diff --git a/aegisub/libass/ass_parse.c b/aegisub/libass/ass_parse.c new file mode 100644 index 000000000..0f3b4414a --- /dev/null +++ b/aegisub/libass/ass_parse.c @@ -0,0 +1,857 @@ +/* + * Copyright (C) 2009 Grigori Goronzy + * + * 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 +#include +#include +#include + +#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; +#if (FREETYPE_MAJOR > 2) || ((FREETYPE_MAJOR == 2) && (FREETYPE_MINOR > 1)) + error = + FT_Stroker_New(render_priv->ftlibrary, + &render_priv->state.stroker); +#else // < 2.2 + error = + FT_Stroker_New(render_priv->state.font->faces[0]-> + memory, &render_priv->state.stroker); +#endif + 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) + + (_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(')'); + 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; +} + +/** + * \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; +} diff --git a/aegisub/libass/ass_parse.h b/aegisub/libass/ass_parse.h new file mode 100644 index 000000000..e4b37487f --- /dev/null +++ b/aegisub/libass/ass_parse.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2009 Grigori Goronzy + * + * 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); +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 */ diff --git a/aegisub/libass/ass_render.h b/aegisub/libass/ass_render.h new file mode 100644 index 000000000..6d9db23fb --- /dev/null +++ b/aegisub/libass/ass_render.h @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2006 Evgeniy Stepanov + * Copyright (C) 2009 Grigori Goronzy + * + * 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 +#include +#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 */