[SCM] LibASS packaging branch, master, updated. debian/0.9.7-2-5-gee7a496
xtophe-guest at users.alioth.debian.org
xtophe-guest at users.alioth.debian.org
Mon Oct 19 10:19:11 UTC 2009
The following commit has been merged in the master branch:
commit c1cec6fdcd4da9a4095f67c142096e45713aac2b
Author: Christophe Mutricy <xtophe at videolan.org>
Date: Mon Oct 19 12:10:11 2009 +0200
Imported Upstream version 0.9.8
diff --git a/Changelog b/Changelog
index aeb100d..ca1ba53 100644
--- a/Changelog
+++ b/Changelog
@@ -1,3 +1,18 @@
+libass (0.9.8)
+ * Support \q override tag
+ * Support wrap style 1 (i.e. wrap, but do not equalize line lengths)
+ * Support border style 3 (opaque box)
+ * Use the event bounding box (instead of vertical position and height) for
+ collision detection
+ * Embold glyphs if no bold variant is available, but was requested
+ * Modify \fax to be similar to VSFilter
+ * Trim spaces after line wrapping
+ * Fix border/shadow overlap combining in some cases
+ * Disable kerning by default. Use "Kerning=yes" style override or
+ "Kerning: yes" in [Script Info] to enable it
+ * Slight bitmap handling optimizations
+ * Various bugfixes
+
libass (0.9.7)
* Build system fixes
* Fixed cache lookup and overload problems
diff --git a/configure b/configure
index 32fe64d..4e096d0 100755
--- a/configure
+++ b/configure
@@ -1,6 +1,6 @@
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.61 for libass 0.9.7.
+# Generated by GNU Autoconf 2.61 for libass 0.9.8.
#
# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
# 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc.
@@ -722,8 +722,8 @@ SHELL=${CONFIG_SHELL-/bin/sh}
# Identity of this package.
PACKAGE_NAME='libass'
PACKAGE_TARNAME='libass'
-PACKAGE_VERSION='0.9.7'
-PACKAGE_STRING='libass 0.9.7'
+PACKAGE_VERSION='0.9.8'
+PACKAGE_STRING='libass 0.9.8'
PACKAGE_BUGREPORT=''
# Factoring default headers for most tests.
@@ -1402,7 +1402,7 @@ if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
-\`configure' configures libass 0.9.7 to adapt to many kinds of systems.
+\`configure' configures libass 0.9.8 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@@ -1472,7 +1472,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
- short | recursive ) echo "Configuration of libass 0.9.7:";;
+ short | recursive ) echo "Configuration of libass 0.9.8:";;
esac
cat <<\_ACEOF
@@ -1486,7 +1486,7 @@ Optional Features:
--disable-dependency-tracking speeds up one-time build
--enable-dependency-tracking do not reject slow dependency extractors
--disable-libtool-lock avoid locking (might break parallel builds)
- --disable-png disable png support [default=check]
+ --enable-png enable png (test program) [default=no]
--disable-enca disable enca (charset autodetect) support
[default=check]
--disable-fontconfig disable fontconfig support [default=check]
@@ -1587,7 +1587,7 @@ fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
-libass configure 0.9.7
+libass configure 0.9.8
generated by GNU Autoconf 2.61
Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
@@ -1601,7 +1601,7 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
-It was created by libass $as_me 0.9.7, which was
+It was created by libass $as_me 0.9.8, which was
generated by GNU Autoconf 2.61. Invocation command line was
$ $0 $@
@@ -2291,7 +2291,7 @@ fi
# Define the identity of the package.
PACKAGE='libass'
- VERSION='0.9.7'
+ VERSION='0.9.8'
cat >>confdefs.h <<_ACEOF
@@ -16009,7 +16009,8 @@ _ACEOF
fi
fi
-if test x$enable_png != xno; then
+libpng=false
+if test x$enable_png = xyes; then
pkg_failed=no
{ echo "$as_me:$LINENO: checking for LIBPNG" >&5
@@ -16069,11 +16070,51 @@ fi
# Put the nasty error message in config.log where it belongs
echo "$LIBPNG_PKG_ERRORS" >&5
- { echo "$as_me:$LINENO: result: no" >&5
-echo "${ECHO_T}no" >&6; }
- libpng=false
+ { { echo "$as_me:$LINENO: error: Package requirements (libpng >= 1.2.0) were not met:
+
+$LIBPNG_PKG_ERRORS
+
+Consider adjusting the PKG_CONFIG_PATH environment variable if you
+installed software in a non-standard prefix.
+
+Alternatively, you may set the environment variables LIBPNG_CFLAGS
+and LIBPNG_LIBS to avoid the need to call pkg-config.
+See the pkg-config man page for more details.
+" >&5
+echo "$as_me: error: Package requirements (libpng >= 1.2.0) were not met:
+
+$LIBPNG_PKG_ERRORS
+
+Consider adjusting the PKG_CONFIG_PATH environment variable if you
+installed software in a non-standard prefix.
+
+Alternatively, you may set the environment variables LIBPNG_CFLAGS
+and LIBPNG_LIBS to avoid the need to call pkg-config.
+See the pkg-config man page for more details.
+" >&2;}
+ { (exit 1); exit 1; }; }
elif test $pkg_failed = untried; then
- libpng=false
+ { { echo "$as_me:$LINENO: error: The pkg-config script could not be found or is too old. Make sure it
+is in your PATH or set the PKG_CONFIG environment variable to the full
+path to pkg-config.
+
+Alternatively, you may set the environment variables LIBPNG_CFLAGS
+and LIBPNG_LIBS to avoid the need to call pkg-config.
+See the pkg-config man page for more details.
+
+To get pkg-config, see <http://pkg-config.freedesktop.org/>.
+See \`config.log' for more details." >&5
+echo "$as_me: error: The pkg-config script could not be found or is too old. Make sure it
+is in your PATH or set the PKG_CONFIG environment variable to the full
+path to pkg-config.
+
+Alternatively, you may set the environment variables LIBPNG_CFLAGS
+and LIBPNG_LIBS to avoid the need to call pkg-config.
+See the pkg-config man page for more details.
+
+To get pkg-config, see <http://pkg-config.freedesktop.org/>.
+See \`config.log' for more details." >&2;}
+ { (exit 1); exit 1; }; }
else
LIBPNG_CFLAGS=$pkg_cv_LIBPNG_CFLAGS
LIBPNG_LIBS=$pkg_cv_LIBPNG_LIBS
@@ -16633,7 +16674,7 @@ exec 6>&1
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
-This file was extended by libass $as_me 0.9.7, which was
+This file was extended by libass $as_me 0.9.8, which was
generated by GNU Autoconf 2.61. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@@ -16686,7 +16727,7 @@ Report bugs to <bug-autoconf at gnu.org>."
_ACEOF
cat >>$CONFIG_STATUS <<_ACEOF
ac_cs_version="\\
-libass config.status 0.9.7
+libass config.status 0.9.8
configured by $0, generated by GNU Autoconf 2.61,
with options \\"`echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`\\"
diff --git a/configure.ac b/configure.ac
index 03aa72e..7698492 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,4 +1,4 @@
-AC_INIT(libass, 0.9.7)
+AC_INIT(libass, 0.9.8)
AM_INIT_AUTOMAKE
AC_CONFIG_MACRO_DIR([shave])
# Disable C++/Fortran checks
@@ -34,8 +34,8 @@ AC_SEARCH_LIBS([iconv_open], [iconv], AC_DEFINE(CONFIG_ICONV, 1, [use iconv]))
AC_CHECK_LIB([m], [fabs])
# Check for libraries via pkg-config
-AC_ARG_ENABLE([png], AS_HELP_STRING([--disable-png],
- [disable png support @<:@default=check@:>@]))
+AC_ARG_ENABLE([png], AS_HELP_STRING([--enable-png],
+ [enable png (test program) @<:@default=no@:>@]))
AC_ARG_ENABLE([enca], AS_HELP_STRING([--disable-enca],
[disable enca (charset autodetect) support @<:@default=check@:>@]))
AC_ARG_ENABLE([fontconfig], AS_HELP_STRING([--disable-fontconfig],
@@ -65,13 +65,14 @@ PKG_CHECK_MODULES([ENCA], enca, [
], [enca=false])
fi
-if test x$enable_png != xno; then
+libpng=false
+if test x$enable_png = xyes; then
PKG_CHECK_MODULES([LIBPNG], libpng >= 1.2.0, [
CFLAGS="$CFLAGS $LIBPNG_CFLAGS"
LIBS="$LIBS $LIBPNG_LIBS"
AC_DEFINE(CONFIG_LIBPNG, 1, [found libpng via pkg-config])
libpng=true
- ], [libpng=false])
+ ])
fi
AM_CONDITIONAL([HAVE_LIBPNG], [test x$libpng = xtrue])
diff --git a/libass/Makefile.am b/libass/Makefile.am
index fd818ae..0cf613e 100644
--- a/libass/Makefile.am
+++ b/libass/Makefile.am
@@ -7,7 +7,8 @@ libass_la_SOURCES = ass.c ass_cache.c ass_font.c ass_fontconfig.c ass_render.c \
ass_utils.c ass_bitmap.c ass_library.c ass_bitmap.h \
ass_cache.h ass_fontconfig.h ass_font.h ass.h \
ass_library.h ass_types.h ass_utils.h ass_drawing.c \
- ass_drawing.h ass_cache_template.h
+ ass_drawing.h ass_cache_template.h ass_render.h \
+ ass_parse.c ass_parse.h
libass_la_LDFLAGS = -version-info $(LIBASS_LT_CURRENT):$(LIBASS_LT_REVISION):$(LIBASS_LT_AGE)
libass_la_LDFLAGS += -export-symbols $(srcdir)/libass.sym
diff --git a/libass/Makefile.in b/libass/Makefile.in
index 1b54bb0..a162f3c 100644
--- a/libass/Makefile.in
+++ b/libass/Makefile.in
@@ -56,7 +56,7 @@ LTLIBRARIES = $(lib_LTLIBRARIES)
libass_la_LIBADD =
am_libass_la_OBJECTS = ass.lo ass_cache.lo ass_font.lo \
ass_fontconfig.lo ass_render.lo ass_utils.lo ass_bitmap.lo \
- ass_library.lo ass_drawing.lo
+ ass_library.lo ass_drawing.lo ass_parse.lo
libass_la_OBJECTS = $(am_libass_la_OBJECTS)
libass_la_LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) \
$(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
@@ -208,7 +208,8 @@ libass_la_SOURCES = ass.c ass_cache.c ass_font.c ass_fontconfig.c ass_render.c \
ass_utils.c ass_bitmap.c ass_library.c ass_bitmap.h \
ass_cache.h ass_fontconfig.h ass_font.h ass.h \
ass_library.h ass_types.h ass_utils.h ass_drawing.c \
- ass_drawing.h ass_cache_template.h
+ ass_drawing.h ass_cache_template.h ass_render.h \
+ ass_parse.c ass_parse.h
libass_la_LDFLAGS = -version-info \
$(LIBASS_LT_CURRENT):$(LIBASS_LT_REVISION):$(LIBASS_LT_AGE) \
@@ -292,6 +293,7 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/ass_font.Plo at am__quote@
@AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/ass_fontconfig.Plo at am__quote@
@AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/ass_library.Plo at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/ass_parse.Plo at am__quote@
@AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/ass_render.Plo at am__quote@
@AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/ass_utils.Plo at am__quote@
diff --git a/libass/ass.c b/libass/ass.c
index 6c28a97..f7f5bcc 100644
--- a/libass/ass.c
+++ b/libass/ass.c
@@ -198,7 +198,7 @@ static int lookup_style(ASS_Track *track, char *name)
static uint32_t string2color(ASS_Library *library, char *p)
{
uint32_t tmp;
- (void) strtocolor(library, &p, &tmp);
+ (void) strtocolor(library, &p, &tmp, 0);
return tmp;
}
@@ -389,6 +389,8 @@ void ass_process_force_style(ASS_Track *track)
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) {
@@ -571,6 +573,8 @@ static int process_info_line(ASS_Track *track, char *str)
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;
}
diff --git a/libass/ass.h b/libass/ass.h
index 908b199..3684b44 100644
--- a/libass/ass.h
+++ b/libass/ass.h
@@ -25,7 +25,7 @@
#include <stdarg.h>
#include "ass_types.h"
-#define LIBASS_VERSION 0x00907010
+#define LIBASS_VERSION 0x00908000
/*
* A linked list of images produced by an ass renderer.
diff --git a/libass/ass_bitmap.c b/libass/ass_bitmap.c
index faddcf3..c7c039d 100644
--- a/libass/ass_bitmap.c
+++ b/libass/ass_bitmap.c
@@ -230,12 +230,12 @@ static Bitmap *glyph_to_bitmap_internal(ASS_Library *library,
}
/**
- * \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 *fix_outline_and_shadow(Bitmap *bm_g, Bitmap *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;
@@ -247,30 +247,21 @@ static Bitmap *fix_outline_and_shadow(Bitmap *bm_g, Bitmap *bm_o)
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 *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 *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 > (3 * c_g) / 5) ? c_o - (3 * c_g) / 5 : 0;
- s[x] = (c_o < 0xFF - c_g) ? c_o + c_g : 0xFF;
+ o[x] = (c_o > c_g) ? c_o - (c_g / 2) : 0;
}
g += bm_g->w;
o += bm_o->w;
- s += bm_s->w;
}
-
- assert(bm_s);
- return bm_s;
}
/**
@@ -475,7 +466,8 @@ static void be_blur(unsigned char *buf, int w, int h)
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 be, double blur_radius, FT_Vector shadow_offset,
+ int border_style)
{
blur_radius *= 2;
int bbord = be > 0 ? sqrt(2 * be) : 0;
@@ -527,14 +519,19 @@ int glyph_to_bitmap(ASS_Library *library, ASS_SynthPriv *priv_blur,
priv_blur->g_w);
}
- if (*bm_o)
- *bm_s = fix_outline_and_shadow(*bm_g, *bm_o);
- else
+ // 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);
- assert(bm_s);
return 0;
}
diff --git a/libass/ass_bitmap.h b/libass/ass_bitmap.h
index 3b63cee..338db01 100644
--- a/libass/ass_bitmap.h
+++ b/libass/ass_bitmap.h
@@ -49,7 +49,8 @@ typedef struct {
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 be, double blur_radius, FT_Vector shadow_offset,
+ int border_style);
void ass_free_bitmap(Bitmap *bm);
diff --git a/libass/ass_cache.c b/libass/ass_cache.c
index ac0a00f..643d991 100644
--- a/libass/ass_cache.c
+++ b/libass/ass_cache.c
@@ -226,7 +226,7 @@ void *cache_add_bitmap(Hashmap *bitmap_cache, BitmapHashKey *key,
// Note: this is only an approximation
if (val->bm_o)
bitmap_cache->cache_size += val->bm_o->w * val->bm_o->h * 3;
- else
+ else if (val->bm)
bitmap_cache->cache_size += val->bm->w * val->bm->h * 3;
return hashmap_insert(bitmap_cache, key, val);
diff --git a/libass/ass_cache_template.h b/libass/ass_cache_template.h
index 6a6c3f2..f335c6b 100644
--- a/libass/ass_cache_template.h
+++ b/libass/ass_cache_template.h
@@ -54,7 +54,7 @@
// describes a bitmap; bitmaps with equivalents structs are considered identical
-START(bitmap, bipmap_hash_key)
+START(bitmap, bitmap_hash_key)
GENERIC(char, bitmap) // bool : true = bitmap, false = outline
GENERIC(ASS_Font *, font)
GENERIC(double, size) // font size
@@ -79,6 +79,8 @@ START(bitmap, bipmap_hash_key)
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
@@ -93,6 +95,7 @@ START(glyph, glyph_hash_key)
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
@@ -105,8 +108,10 @@ START(composite, composite_hash_key)
GENERIC(int, ay)
GENERIC(int, bx)
GENERIC(int, by)
- BITMAPHASHKEY(a)
- BITMAPHASHKEY(b)
+ GENERIC(int, as)
+ GENERIC(int, bs)
+ GENERIC(unsigned char *, a)
+ GENERIC(unsigned char *, b)
END(CompositeHashKey)
diff --git a/libass/ass_drawing.c b/libass/ass_drawing.c
index 95348a2..8c5f062 100644
--- a/libass/ass_drawing.c
+++ b/libass/ass_drawing.c
@@ -86,8 +86,10 @@ static inline void drawing_close_shape(ASS_Drawing *drawing)
drawing->max_contours);
}
- ol->contours[ol->n_contours] = ol->n_points - 1;
- ol->n_contours++;
+ if (ol->n_points) {
+ ol->contours[ol->n_contours] = ol->n_points - 1;
+ ol->n_contours++;
+ }
}
/*
diff --git a/libass/ass_font.c b/libass/ass_font.c
index e6da4bc..7d848a2 100644
--- a/libass/ass_font.c
+++ b/libass/ass_font.c
@@ -26,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"
@@ -299,7 +300,10 @@ static int ass_strike_outline_glyph(FT_Face face, ASS_Font *font,
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;
+ int bear, advance, y_scale, i, dir;
+
+ if (!under && !through)
+ return 0;
// Grow outline
i = (under ? 4 : 0) + (through ? 4 : 0);
@@ -317,6 +321,9 @@ static int ass_strike_outline_glyph(FT_Face face, ASS_Font *font,
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;
@@ -325,7 +332,7 @@ static int ass_strike_outline_glyph(FT_Face face, ASS_Font *font,
y_scale * font->scale_y / 2);
if (pos > 0 || size <= 0)
- return 0;
+ return 1;
FT_Vector points[4] = {
{.x = bear, .y = pos + size},
@@ -334,10 +341,18 @@ static int ass_strike_outline_glyph(FT_Face face, ASS_Font *font,
{.x = bear, .y = pos - size},
};
- for (i = 0; i < 4; i++) {
- ol->points[ol->n_points] = points[i];
- ol->tags[ol->n_points++] = 1;
+ 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;
}
@@ -347,7 +362,7 @@ static int ass_strike_outline_glyph(FT_Face face, ASS_Font *font,
size = FT_MulFix(os2->yStrikeoutSize, y_scale * font->scale_y / 2);
if (pos < 0 || size <= 0)
- return 0;
+ return 1;
FT_Vector points[4] = {
{.x = bear, .y = pos + size},
@@ -356,15 +371,38 @@ static int ass_strike_outline_glyph(FT_Face face, ASS_Font *font,
{.x = bear, .y = pos - size},
};
- for (i = 0; i < 4; i++) {
- ol->points[ol->n_points] = points[i];
- ol->tags[ol->n_points++] = 1;
+ 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 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);
}
/**
@@ -383,6 +421,9 @@ FT_Glyph ass_font_get_glyph(void *fontconfig_priv, ASS_Font *font,
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;
@@ -443,6 +484,11 @@ FT_Glyph ass_font_get_glyph(void *fontconfig_priv, ASS_Font *font,
(font->desc.italic > 55)) {
FT_GlyphSlot_Oblique(face->glyph);
}
+
+ if (!(face->style_flags & FT_STYLE_FLAG_BOLD) &&
+ (font->desc.bold > 80)) {
+ ass_glyph_embolden(face->glyph);
+ }
#endif
error = FT_Get_Glyph(face->glyph, &glyph);
if (error) {
diff --git a/libass/ass_fontconfig.c b/libass/ass_fontconfig.c
index 684c2a4..006be97 100644
--- a/libass/ass_fontconfig.c
+++ b/libass/ass_fontconfig.c
@@ -564,8 +564,10 @@ int fontconfig_update(FCInstance *priv)
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)
diff --git a/libass/ass_parse.c b/libass/ass_parse.c
new file mode 100644
index 0000000..535e16f
--- /dev/null
+++ b/libass/ass_parse.c
@@ -0,0 +1,932 @@
+/*
+ * 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;
+#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;
+}
+
+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;
+}
diff --git a/libass/ass_parse.h b/libass/ass_parse.h
new file mode 100644
index 0000000..c65b565
--- /dev/null
+++ b/libass/ass_parse.h
@@ -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 */
diff --git a/libass/ass_render.c b/libass/ass_render.c
index 3d1e8e6..c2756fd 100644
--- a/libass/ass_render.c
+++ b/libass/ass_render.c
@@ -37,232 +37,16 @@
#include "ass_fontconfig.h"
#include "ass_library.h"
#include "ass_drawing.h"
+#include "ass_render.h"
+#include "ass_parse.h"
#define MAX_GLYPHS_INITIAL 1024
#define MAX_LINES_INITIAL 64
-#define BLUR_MAX_RADIUS 100.0
-#define MAX_BE 127
#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 {
- 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;
- 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;
- 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;
-
-} 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;
-};
-
-struct render_priv {
- int top, height;
- int render_id;
-};
-
-typedef struct {
- int x0;
- int y0;
- int x1;
- int y1;
-} Rect;
-
-typedef struct {
- int a, b; // top and height
-} Segment;
-
-/* End of type definitions */
-
static void ass_lazy_track_init(ASS_Renderer *render_priv)
{
ASS_Track *track = render_priv->track;
@@ -409,8 +193,8 @@ void ass_renderer_done(ASS_Renderer *render_priv)
* 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)
+ int bitmap_h, int stride, int dst_x,
+ int dst_y, uint32_t color)
{
ASS_Image *img = calloc(1, sizeof(ASS_Image));
@@ -442,9 +226,9 @@ static double y2scr_pos(ASS_Renderer *render_priv, double y);
* karaoke effects. This can result in a lot of bitmaps (6 to be exact).
*/
static ASS_Image **render_glyph_i(ASS_Renderer *render_priv,
- Bitmap *bm, int dst_x, int dst_y,
- uint32_t color, uint32_t color2, int brk,
- ASS_Image **tail)
+ Bitmap *bm, int dst_x, int dst_y,
+ uint32_t color, uint32_t color2, int brk,
+ ASS_Image **tail)
{
int i, j, x0, y0, x1, y1, cx0, cy0, cx1, cy1, sx, sy, zx, zy;
Rect r[4];
@@ -539,10 +323,9 @@ static ASS_Image **render_glyph_i(ASS_Renderer *render_priv,
* \return pointer to the new list tail
* Performs clipping. Uses my_draw_bitmap for actual bitmap convertion.
*/
-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)
+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)
{
// Inverse clipping in use?
if (render_priv->state.clip_mode)
@@ -632,14 +415,13 @@ static unsigned char *clone_bitmap_buffer(ASS_Image *img)
/**
* \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_Renderer *render_priv, ASS_Image **last_tail,
- ASS_Image **tail, BitmapHashKey *last_hash,
- BitmapHashKey *hash)
+ ASS_Image **tail)
{
int left, top, bottom, right;
int old_left, old_top, w, h, cur_left, cur_top;
@@ -683,8 +465,8 @@ render_overlap(ASS_Renderer *render_priv, ASS_Image **last_tail,
// Query cache
memset(&hk, 0, sizeof(hk));
- memcpy(&hk.a, last_hash, sizeof(*last_hash));
- memcpy(&hk.b, hash, sizeof(*hash));
+ hk.a = (*last_tail)->bitmap;
+ hk.b = (*tail)->bitmap;
hk.aw = aw;
hk.ah = ah;
hk.bw = bw;
@@ -693,6 +475,8 @@ render_overlap(ASS_Renderer *render_priv, ASS_Image **last_tail,
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;
@@ -703,12 +487,12 @@ render_overlap(ASS_Renderer *render_priv, ASS_Image **last_tail,
a = clone_bitmap_buffer(*last_tail);
b = clone_bitmap_buffer(*tail);
- // Composite overlapping area
+ // 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 = (a[opos] > b[cpos]) ? a[opos] : b[cpos];
+ m = FFMIN(a[opos] + b[cpos], 0xff);
(*last_tail)->bitmap[opos] = 0;
(*tail)->bitmap[cpos] = m;
}
@@ -808,7 +592,7 @@ static void blend_vector_clip(ASS_Renderer *render_priv,
free_list_add(render_priv, nbuffer);
// Blend together
- memcpy(nbuffer, abuffer, as * ah);
+ 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;
@@ -859,13 +643,12 @@ static ASS_Image *render_text(ASS_Renderer *render_priv, int dst_x,
ASS_Image **tail = &head;
ASS_Image **last_tail = 0;
ASS_Image **here_tail = 0;
- BitmapHashKey *last_hash = 0;
TextInfo *text_info = &render_priv->text_info;
for (i = 0; i < text_info->length; ++i) {
GlyphInfo *info = text_info->glyphs + i;
if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm_s
- || (info->shadow_x == 0 && info->shadow_y == 0))
+ || (info->shadow_x == 0 && info->shadow_y == 0) || info->skip)
continue;
pen_x =
@@ -881,16 +664,16 @@ static ASS_Image *render_text(ASS_Renderer *render_priv, int dst_x,
render_glyph(render_priv, bm, pen_x, pen_y, info->c[3], 0,
1000000, tail);
if (last_tail && tail != here_tail && ((info->c[3] & 0xff) > 0))
- render_overlap(render_priv, last_tail, here_tail, last_hash,
- &info->hash_key);
+ render_overlap(render_priv, last_tail, here_tail);
+
last_tail = here_tail;
- last_hash = &info->hash_key;
}
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)
+ if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm_o
+ || info->skip)
continue;
pen_x = dst_x + (info->pos.x >> 6);
@@ -906,15 +689,16 @@ static ASS_Image *render_text(ASS_Renderer *render_priv, int dst_x,
render_glyph(render_priv, bm, pen_x, pen_y, info->c[2],
0, 1000000, tail);
if (last_tail && tail != here_tail && ((info->c[2] & 0xff) > 0))
- render_overlap(render_priv, last_tail, here_tail,
- last_hash, &info->hash_key);
+ render_overlap(render_priv, last_tail, here_tail);
+
last_tail = here_tail;
- last_hash = &info->hash_key;
}
}
+
for (i = 0; i < text_info->length; ++i) {
GlyphInfo *info = text_info->glyphs + i;
- if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm)
+ if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm
+ || info->skip)
continue;
pen_x = dst_x + (info->pos.x >> 6);
@@ -1015,6 +799,7 @@ static void compute_string_bbox(TextInfo *info, DBBox *bbox)
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);
@@ -1024,911 +809,11 @@ static void compute_string_bbox(TextInfo *info, DBBox *bbox)
bbox->xMin = bbox->xMax = bbox->yMin = bbox->yMax = 0.;
}
-
-/**
- * \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.
- */
-static 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
- */
-static 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)
- 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;
- }
-}
-
-#define _r(c) ((c)>>24)
-#define _g(c) (((c)>>16)&0xFF)
-#define _b(c) (((c)>>8)&0xFF)
-#define _a(c) ((c)&0xFF)
-
-/**
- * \brief Calculate a weighted average of two colors
- * calculates c1*(1-a) + c2*a, but separately for each component except alpha
- */
-static void change_color(uint32_t *var, uint32_t new, double pwr)
-{
- (*var) = ((uint32_t) (_r(*var) * (1 - pwr) + _r(new) * pwr) << 24) +
- ((uint32_t) (_g(*var) * (1 - pwr) + _g(new) * pwr) << 16) +
- ((uint32_t) (_b(*var) * (1 - pwr) + _b(new) * pwr) << 8) + _a(*var);
-}
-
-// like change_color, but for alpha component only
-static void change_alpha(uint32_t *var, uint32_t new, double pwr)
-{
- *var =
- (_r(*var) << 24) + (_g(*var) << 16) + (_b(*var) << 8) +
- (_a(*var) * (1 - pwr) + _a(new) * pwr);
-}
-
-/**
- * \brief Multiply two alpha values
- * \param a first value
- * \param b second value
- * \return result of multiplication
- * Parameters and result are limited by 0xFF.
- */
-static uint32_t mult_alpha(uint32_t a, uint32_t b)
-{
- return 0xFF - (0xFF - a) * (0xFF - b) / 0xFF;
-}
-
-/**
- * \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;
-}
-
-#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; }
-
-/**
- * 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;
-}
-
-static void reset_render_context(ASS_Renderer *);
-
-/**
- * \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;
- if (strtocolor(render_priv->library, &p, &val)) {
- 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)
- render_priv->state.alignment = 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;
- if (!strtocolor(render_priv->library, &p, &val))
- 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;
- assert((n >= '1') && (n <= '4'));
- if (!strtocolor(render_priv->library, &p, &val))
- 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;
- }
-
- return p;
-
-#undef skip
-#undef skipopt
-#undef skip_to
-}
-
-/**
- * \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(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->track->WrapStyle == 2))) {
- p += 2;
- *str = p;
- return '\n';
- } else if ((*(p + 1) == 'n') || (*(p + 1) == 'h')) {
- p += 2;
- *str = p;
- return ' ';
- }
- }
- chr = ass_utf8_get_char((char **) &p);
- *str = p;
- return chr;
-}
-
-static 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 partially reset render_context to style values
* Works like {\r}: resets some style overrides
*/
-static void reset_render_context(ASS_Renderer *render_priv)
+void reset_render_context(ASS_Renderer *render_priv)
{
render_priv->state.c[0] = render_priv->state.style->PrimaryColour;
render_priv->state.c[1] = render_priv->state.style->SecondaryColour;
@@ -1959,6 +844,7 @@ static void reset_render_context(ASS_Renderer *render_priv)
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.
}
@@ -2092,6 +978,64 @@ static void fix_freetype_stroker(FT_OutlineGlyph glyph, int border_x,
}
/*
+ * Replace the outline of a glyph by a contour which makes up a simple
+ * opaque rectangle.
+ */
+static void draw_opaque_box(ASS_Renderer *render_priv, uint32_t ch,
+ FT_Glyph glyph, int sx, int sy)
+{
+ int asc = 0, desc = 0;
+ int i;
+ int adv = d16_to_d6(glyph->advance.x);
+ double scale_y = render_priv->state.scale_y;
+ double scale_x = render_priv->state.scale_x
+ * render_priv->font_scale_x;
+ FT_OutlineGlyph og = (FT_OutlineGlyph) glyph;
+ FT_Outline *ol;
+
+ // to avoid gaps
+ sx = FFMAX(64, sx);
+ sy = FFMAX(64, sy);
+
+ if (ch == -1) {
+ asc = render_priv->state.drawing->asc;
+ desc = render_priv->state.drawing->desc;
+ } else {
+ ass_font_get_asc_desc(render_priv->state.font, ch, &asc, &desc);
+ asc *= scale_y;
+ desc *= scale_y;
+ }
+
+ // Emulate the WTFish behavior of VSFilter, i.e. double-scale
+ // the sizes of the opaque box.
+ adv += double_to_d6(render_priv->state.hspacing * render_priv->font_scale
+ * scale_x);
+ adv *= scale_x;
+ sx *= scale_x;
+ sy *= scale_y;
+ desc *= scale_y;
+ desc += asc * (scale_y - 1.0);
+
+ FT_Vector points[4] = {
+ { .x = -sx, .y = asc + sy },
+ { .x = adv + sx, .y = asc + sy },
+ { .x = adv + sx, .y = -desc - sy },
+ { .x = -sx, .y = -desc - sy },
+ };
+
+ FT_Outline_Done(render_priv->ftlibrary, &og->outline);
+ FT_Outline_New(render_priv->ftlibrary, 4, 1, &og->outline);
+
+ ol = &og->outline;
+ ol->n_points = ol->n_contours = 0;
+ for (i = 0; i < 4; i++) {
+ ol->points[ol->n_points] = points[i];
+ ol->tags[ol->n_points++] = 1;
+ }
+ ol->contours[ol->n_contours++] = ol->n_points - 1;
+}
+
+/*
* Stroke an outline glyph in x/y direction. Applies various fixups to get
* around limitations of the FreeType stroker.
*/
@@ -2144,8 +1088,8 @@ static void stroke_outline_glyph(ASS_Renderer *render_priv,
* The glyphs are returned in info->glyph and info->outline_glyph
*/
static void
-get_outline_glyph(ASS_Renderer *render_priv, int symbol,
- GlyphInfo *info, ASS_Drawing *drawing)
+get_outline_glyph(ASS_Renderer *render_priv, int symbol, GlyphInfo *info,
+ ASS_Drawing *drawing)
{
GlyphHashValue *val;
GlyphHashKey key;
@@ -2156,6 +1100,7 @@ get_outline_glyph(ASS_Renderer *render_priv, int symbol,
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;
@@ -2168,6 +1113,7 @@ get_outline_glyph(ASS_Renderer *render_priv, int symbol,
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));
@@ -2201,8 +1147,17 @@ get_outline_glyph(ASS_Renderer *render_priv, int symbol,
info->advance.y = d16_to_d6(info->glyph->advance.y);
FT_Glyph_Get_CBox(info->glyph, FT_GLYPH_BBOX_SUBPIXELS, &info->bbox);
- if (render_priv->state.border_x > 0 ||
- render_priv->state.border_y > 0) {
+ 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) {
FT_Glyph_Copy(info->glyph, &info->outline_glyph);
stroke_outline_glyph(render_priv,
@@ -2229,7 +1184,8 @@ get_outline_glyph(ASS_Renderer *render_priv, int symbol,
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);
+ double frz, double fax, double fay, double scale,
+ int yshift);
/**
* \brief Get bitmaps for a glyph
@@ -2255,15 +1211,20 @@ get_bitmap_glyph(ASS_Renderer *render_priv, GlyphInfo *info)
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) {
+ 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, info->fax,
- info->fay, render_priv->font_scale);
+ info->frx, info->fry, info->frz, fax_scaled,
+ fay_scaled, render_priv->font_scale, info->asc);
// subpixel shift
if (info->glyph)
@@ -2284,7 +1245,8 @@ get_bitmap_glyph(ASS_Renderer *render_priv, GlyphInfo *info)
&info->bm, &info->bm_o,
&info->bm_s, info->be,
info->blur * render_priv->border_scale,
- info->hash_key.shadow_offset);
+ info->hash_key.shadow_offset,
+ info->hash_key.border_style);
if (error)
info->symbol = 0;
@@ -2350,6 +1312,61 @@ static void measure_text(ASS_Renderer *render_priv)
}
/**
+ * 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:
@@ -2357,6 +1374,8 @@ static void measure_text(ASS_Renderer *render_priv)
* 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(ASS_Renderer *render_priv, double max_text_width)
@@ -2391,7 +1410,7 @@ wrap_lines_smart(ASS_Renderer *render_priv, double max_text_width)
}
if ((len >= max_text_width)
- && (render_priv->track->WrapStyle != 2)) {
+ && (render_priv->state.wrap_style != 2)) {
break_type = 1;
break_at = last_space;
if (break_at == -1)
@@ -2432,7 +1451,7 @@ wrap_lines_smart(ASS_Renderer *render_priv, double max_text_width)
}
#define DIFF(x,y) (((x) < (y)) ? (y - x) : (x - y))
exit = 0;
- while (!exit) {
+ while (!exit && render_priv->state.wrap_style != 1) {
exit = 1;
w = s3 = text_info->glyphs;
s1 = s2 = 0;
@@ -2486,13 +1505,23 @@ wrap_lines_smart(ASS_Renderer *render_priv, double max_text_width)
#undef DIFF
measure_text(render_priv);
+ trim_whitespace(render_priv);
pen_shift_x = 0.;
pen_shift_y = 0.;
cur_line = 1;
+
+ i = 0;
+ cur = text_info->glyphs + i;
+ while (i < text_info->length && cur->skip)
+ cur = text_info->glyphs + ++i;
+ pen_shift_x = d6_to_double(-cur->pos.x);
+
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;
@@ -2620,9 +1649,9 @@ static void get_base_point(DBBox *bbox, int alignment, double *bx, double *by)
* onto the screen plane.
*/
static void
-transform_3d_points(FT_Vector shift, FT_Glyph glyph, double frx,
- double fry, double frz, double fax, double fay,
- double scale)
+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);
@@ -2637,7 +1666,7 @@ transform_3d_points(FT_Vector shift, FT_Glyph glyph, double frx,
dist = 20000 * scale;
for (i = 0; i < outline->n_points; i++) {
- x = (double) p[i].x + shift.x + (-fax * p[i].y);
+ 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.;
@@ -2675,18 +1704,18 @@ transform_3d_points(FT_Vector shift, FT_Glyph glyph, double frx,
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)
+ double scale, int yshift)
{
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);
+ fax, fay, scale, yshift);
if (glyph2 && *glyph2)
transform_3d_points(shift, *glyph2, frx, fry, frz,
- fax, fay, scale);
+ fax, fay, scale, yshift);
}
}
@@ -2711,6 +1740,7 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event,
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;
@@ -2774,13 +1804,15 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event,
}
// Add kerning to pen
- if (previous && code && !drawing->hash) {
+ 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;
- pen.y += delta.y * render_priv->state.scale_y;
+ 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;
}
ass_font_set_transform(render_priv->state.font,
@@ -2791,14 +1823,30 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event,
get_outline_glyph(render_priv, code,
text_info->glyphs + text_info->length, drawing);
+ // 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;
+ }
+ }
+
text_info->glyphs[text_info->length].pos.x = pen.x;
text_info->glyphs[text_info->length].pos.y = pen.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->font_scale
+ * render_priv->state.scale_x);
pen.y += text_info->glyphs[text_info->length].advance.y;
- pen.y += render_priv->state.fay *
+ pen.y += (render_priv->state.fay * render_priv->state.scale_y) *
text_info->glyphs[text_info->length].advance.x;
previous = code;
@@ -2880,6 +1928,8 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event,
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 -
@@ -2890,6 +1940,8 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event,
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;
text_info->length++;
@@ -2953,9 +2005,13 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event,
text_info->glyphs + last_break + 1;
GlyphInfo *last_glyph = text_info->glyphs + i - 1;
+ while (first_glyph < last_glyph && first_glyph->skip)
+ first_glyph++;
+
while ((last_glyph > first_glyph)
&& ((last_glyph->symbol == '\n')
- || (last_glyph->symbol == 0)))
+ || (last_glyph->symbol == 0)
+ || (last_glyph->skip)))
last_glyph--;
width = d6_to_double(
@@ -3126,6 +2182,8 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event,
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;
@@ -3342,21 +2400,22 @@ static int cmp_event_layer(const void *p1, const void *p2)
}
static ASS_RenderPriv *get_render_priv(ASS_Renderer *render_priv,
- ASS_Event *event)
+ ASS_Event *event)
{
if (!event->render_priv)
event->render_priv = calloc(1, sizeof(ASS_RenderPriv));
- // FIXME: check render_id
if (render_priv->render_id != event->render_priv->render_id) {
memset(event->render_priv, 0, sizeof(ASS_RenderPriv));
event->render_priv->render_id = render_priv->render_id;
}
+
return event->render_priv;
}
static int overlap(Segment *s1, Segment *s2)
{
- if (s1->a >= s2->b || s2->a >= s1->b)
+ if (s1->a >= s2->b || s2->a >= s1->b ||
+ s1->ha >= s2->hb || s2->ha >= s1->hb)
return 0;
return 1;
}
@@ -3401,18 +2460,22 @@ static int fit_segment(Segment *s, Segment *fixed, int *cnt, int dir)
if (dir == 1) // move down
for (i = 0; i < *cnt; ++i) {
- if (s->b + shift <= fixed[i].a || s->a + shift >= fixed[i].b)
+ 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)
+ 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;
+ fixed[*cnt].ha = s->ha;
+ fixed[*cnt].hb = s->hb;
(*cnt)++;
qsort(fixed, *cnt, sizeof(Segment), cmp_segment);
@@ -3436,20 +2499,28 @@ fix_collisions(ASS_Renderer *render_priv, EventImages *imgs, int cnt)
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);
}
@@ -3468,13 +2539,16 @@ fix_collisions(ASS_Renderer *render_priv, EventImages *imgs, int cnt)
Segment 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);
+ 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;
}
}
@@ -3554,7 +2628,7 @@ static int ass_detect_change(ASS_Renderer *priv)
* Can be NULL, in that case no detection is performed.
*/
ASS_Image *ass_render_frame(ASS_Renderer *priv, ASS_Track *track,
- long long now, int *detect_change)
+ long long now, int *detect_change)
{
int i, cnt, rc;
EventImages *last;
diff --git a/libass/ass_render.h b/libass/ass_render.h
new file mode 100644
index 0000000..6d9db23
--- /dev/null
+++ b/libass/ass_render.h
@@ -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 */
diff --git a/libass/ass_types.h b/libass/ass_types.h
index d6a0111..63bc36c 100644
--- a/libass/ass_types.h
+++ b/libass/ass_types.h
@@ -113,6 +113,7 @@ typedef struct ass_track {
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
diff --git a/libass/ass_utils.c b/libass/ass_utils.c
index e8fce67..6ca78b8 100644
--- a/libass/ass_utils.c
+++ b/libass/ass_utils.c
@@ -74,11 +74,12 @@ int mystrtod(char **p, double *res)
return 0;
}
-int strtocolor(ASS_Library *library, 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;
+ int base = hex ? 16 : 10;
if (*p == '&')
++p;
@@ -89,7 +90,7 @@ int strtocolor(ASS_Library *library, char **q, uint32_t *res)
++p;
result = mystrtou32(&p, 16, &color);
} else {
- result = mystrtou32(&p, 0, &color);
+ result = mystrtou32(&p, base, &color);
}
{
diff --git a/libass/ass_utils.h b/libass/ass_utils.h
index 8590bb4..bade578 100644
--- a/libass/ass_utils.h
+++ b/libass/ass_utils.h
@@ -49,7 +49,7 @@ 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 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, ...);
--
LibASS packaging
More information about the pkg-multimedia-commits
mailing list