diff --git a/aegisub/subtitles_provider_libass.cpp b/aegisub/subtitles_provider_libass.cpp index 6cfb98d73..d19fd385d 100644 --- a/aegisub/subtitles_provider_libass.cpp +++ b/aegisub/subtitles_provider_libass.cpp @@ -47,7 +47,14 @@ extern "C" { /////////// // Library #ifdef __VISUALC__ -#pragma comment(lib,"libass.dll.a") +#ifdef __WXDEBUG__ +//#pragma comment(lib,"libassd.lib") +#pragma comment(lib,"freetype233MT_D.lib") +#else +//#pragma comment(lib,"libass.lib") +#pragma comment(lib,"freetype233MT.lib") +#endif +#pragma comment(lib,"libfontconfig.lib") #endif diff --git a/libass/Makefile.am b/libass/Makefile.am new file mode 100644 index 000000000..7c4056e0a --- /dev/null +++ b/libass/Makefile.am @@ -0,0 +1,10 @@ + +lib_LTLIBRARIES = libass.la +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 mputils.c \ + ass_bitmap.h ass_cache.h ass_fontconfig.h ass_font.h ass.h \ + ass_library.h ass_types.h ass_utils.h help_mp.h mputils.h + +assheadersdir = $(includedir)/ass +dist_assheaders_HEADERS = ass.h ass_types.h + diff --git a/libass/Makefile.in b/libass/Makefile.in new file mode 100644 index 000000000..f171c8b65 --- /dev/null +++ b/libass/Makefile.in @@ -0,0 +1,498 @@ +# Makefile.in generated by automake 1.9.6 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005 Free Software Foundation, Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +srcdir = @srcdir@ +top_srcdir = @top_srcdir@ +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +top_builddir = .. +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +INSTALL = @INSTALL@ +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = libass +DIST_COMMON = $(dist_assheaders_HEADERS) $(srcdir)/Makefile.am \ + $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = `echo $$p | sed -e 's|^.*/||'`; +am__installdirs = "$(DESTDIR)$(libdir)" "$(DESTDIR)$(assheadersdir)" +libLTLIBRARIES_INSTALL = $(INSTALL) +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 mputils.lo +libass_la_OBJECTS = $(am_libass_la_OBJECTS) +DEFAULT_INCLUDES = -I. -I$(srcdir) -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +CCLD = $(CC) +LINK = $(LIBTOOL) --tag=CC --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +SOURCES = $(libass_la_SOURCES) +DIST_SOURCES = $(libass_la_SOURCES) +dist_assheadersHEADERS_INSTALL = $(INSTALL_HEADER) +HEADERS = $(dist_assheaders_HEADERS) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMDEP_FALSE = @AMDEP_FALSE@ +AMDEP_TRUE = @AMDEP_TRUE@ +AMTAR = @AMTAR@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO = @ECHO@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +F77 = @F77@ +FFLAGS = @FFLAGS@ +FONTCONFIG_CFLAGS = @FONTCONFIG_CFLAGS@ +FONTCONFIG_LIBS = @FONTCONFIG_LIBS@ +FREETYPE_CFLAGS = @FREETYPE_CFLAGS@ +FREETYPE_LIBS = @FREETYPE_LIBS@ +GREP = @GREP@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +POW_LIB = @POW_LIB@ +RANLIB = @RANLIB@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VERSION = @VERSION@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_F77 = @ac_ct_F77@ +am__fastdepCC_FALSE = @am__fastdepCC_FALSE@ +am__fastdepCC_TRUE = @am__fastdepCC_TRUE@ +am__fastdepCXX_FALSE = @am__fastdepCXX_FALSE@ +am__fastdepCXX_TRUE = @am__fastdepCXX_TRUE@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +lib_LTLIBRARIES = libass.la +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 mputils.c \ + ass_bitmap.h ass_cache.h ass_fontconfig.h ass_font.h ass.h \ + ass_library.h ass_types.h ass_utils.h help_mp.h mputils.h + +assheadersdir = $(includedir)/ass +dist_assheaders_HEADERS = ass.h ass_types.h +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh \ + && exit 0; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu libass/Makefile'; \ + cd $(top_srcdir) && \ + $(AUTOMAKE) --gnu libass/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +install-libLTLIBRARIES: $(lib_LTLIBRARIES) + @$(NORMAL_INSTALL) + test -z "$(libdir)" || $(mkdir_p) "$(DESTDIR)$(libdir)" + @list='$(lib_LTLIBRARIES)'; for p in $$list; do \ + if test -f $$p; then \ + f=$(am__strip_dir) \ + echo " $(LIBTOOL) --mode=install $(libLTLIBRARIES_INSTALL) $(INSTALL_STRIP_FLAG) '$$p' '$(DESTDIR)$(libdir)/$$f'"; \ + $(LIBTOOL) --mode=install $(libLTLIBRARIES_INSTALL) $(INSTALL_STRIP_FLAG) "$$p" "$(DESTDIR)$(libdir)/$$f"; \ + else :; fi; \ + done + +uninstall-libLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @set -x; list='$(lib_LTLIBRARIES)'; for p in $$list; do \ + p=$(am__strip_dir) \ + echo " $(LIBTOOL) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$p'"; \ + $(LIBTOOL) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$p"; \ + done + +clean-libLTLIBRARIES: + -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES) + @list='$(lib_LTLIBRARIES)'; for p in $$list; do \ + dir="`echo $$p | sed -e 's|/[^/]*$$||'`"; \ + test "$$dir" != "$$p" || dir=.; \ + echo "rm -f \"$${dir}/so_locations\""; \ + rm -f "$${dir}/so_locations"; \ + done +libass.la: $(libass_la_OBJECTS) $(libass_la_DEPENDENCIES) + $(LINK) -rpath $(libdir) $(libass_la_LDFLAGS) $(libass_la_OBJECTS) $(libass_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ass.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ass_bitmap.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ass_cache.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ass_font.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ass_fontconfig.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ass_library.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ass_render.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ass_utils.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mputils.Plo@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ if $(COMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ $<; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Po"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ if $(COMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ `$(CYGPATH_W) '$<'`; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Po"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ if $(LTCOMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ $<; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Plo"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +distclean-libtool: + -rm -f libtool +uninstall-info-am: +install-dist_assheadersHEADERS: $(dist_assheaders_HEADERS) + @$(NORMAL_INSTALL) + test -z "$(assheadersdir)" || $(mkdir_p) "$(DESTDIR)$(assheadersdir)" + @list='$(dist_assheaders_HEADERS)'; for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + f=$(am__strip_dir) \ + echo " $(dist_assheadersHEADERS_INSTALL) '$$d$$p' '$(DESTDIR)$(assheadersdir)/$$f'"; \ + $(dist_assheadersHEADERS_INSTALL) "$$d$$p" "$(DESTDIR)$(assheadersdir)/$$f"; \ + done + +uninstall-dist_assheadersHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(dist_assheaders_HEADERS)'; for p in $$list; do \ + f=$(am__strip_dir) \ + echo " rm -f '$(DESTDIR)$(assheadersdir)/$$f'"; \ + rm -f "$(DESTDIR)$(assheadersdir)/$$f"; \ + done + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + tags=; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$tags $$unique; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + tags=; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + test -z "$(CTAGS_ARGS)$$tags$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$tags $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && cd $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) $$here + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's|.|.|g'`; \ + list='$(DISTFILES)'; for file in $$list; do \ + case $$file in \ + $(srcdir)/*) file=`echo "$$file" | sed "s|^$$srcdirstrip/||"`;; \ + $(top_srcdir)/*) file=`echo "$$file" | sed "s|^$$topsrcdirstrip/|$(top_builddir)/|"`;; \ + esac; \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + dir=`echo "$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test "$$dir" != "$$file" && test "$$dir" != "."; then \ + dir="/$$dir"; \ + $(mkdir_p) "$(distdir)$$dir"; \ + else \ + dir=''; \ + fi; \ + if test -d $$d/$$file; then \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \ + fi; \ + cp -pR $$d/$$file $(distdir)$$dir || exit 1; \ + else \ + test -f $(distdir)/$$file \ + || cp -p $$d/$$file $(distdir)/$$file \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(assheadersdir)"; do \ + test -z "$$dir" || $(mkdir_p) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \ + mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-libtool distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +info: info-am + +info-am: + +install-data-am: install-dist_assheadersHEADERS + +install-exec-am: install-libLTLIBRARIES + +install-info: install-info-am + +install-man: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-dist_assheadersHEADERS uninstall-info-am \ + uninstall-libLTLIBRARIES + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \ + clean-libLTLIBRARIES clean-libtool ctags distclean \ + distclean-compile distclean-generic distclean-libtool \ + distclean-tags distdir dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am \ + install-dist_assheadersHEADERS install-exec install-exec-am \ + install-info install-info-am install-libLTLIBRARIES \ + install-man install-strip installcheck installcheck-am \ + installdirs maintainer-clean maintainer-clean-generic \ + mostlyclean mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool pdf pdf-am ps ps-am tags uninstall \ + uninstall-am uninstall-dist_assheadersHEADERS \ + uninstall-info-am uninstall-libLTLIBRARIES + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/libass/ass.c b/libass/ass.c new file mode 100644 index 000000000..546e41797 --- /dev/null +++ b/libass/ass.c @@ -0,0 +1,1077 @@ +// -*- c-basic-offset: 8; indent-tabs-mode: t -*- +// vim:ts=8:sw=8:noet:ai: +/* + Copyright (C) 2006 Evgeniy Stepanov + + This program 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. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef USE_ICONV +#include +#endif + +#include "ass.h" +#include "ass_utils.h" +#include "ass_library.h" +#include "mputils.h" + +typedef enum {PST_UNKNOWN = 0, PST_INFO, PST_STYLES, PST_EVENTS, PST_FONTS} parser_state_t; + +struct parser_priv_s { + parser_state_t state; + char* fontname; + char* fontdata; + int fontdata_size; + int fontdata_used; +}; + +#define ASS_STYLES_ALLOC 20 +#define ASS_EVENTS_ALLOC 200 + +void ass_free_track(ass_track_t* track) { + int i; + + if (track->parser_priv) { + if (track->parser_priv->fontname) + free(track->parser_priv->fontname); + if (track->parser_priv->fontdata) + free(track->parser_priv->fontdata); + free(track->parser_priv); + } + if (track->style_format) + free(track->style_format); + if (track->event_format) + free(track->event_format); + if (track->styles) { + for (i = 0; i < track->n_styles; ++i) + ass_free_style(track, i); + free(track->styles); + } + if (track->events) { + for (i = 0; i < track->n_events; ++i) + ass_free_event(track, i); + free(track->events); + } +} + +/// \brief Allocate a new style struct +/// \param track track +/// \return style id +int ass_alloc_style(ass_track_t* track) { + int sid; + + assert(track->n_styles <= track->max_styles); + + if (track->n_styles == track->max_styles) { + track->max_styles += ASS_STYLES_ALLOC; + track->styles = (ass_style_t*)realloc(track->styles, sizeof(ass_style_t)*track->max_styles); + } + + sid = track->n_styles++; + memset(track->styles + sid, 0, sizeof(ass_style_t)); + return sid; +} + +/// \brief Allocate a new event struct +/// \param track track +/// \return event id +int ass_alloc_event(ass_track_t* track) { + int eid; + + assert(track->n_events <= track->max_events); + + if (track->n_events == track->max_events) { + track->max_events += ASS_EVENTS_ALLOC; + track->events = (ass_event_t*)realloc(track->events, sizeof(ass_event_t)*track->max_events); + } + + eid = track->n_events++; + memset(track->events + eid, 0, sizeof(ass_event_t)); + return eid; +} + +void ass_free_event(ass_track_t* track, int eid) { + ass_event_t* event = track->events + eid; + if (event->Name) + free(event->Name); + if (event->Effect) + free(event->Effect); + if (event->Text) + free(event->Text); + if (event->render_priv) + free(event->render_priv); +} + +void ass_free_style(ass_track_t* track, int sid) { + ass_style_t* style = track->styles + sid; + if (style->Name) + free(style->Name); + if (style->FontName) + free(style->FontName); +} + +// ============================================================================================== + +static void skip_spaces(char** str) { + char* p = *str; + while ((*p==' ') || (*p=='\t')) + ++p; + *str = p; +} + +static void rskip_spaces(char** str, char* limit) { + char* p = *str; + while ((p >= limit) && ((*p==' ') || (*p=='\t'))) + --p; + *str = p; +} + +/** + * \brief find style by name + * \param track track + * \param name style name + * \return index in track->styles + * Returnes 0 if no styles found => expects at least 1 style. + * Parsing code always adds "Default" style in the end. + */ +static int lookup_style(ass_track_t* track, char* name) { + int i; + if (*name == '*') ++name; // FIXME: what does '*' really mean ? + for (i=0; in_styles; ++i) { + // FIXME: mb strcasecmp ? + if (strcmp(track->styles[i].Name, name) == 0) + return i; + } + i = track->default_style; + mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_NoStyleNamedXFoundUsingY, track, name, track->styles[i].Name); + return i; // use the first style +} + +static uint32_t string2color(char* p) { + uint32_t tmp; + (void)strtocolor(&p, &tmp); + return tmp; +} + +static long long string2timecode(char* p) { + unsigned h, m, s, ms; + long long tm; + int res = sscanf(p, "%1d:%2d:%2d.%2d", &h, &m, &s, &ms); + if (res < 4) { + mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_BadTimestamp); + return 0; + } + tm = ((h * 60 + m) * 60 + s) * 1000 + ms * 10; + return tm; +} + +/** + * \brief converts numpad-style align to align. + */ +static int numpad2align(int val) { + int res, v; + v = (val - 1) / 3; // 0, 1 or 2 for vertical alignment + if (v != 0) v = 3 - v; + res = ((val - 1) % 3) + 1; // horizontal alignment + res += v*4; + return res; +} + +#define NEXT(str,token) \ + token = next_token(&str); \ + if (!token) break; + +#define ANYVAL(name,func) \ + } else if (strcasecmp(tname, #name) == 0) { \ + target->name = func(token); \ + mp_msg(MSGT_ASS, MSGL_DBG2, "%s = %s\n", #name, token); + +#define STRVAL(name) \ + } else if (strcasecmp(tname, #name) == 0) { \ + if (target->name != NULL) free(target->name); \ + target->name = strdup(token); \ + mp_msg(MSGT_ASS, MSGL_DBG2, "%s = %s\n", #name, token); + +#define COLORVAL(name) ANYVAL(name,string2color) +#define INTVAL(name) ANYVAL(name,atoi) +#define FPVAL(name) ANYVAL(name,atof) +#define TIMEVAL(name) ANYVAL(name,string2timecode) +#define STYLEVAL(name) \ + } else if (strcasecmp(tname, #name) == 0) { \ + target->name = lookup_style(track, token); \ + mp_msg(MSGT_ASS, MSGL_DBG2, "%s = %s\n", #name, token); + +#define ALIAS(alias,name) \ + if (strcasecmp(tname, #alias) == 0) {tname = #name;} + +static char* next_token(char** str) { + char* p = *str; + char* start; + skip_spaces(&p); + if (*p == '\0') { + *str = p; + return 0; + } + start = p; // start of the token + for (; (*p != '\0') && (*p != ','); ++p) {} + if (*p == '\0') { + *str = p; // eos found, str will point to '\0' at exit + } else { + *p = '\0'; + *str = p + 1; // ',' found, str will point to the next char (beginning of the next token) + } + --p; // end of current token + rskip_spaces(&p, start); + if (p < start) + p = start; // empty token + else + ++p; // the first space character, or '\0' + *p = '\0'; + return start; +} +/** + * \brief Parse the tail of Dialogue line + * \param track track + * \param event parsed data goes here + * \param str string to parse, zero-terminated + * \param n_ignored number of format options to skip at the beginning +*/ +static int process_event_tail(ass_track_t* track, ass_event_t* event, char* str, int n_ignored) +{ + char* token; + char* tname; + char* p = str; + int i; + ass_event_t* target = event; + + char* format = strdup(track->event_format); + char* q = format; // format scanning pointer + + for (i = 0; i < n_ignored; ++i) { + NEXT(q, tname); + } + + while (1) { + NEXT(q, tname); + if (strcasecmp(tname, "Text") == 0) { + char* last; + event->Text = strdup(p); + if (*event->Text != 0) { + last = event->Text + strlen(event->Text) - 1; + if (last >= event->Text && *last == '\r') + *last = 0; + } + mp_msg(MSGT_ASS, MSGL_DBG2, "Text = %s\n", event->Text); + event->Duration -= event->Start; + free(format); + return 0; // "Text" is always the last + } + NEXT(p, token); + + ALIAS(End,Duration) // temporarily store end timecode in event->Duration + if (0) { // cool ;) + INTVAL(Layer) + STYLEVAL(Style) + STRVAL(Name) + STRVAL(Effect) + INTVAL(MarginL) + INTVAL(MarginR) + INTVAL(MarginV) + TIMEVAL(Start) + TIMEVAL(Duration) + } + } + free(format); + return 1; +} + +/** + * \brief Parse command line style overrides (--ass-force-style option) + * \param track track to apply overrides to + * The format for overrides is [StyleName.]Field=Value + */ +void process_force_style(ass_track_t* track) { + char **fs, *eq, *dt, *style, *tname, *token; + ass_style_t* target; + int sid; + char** list = track->library->style_overrides; + + if (!list) return; + + for (fs = list; *fs; ++fs) { + eq = strchr(*fs, '='); + if (!eq) + continue; + *eq = '\0'; + token = eq + 1; + + dt = strchr(*fs, '.'); + if (dt) { + *dt = '\0'; + style = *fs; + tname = dt + 1; + } else { + style = NULL; + tname = *fs; + } + for (sid = 0; sid < track->n_styles; ++sid) { + if (style == NULL || strcasecmp(track->styles[sid].Name, style) == 0) { + target = track->styles + sid; + if (0) { + STRVAL(FontName) + COLORVAL(PrimaryColour) + COLORVAL(SecondaryColour) + COLORVAL(OutlineColour) + COLORVAL(BackColour) + INTVAL(FontSize) + INTVAL(Bold) + INTVAL(Italic) + INTVAL(Underline) + INTVAL(StrikeOut) + FPVAL(Spacing) + INTVAL(Angle) + INTVAL(BorderStyle) + INTVAL(Alignment) + INTVAL(MarginL) + INTVAL(MarginR) + INTVAL(MarginV) + INTVAL(Encoding) + FPVAL(ScaleX) + FPVAL(ScaleY) + FPVAL(Outline) + FPVAL(Shadow) + } + } + } + *eq = '='; + if (dt) *dt = '.'; + } +} + +/** + * \brief Parse the Style line + * \param track track + * \param str string to parse, zero-terminated + * Allocates a new style struct. +*/ +static int process_style(ass_track_t* track, char *str) +{ + + char* token; + char* tname; + char* p = str; + char* format; + char* q; // format scanning pointer + int sid; + ass_style_t* style; + ass_style_t* target; + + if (!track->style_format) { + // no style format header + // probably an ancient script version + if (track->track_type == TRACK_TYPE_SSA) + track->style_format = strdup("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour," + "TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline," + "Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding"); + else + track->style_format = strdup("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour," + "OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut," + "ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow," + "Alignment, MarginL, MarginR, MarginV, Encoding"); + } + + q = format = strdup(track->style_format); + + mp_msg(MSGT_ASS, MSGL_V, "[%p] Style: %s\n", track, str); + + sid = ass_alloc_style(track); + + style = track->styles + sid; + target = style; +// fill style with some default values + style->ScaleX = 100.; + style->ScaleY = 100.; + + while (1) { + NEXT(q, tname); + NEXT(p, token); + +// ALIAS(TertiaryColour,OutlineColour) // ignore TertiaryColour; it appears only in SSA, and is overridden by BackColour + + if (0) { // cool ;) + STRVAL(Name) + if ((strcmp(target->Name, "Default")==0) || (strcmp(target->Name, "*Default")==0)) + track->default_style = sid; + STRVAL(FontName) + COLORVAL(PrimaryColour) + COLORVAL(SecondaryColour) + COLORVAL(OutlineColour) // TertiaryColor + COLORVAL(BackColour) + // SSA uses BackColour for both outline and shadow + // this will destroy SSA's TertiaryColour, but i'm not going to use it anyway + if (track->track_type == TRACK_TYPE_SSA) + target->OutlineColour = target->BackColour; + INTVAL(FontSize) + INTVAL(Bold) + INTVAL(Italic) + INTVAL(Underline) + INTVAL(StrikeOut) + FPVAL(Spacing) + INTVAL(Angle) + INTVAL(BorderStyle) + INTVAL(Alignment) + if (track->track_type == TRACK_TYPE_ASS) + target->Alignment = numpad2align(target->Alignment); + INTVAL(MarginL) + INTVAL(MarginR) + INTVAL(MarginV) + INTVAL(Encoding) + FPVAL(ScaleX) + FPVAL(ScaleY) + FPVAL(Outline) + FPVAL(Shadow) + } + } + style->ScaleX /= 100.; + style->ScaleY /= 100.; + style->Bold = !!style->Bold; + style->Italic = !!style->Italic; + style->Underline = !!style->Underline; + if (!style->Name) + style->Name = strdup("Default"); + if (!style->FontName) + style->FontName = strdup("Arial"); + free(format); + return 0; + +} + +static int process_styles_line(ass_track_t* track, char *str) +{ + if (!strncmp(str,"Format:", 7)) { + char* p = str + 7; + skip_spaces(&p); + track->style_format = strdup(p); + mp_msg(MSGT_ASS, MSGL_DBG2, "Style format: %s\n", track->style_format); + } else if (!strncmp(str,"Style:", 6)) { + char* p = str + 6; + skip_spaces(&p); + process_style(track, p); + } + return 0; +} + +static int process_info_line(ass_track_t* track, char *str) +{ + if (!strncmp(str, "PlayResX:", 9)) { + track->PlayResX = atoi(str + 9); + } else if (!strncmp(str,"PlayResY:", 9)) { + track->PlayResY = atoi(str + 9); + } else if (!strncmp(str,"Timer:", 6)) { + track->Timer = atof(str + 6); + } else if (!strncmp(str,"WrapStyle:", 10)) { + track->WrapStyle = atoi(str + 10); + } + return 0; +} + +static int process_events_line(ass_track_t* track, char *str) +{ + if (!strncmp(str, "Format:", 7)) { + char* p = str + 7; + skip_spaces(&p); + track->event_format = strdup(p); + mp_msg(MSGT_ASS, MSGL_DBG2, "Event format: %s\n", track->event_format); + } else if (!strncmp(str, "Dialogue:", 9)) { + // This should never be reached for embedded subtitles. + // They have slightly different format and are parsed in ass_process_chunk, + // called directly from demuxer + int eid; + ass_event_t* event; + + str += 9; + skip_spaces(&str); + + eid = ass_alloc_event(track); + event = track->events + eid; + + process_event_tail(track, event, str, 0); + } else { + mp_msg(MSGT_ASS, MSGL_V, "Not understood: %s \n", str); + } + return 0; +} + +// Copied from mkvtoolnix +static unsigned char* decode_chars(unsigned char c1, unsigned char c2, + unsigned char c3, unsigned char c4, unsigned char* dst, int cnt) +{ + uint32_t value; + unsigned char bytes[3]; + int i; + + value = ((c1 - 33) << 18) + ((c2 - 33) << 12) + ((c3 - 33) << 6) + (c4 - 33); + bytes[2] = value & 0xff; + bytes[1] = (value & 0xff00) >> 8; + bytes[0] = (value & 0xff0000) >> 16; + + for (i = 0; i < cnt; ++i) + *dst++ = bytes[i]; + return dst; +} + +static int decode_font(ass_track_t* track) +{ + unsigned char* p; + unsigned char* q; + int i; + int size; // original size + int dsize; // decoded size + unsigned char* buf = 0; + + mp_msg(MSGT_ASS, MSGL_V, "font: %d bytes encoded data \n", track->parser_priv->fontdata_used); + size = track->parser_priv->fontdata_used; + if (size % 4 == 1) { + mp_msg(MSGT_ASS, MSGL_ERR, MSGTR_LIBASS_BadEncodedDataSize); + goto error_decode_font; + } + buf = malloc(size / 4 * 3 + 2); + q = buf; + for (i = 0, p = (unsigned char*)track->parser_priv->fontdata; i < size / 4; i++, p+=4) { + q = decode_chars(p[0], p[1], p[2], p[3], q, 3); + } + if (size % 4 == 2) { + q = decode_chars(p[0], p[1], 0, 0, q, 1); + } else if (size % 4 == 3) { + q = decode_chars(p[0], p[1], p[2], 0, q, 2); + } + dsize = q - buf; + assert(dsize <= size / 4 * 3 + 2); + + if (track->library->extract_fonts) + ass_add_font(track->library, track->parser_priv->fontname, (char*)buf, dsize); + +error_decode_font: + if (buf) free(buf); + free(track->parser_priv->fontname); + free(track->parser_priv->fontdata); + track->parser_priv->fontname = 0; + track->parser_priv->fontdata = 0; + track->parser_priv->fontdata_size = 0; + track->parser_priv->fontdata_used = 0; + return 0; +} + +static int process_fonts_line(ass_track_t* track, char *str) +{ + int len; + + if (!strncmp(str, "fontname:", 9)) { + char* p = str + 9; + skip_spaces(&p); + if (track->parser_priv->fontname) { + decode_font(track); + } + track->parser_priv->fontname = strdup(p); + mp_msg(MSGT_ASS, MSGL_V, "fontname: %s\n", track->parser_priv->fontname); + return 0; + } + + if (!track->parser_priv->fontname) { + mp_msg(MSGT_ASS, MSGL_V, "Not understood: %s \n", str); + return 0; + } + + len = strlen(str); + if (len > 80) { + mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FontLineTooLong, len, str); + return 0; + } + if (track->parser_priv->fontdata_used + len > track->parser_priv->fontdata_size) { + track->parser_priv->fontdata_size += 100 * 1024; + track->parser_priv->fontdata = realloc(track->parser_priv->fontdata, track->parser_priv->fontdata_size); + } + memcpy(track->parser_priv->fontdata + track->parser_priv->fontdata_used, str, len); + track->parser_priv->fontdata_used += len; + + return 0; +} + +/** + * \brief Parse a header line + * \param track track + * \param str string to parse, zero-terminated +*/ +static int process_line(ass_track_t* track, char *str) +{ + if (strstr(str, "[Script Info]")) { // FIXME: strstr to skip possible BOM at the beginning of the script + track->parser_priv->state = PST_INFO; + } else if (!strncmp(str, "[V4 Styles]", 11)) { + track->parser_priv->state = PST_STYLES; + track->track_type = TRACK_TYPE_SSA; + } else if (!strncmp(str, "[V4+ Styles]", 12)) { + track->parser_priv->state = PST_STYLES; + track->track_type = TRACK_TYPE_ASS; + } else if (!strncmp(str, "[Events]", 8)) { + track->parser_priv->state = PST_EVENTS; + } else if (!strncmp(str, "[Fonts]", 7)) { + track->parser_priv->state = PST_FONTS; + } else { + switch (track->parser_priv->state) { + case PST_INFO: + process_info_line(track, str); + break; + case PST_STYLES: + process_styles_line(track, str); + break; + case PST_EVENTS: + process_events_line(track, str); + break; + case PST_FONTS: + process_fonts_line(track, str); + break; + default: + break; + } + } + + // there is no explicit end-of-font marker in ssa/ass + if ((track->parser_priv->state != PST_FONTS) && (track->parser_priv->fontname)) + decode_font(track); + + return 0; +} + +static int process_text(ass_track_t* track, char* str) +{ + char* p = str; + while(1) { + char* q; + for (;((*p=='\r')||(*p=='\n'));++p) {} + for (q=p; ((*q!='\0')&&(*q!='\r')&&(*q!='\n')); ++q) {}; + if (q==p) + break; + if (*q != '\0') + *(q++) = '\0'; + process_line(track, p); + if (*q == '\0') + break; + p = q; + } + return 0; +} + +/** + * \brief Process CodecPrivate section of subtitle stream + * \param track track + * \param data string to parse + * \param size length of data + CodecPrivate section contains [Stream Info] and [V4+ Styles] ([V4 Styles] for SSA) sections +*/ +void ass_process_codec_private(ass_track_t* track, char *data, int size) +{ + char* str = malloc(size + 1); + int sid; + + memcpy(str, data, size); + str[size] = '\0'; + + process_text(track, str); + free(str); + + // add "Default" style to the end + // will be used if track does not contain a default style (or even does not contain styles at all) + sid = ass_alloc_style(track); + track->styles[sid].Name = strdup("Default"); + track->styles[sid].FontName = strdup("Arial"); + + if (!track->event_format) { + // probably an mkv produced by ancient mkvtoolnix + // such files don't have [Events] and Format: headers + track->parser_priv->state = PST_EVENTS; + if (track->track_type == TRACK_TYPE_SSA) + track->event_format = strdup("Format: Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text"); + else + track->event_format = strdup("Format: Layer, Start, End, Style, Actor, MarginL, MarginR, MarginV, Effect, Text"); + } + + process_force_style(track); +} + +static int check_duplicate_event(ass_track_t* track, int ReadOrder) +{ + int i; + for (i = 0; in_events - 1; ++i) // ignoring last event, it is the one we are comparing with + if (track->events[i].ReadOrder == ReadOrder) + return 1; + return 0; +} + +/** + * \brief Process a chunk of subtitle stream data. In matroska, this containes exactly 1 event (or a commentary) + * \param track track + * \param data string to parse + * \param size length of data + * \param timecode starting time of the event (milliseconds) + * \param duration duration of the event (milliseconds) +*/ +void ass_process_chunk(ass_track_t* track, char *data, int size, long long timecode, long long duration) +{ + char* str; + int eid; + char* p; + char* token; + ass_event_t* event; + + if (!track->event_format) { + mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_EventFormatHeaderMissing); + return; + } + + str = malloc(size + 1); + memcpy(str, data, size); + str[size] = '\0'; + mp_msg(MSGT_ASS, MSGL_V, "event at %" PRId64 ", +%" PRId64 ": %s \n", (int64_t)timecode, (int64_t)duration, str); + + eid = ass_alloc_event(track); + event = track->events + eid; + + p = str; + + do { + NEXT(p, token); + event->ReadOrder = atoi(token); + if (check_duplicate_event(track, event->ReadOrder)) + break; + + NEXT(p, token); + event->Layer = atoi(token); + + process_event_tail(track, event, p, 3); + + event->Start = timecode; + event->Duration = duration; + + free(str); + return; +// dump_events(tid); + } while (0); + // some error + ass_free_event(track, eid); + track->n_events--; + free(str); +} + +#ifdef USE_ICONV +/** \brief recode buffer to utf-8 + * constraint: codepage != 0 + * \param data pointer to text buffer + * \param size buffer size + * \return a pointer to recoded buffer, caller is responsible for freeing it +**/ +static char* sub_recode(char* data, size_t size, char* codepage) +{ + static iconv_t icdsc = (iconv_t)(-1); + char* tocp = "UTF-8"; + char* outbuf; + assert(codepage); + + { + char* cp_tmp = codepage ? strdup(codepage) : 0; +#ifdef HAVE_ENCA + char enca_lang[3], enca_fallback[100]; + if (sscanf(codepage, "enca:%2s:%99s", enca_lang, enca_fallback) == 2 + || sscanf(codepage, "ENCA:%2s:%99s", enca_lang, enca_fallback) == 2) { + cp_tmp = guess_buffer_cp((unsigned char*)data, size, enca_lang, enca_fallback); + } +#endif + if ((icdsc = iconv_open (tocp, cp_tmp)) != (iconv_t)(-1)){ + mp_msg(MSGT_ASS,MSGL_V,"LIBSUB: opened iconv descriptor.\n"); + } else + mp_msg(MSGT_ASS,MSGL_ERR,MSGTR_LIBASS_ErrorOpeningIconvDescriptor); +#ifdef HAVE_ENCA + if (cp_tmp) free(cp_tmp); +#endif + } + + { + size_t osize = size; + size_t ileft = size; + size_t oleft = size - 1; + char* ip; + char* op; + size_t rc; + + outbuf = malloc(size); + ip = data; + op = outbuf; + + while (ileft) { + rc = iconv(icdsc, &ip, &ileft, &op, &oleft); + if (rc == (size_t)(-1)) { + if (errno == E2BIG) { + int offset = op - outbuf; + outbuf = (char*)realloc(outbuf, osize + size); + op = outbuf + offset; + osize += size; + oleft += size; + } else { + mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_ErrorRecodingFile); + return NULL; + } + } + } + outbuf[osize - oleft - 1] = 0; + } + + if (icdsc != (iconv_t)(-1)) { + (void)iconv_close(icdsc); + icdsc = (iconv_t)(-1); + mp_msg(MSGT_ASS,MSGL_V,"LIBSUB: closed iconv descriptor.\n"); + } + + return outbuf; +} +#endif // ICONV + +/** + * \brief read file contents into newly allocated buffer + * \param fname file name + * \param bufsize out: file size + * \return pointer to file contents. Caller is responsible for its deallocation. + */ +static char* read_file(char* fname, size_t *bufsize) +{ + int res; + long sz; + long bytes_read; + char* buf; + + FILE* fp = fopen(fname, "rb"); + if (!fp) { + mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FopenFailed, fname); + return 0; + } + res = fseek(fp, 0, SEEK_END); + if (res == -1) { + mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FseekFailed, fname); + fclose(fp); + return 0; + } + + sz = ftell(fp); + rewind(fp); + + if (sz > 10*1024*1024) { + mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_RefusingToLoadSubtitlesLargerThan10M, fname); + fclose(fp); + return 0; + } + + mp_msg(MSGT_ASS, MSGL_V, "file size: %ld\n", sz); + + buf = malloc(sz + 1); + assert(buf); + bytes_read = 0; + do { + res = fread(buf + bytes_read, 1, sz - bytes_read, fp); + if (res <= 0) { + mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_ReadFailed, errno, strerror(errno)); + fclose(fp); + free(buf); + return 0; + } + bytes_read += res; + } while (sz - bytes_read > 0); + buf[sz] = '\0'; + fclose(fp); + + if (bufsize) + *bufsize = sz; + return buf; +} + +/* + * \param buf pointer to subtitle text in utf-8 + */ +static ass_track_t* parse_memory(ass_library_t* library, char* buf) +{ + ass_track_t* track; + int i; + + track = ass_new_track(library); + + // process header + process_text(track, buf); + + // external SSA/ASS subs does not have ReadOrder field + for (i = 0; i < track->n_events; ++i) + track->events[i].ReadOrder = i; + + // there is no explicit end-of-font marker in ssa/ass + if (track->parser_priv->fontname) + decode_font(track); + + if (track->track_type == TRACK_TYPE_UNKNOWN) { + ass_free_track(track); + return 0; + } + + process_force_style(track); + + return track; +} + +/** + * \brief Read subtitles from memory. + * \param library libass library object + * \param buf pointer to subtitles text + * \param bufsize size of buffer + * \param codepage recode buffer contents from given codepage + * \return newly allocated track +*/ +ass_track_t* ass_read_memory(ass_library_t* library, char* buf, size_t bufsize, char* codepage) +{ + ass_track_t* track; + int need_free = 0; + + if (!buf) + return 0; + +#ifdef USE_ICONV + if (codepage) + buf = sub_recode(buf, bufsize, codepage); + if (!buf) + return 0; + else + need_free = 1; +#endif + track = parse_memory(library, buf); + if (need_free) + free(buf); + if (!track) + return 0; + + mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_AddedSubtitleFileMemory, track->n_styles, track->n_events); + return track; +} + +/** + * \brief Read subtitles from file. + * \param library libass library object + * \param fname file name + * \param codepage recode buffer contents from given codepage + * \return newly allocated track +*/ +ass_track_t* ass_read_file(ass_library_t* library, char* fname, char* codepage) +{ + char* buf; + ass_track_t* track; + size_t bufsize; + + buf = read_file(fname, &bufsize); + if (!buf) + return 0; +#ifdef USE_ICONV + if (codepage) { + char* tmpbuf = sub_recode(buf, bufsize, codepage); + free(buf); + buf = tmpbuf; + } + if (!buf) + return 0; +#endif + track = parse_memory(library, buf); + free(buf); + if (!track) + return 0; + + track->name = strdup(fname); + + mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_AddedSubtitleFileFname, fname, track->n_styles, track->n_events); + +// dump_events(forced_tid); + return track; +} + +/** + * \brief read styles from file into already initialized track + */ +int ass_read_styles(ass_track_t* track, char* fname, char* codepage) +{ + char* buf; + parser_state_t old_state; + size_t sz; + + buf = read_file(fname, &sz); + if (!buf) + return 1; +#ifdef USE_ICONV + if (codepage) { + char* tmpbuf; + tmpbuf = sub_recode(buf, sz, codepage); + free(buf); + buf = tmpbuf; + } + if (!buf) + return 0; +#endif + + old_state = track->parser_priv->state; + track->parser_priv->state = PST_STYLES; + process_text(track, buf); + track->parser_priv->state = old_state; + + return 0; +} + +long long ass_step_sub(ass_track_t* track, long long now, int movement) { + int i; + + if (movement == 0) return 0; + if (track->n_events == 0) return 0; + + if (movement < 0) + for (i = 0; (i < track->n_events) && ((long long)(track->events[i].Start + track->events[i].Duration) <= now); ++i) {} + else + for (i = track->n_events - 1; (i >= 0) && ((long long)(track->events[i].Start) > now); --i) {} + + // -1 and n_events are ok + assert(i >= -1); assert(i <= track->n_events); + i += movement; + if (i < 0) i = 0; + if (i >= track->n_events) i = track->n_events - 1; + return ((long long)track->events[i].Start) - now; +} + +ass_track_t* ass_new_track(ass_library_t* library) { + ass_track_t* track = calloc(1, sizeof(ass_track_t)); + track->library = library; + track->parser_priv = calloc(1, sizeof(parser_priv_t)); + return track; +} + diff --git a/libass/ass.h b/libass/ass.h new file mode 100644 index 000000000..7cba671ff --- /dev/null +++ b/libass/ass.h @@ -0,0 +1,198 @@ +// -*- c-basic-offset: 8; indent-tabs-mode: t -*- +// vim:ts=8:sw=8:noet:ai: +/* + Copyright (C) 2006 Evgeniy Stepanov + + This program 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. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef __ASS_H__ +#define __ASS_H__ + +#include "ass_types.h" + +/// Libass renderer object. Contents are private. +typedef struct ass_renderer_s ass_renderer_t; + +/// a linked list of images produced by ass renderer +typedef struct ass_image_s { + int w, h; // bitmap width/height + int stride; // bitmap stride + unsigned char* bitmap; // 1bpp stride*h alpha buffer + uint32_t color; // RGBA + int dst_x, dst_y; // bitmap placement inside the video frame + + struct ass_image_s* next; // linked list +} ass_image_t; + +/** + * \brief initialize the library + * \return library handle or NULL if failed + */ +ass_library_t* ass_library_init(void); + +/** + * \brief finalize the library + * \param priv library handle + */ +void ass_library_done(ass_library_t*); + +/** + * \brief set private font directory + * It is used for saving embedded fonts and also in font lookup. + */ +void ass_set_fonts_dir(ass_library_t* priv, const char* fonts_dir); + +void ass_set_extract_fonts(ass_library_t* priv, int extract); + +void ass_set_style_overrides(ass_library_t* priv, char** list); + +/** + * \brief initialize the renderer + * \param priv library handle + * \return renderer handle or NULL if failed + */ +ass_renderer_t* ass_renderer_init(ass_library_t*); + +/** + * \brief finalize the renderer + * \param priv renderer handle + */ +void ass_renderer_done(ass_renderer_t* priv); + +void ass_set_frame_size(ass_renderer_t* priv, int w, int h); +void ass_set_margins(ass_renderer_t* priv, int t, int b, int l, int r); +void ass_set_use_margins(ass_renderer_t* priv, int use); +void ass_set_aspect_ratio(ass_renderer_t* priv, double ar); +void ass_set_font_scale(ass_renderer_t* priv, double font_scale); + +/** + * \brief set font lookup defaults + */ +int ass_set_fonts(ass_renderer_t* priv, const char* default_font, const char* default_family); + +/** + * \brief render a frame, producing a list of ass_image_t + * \param priv library + * \param track subtitle track + * \param now video timestamp in milliseconds + */ +ass_image_t* ass_render_frame(ass_renderer_t *priv, ass_track_t* track, long long now, int* detect_change); + + +// The following functions operate on track objects and do not need an ass_renderer // + +/** + * \brief allocate a new empty track object + * \return pointer to empty track + */ +ass_track_t* ass_new_track(ass_library_t*); + +/** + * \brief deallocate track and all its child objects (styles and events) + * \param track track to deallocate + */ +void ass_free_track(ass_track_t* track); + +/** + * \brief allocate new style + * \param track track + * \return newly allocated style id + */ +int ass_alloc_style(ass_track_t* track); + +/** + * \brief allocate new event + * \param track track + * \return newly allocated event id + */ +int ass_alloc_event(ass_track_t* track); + +/** + * \brief delete a style + * \param track track + * \param sid style id + * Deallocates style data. Does not modify track->n_styles. + */ +void ass_free_style(ass_track_t* track, int sid); + +/** + * \brief delete an event + * \param track track + * \param eid event id + * Deallocates event data. Does not modify track->n_events. + */ +void ass_free_event(ass_track_t* track, int eid); + +/** + * \brief Process Codec Private section of subtitle stream + * \param track target track + * \param data string to parse + * \param size length of data + */ +void ass_process_codec_private(ass_track_t* track, char *data, int size); + +/** + * \brief Process a chunk of subtitle stream data. In matroska, this containes exactly 1 event (or a commentary) + * \param track track + * \param data string to parse + * \param size length of data + * \param timecode starting time of the event (milliseconds) + * \param duration duration of the event (milliseconds) +*/ +void ass_process_chunk(ass_track_t* track, char *data, int size, long long timecode, long long duration); + +/** + * \brief Read subtitles from file. + * \param fname file name + * \return newly allocated track +*/ +ass_track_t* ass_read_file(ass_library_t* library, char* fname, char* codepage); + +/** + * \brief Read subtitles from memory. + * \param library libass library object + * \param buf pointer to subtitles text + * \param bufsize size of buffer + * \param codepage recode buffer contents from given codepage + * \return newly allocated track +*/ +ass_track_t* ass_read_memory(ass_library_t* library, char* buf, size_t bufsize, char* codepage); +/** + * \brief read styles from file into already initialized track + * \return 0 on success + */ +int ass_read_styles(ass_track_t* track, char* fname, char* codepage); + +/** + * \brief Add a memory font. + * \param name attachment name + * \param data binary font data + * \param data_size data size +*/ +void ass_add_font(ass_library_t* library, char* name, char* data, int data_size); + +/** + * \brief Calculates timeshift from now to the start of some other subtitle event, depending on movement parameter + * \param track subtitle track + * \param now current time, ms + * \param movement how many events to skip from the one currently displayed + * +2 means "the one after the next", -1 means "previous" + * \return timeshift, ms + */ +long long ass_step_sub(ass_track_t* track, long long now, int movement); + +#endif + diff --git a/libass/ass_bitmap.c b/libass/ass_bitmap.c new file mode 100644 index 000000000..6a686673c --- /dev/null +++ b/libass/ass_bitmap.c @@ -0,0 +1,272 @@ +// -*- c-basic-offset: 8; indent-tabs-mode: t -*- +// vim:ts=8:sw=8:noet:ai: +/* + Copyright (C) 2006 Evgeniy Stepanov + + This program 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. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include +#include +#include +#include +#include +#include FT_GLYPH_H + +#include "mputils.h" +#include "ass_bitmap.h" + +struct ass_synth_priv_s { + int tmp_w, tmp_h; + unsigned short* tmp; + + int g_r; + int g_w; + + unsigned *g; + unsigned *gt2; +}; + +static const unsigned int maxcolor = 255; +static const unsigned base = 256; +static const double blur_radius = 1.5; + +static int generate_tables(ass_synth_priv_t* priv, double radius) +{ + double A = log(1.0/base)/(radius*radius*2); + int mx, i; + double volume_diff, volume_factor = 0; + unsigned volume; + + priv->g_r = ceil(radius); + priv->g_w = 2*priv->g_r+1; + + if (priv->g_r) { + priv->g = malloc(priv->g_w * sizeof(unsigned)); + priv->gt2 = malloc(256 * priv->g_w * sizeof(unsigned)); + if (priv->g==NULL || priv->gt2==NULL) { + return -1; + } + } + + if (priv->g_r) { + // gaussian curve with volume = 256 + for (volume_diff=10000000; volume_diff>0.0000001; volume_diff*=0.5){ + volume_factor+= volume_diff; + volume=0; + for (i = 0; ig_w; ++i) { + priv->g[i] = (unsigned)(exp(A * (i-priv->g_r)*(i-priv->g_r)) * volume_factor + .5); + volume+= priv->g[i]; + } + if(volume>256) volume_factor-= volume_diff; + } + volume=0; + for (i = 0; ig_w; ++i) { + priv->g[i] = (unsigned)(exp(A * (i-priv->g_r)*(i-priv->g_r)) * volume_factor + .5); + volume+= priv->g[i]; + } + + // gauss table: + for(mx=0;mxg_w;mx++){ + for(i=0;i<256;i++){ + priv->gt2[mx+i*priv->g_w] = i*priv->g[mx]; + } + } + } + + return 0; +} + +static void resize_tmp(ass_synth_priv_t* priv, int w, int h) +{ + if (priv->tmp_w >= w && priv->tmp_h >= h) + return; + if (priv->tmp_w == 0) + priv->tmp_w = 64; + if (priv->tmp_h == 0) + priv->tmp_h = 64; + while (priv->tmp_w < w) priv->tmp_w *= 2; + while (priv->tmp_h < h) priv->tmp_h *= 2; + if (priv->tmp) + free(priv->tmp); + priv->tmp = malloc((priv->tmp_w + 1) * priv->tmp_h * sizeof(short)); +} + +ass_synth_priv_t* ass_synth_init() +{ + ass_synth_priv_t* priv = calloc(1, sizeof(ass_synth_priv_t)); + generate_tables(priv, blur_radius); + return priv; +} + +void ass_synth_done(ass_synth_priv_t* priv) +{ + if (priv->tmp) + free(priv->tmp); + if (priv->g) + free(priv->g); + if (priv->gt2) + free(priv->gt2); + free(priv); +} + +static bitmap_t* alloc_bitmap(int w, int h) +{ + bitmap_t* bm; + bm = calloc(1, sizeof(bitmap_t)); + bm->buffer = malloc(w*h); + bm->w = w; + bm->h = h; + bm->left = bm->top = 0; + return bm; +} + +void ass_free_bitmap(bitmap_t* bm) +{ + if (bm) { + if (bm->buffer) free(bm->buffer); + free(bm); + } +} + +static bitmap_t* copy_bitmap(const bitmap_t* src) +{ + bitmap_t* dst = alloc_bitmap(src->w, src->h); + dst->left = src->left; + dst->top = src->top; + memcpy(dst->buffer, src->buffer, src->w * src->h); + return dst; +} + +static bitmap_t* glyph_to_bitmap_internal(FT_Glyph glyph, int bord) +{ + FT_BitmapGlyph bg; + FT_Bitmap* bit; + bitmap_t* bm; + int w, h; + unsigned char* src; + unsigned char* dst; + int i; + int error; + + error = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 0); + if (error) { + mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FT_Glyph_To_BitmapError, error); + return 0; + } + + bg = (FT_BitmapGlyph)glyph; + bit = &(bg->bitmap); + if (bit->pixel_mode != FT_PIXEL_MODE_GRAY) { + mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_UnsupportedPixelMode, (int)(bit->pixel_mode)); + FT_Done_Glyph(glyph); + return 0; + } + + w = bit->width; + h = bit->rows; + bm = alloc_bitmap(w + 2*bord, h + 2*bord); + memset(bm->buffer, 0, bm->w * bm->h); + bm->left = bg->left - bord; + bm->top = - bg->top - bord; + + src = bit->buffer; + dst = bm->buffer + bord + bm->w * bord; + for (i = 0; i < h; ++i) { + memcpy(dst, src, w); + src += bit->pitch; + dst += bm->w; + } + + return bm; +} + +/** + * \brief fix outline bitmap and generate shadow bitmap + * Two things are done here: + * 1. Glyph bitmap is subtracted from outline bitmap. This way looks much better in some cases. + * 2. Shadow bitmap is created as a sum of glyph and outline bitmaps. + */ +static bitmap_t* fix_outline_and_shadow(bitmap_t* bm_g, bitmap_t* bm_o) +{ + int x, y; + const int l = bm_o->left > bm_g->left ? bm_o->left : bm_g->left; + const int t = bm_o->top > bm_g->top ? bm_o->top : bm_g->top; + const int r = bm_o->left + bm_o->w < bm_g->left + bm_g->w ? bm_o->left + bm_o->w : bm_g->left + bm_g->w; + const int b = bm_o->top + bm_o->h < bm_g->top + bm_g->h ? bm_o->top + bm_o->h : bm_g->top + bm_g->h; + + bitmap_t* bm_s = copy_bitmap(bm_o); + + unsigned char* g = bm_g->buffer + (t - bm_g->top) * bm_g->w + (l - bm_g->left); + unsigned char* o = bm_o->buffer + (t - bm_o->top) * bm_o->w + (l - bm_o->left); + unsigned char* s = bm_s->buffer + (t - bm_s->top) * bm_s->w + (l - bm_s->left); + + for (y = 0; y < b - t; ++y) { + for (x = 0; x < r - l; ++x) { + unsigned char c_g, c_o; + c_g = g[x]; + c_o = o[x]; + o[x] = (c_o > c_g) ? c_o : 0; + s[x] = (c_o < 0xFF - c_g) ? c_o + c_g : 0xFF; + } + g += bm_g->w; + o += bm_o->w; + s += bm_s->w; + } + + assert(bm_s); + return bm_s; +} + +int glyph_to_bitmap(ass_synth_priv_t* priv, FT_Glyph glyph, FT_Glyph outline_glyph, + bitmap_t** bm_g, bitmap_t** bm_o, bitmap_t** bm_s, int be) +{ + const int bord = be ? ceil(blur_radius) : 0; + + assert(bm_g && bm_o && bm_s); + + *bm_g = *bm_o = *bm_s = 0; + + if (glyph) + *bm_g = glyph_to_bitmap_internal(glyph, bord); + if (!*bm_g) + return 1; + + if (outline_glyph) { + *bm_o = glyph_to_bitmap_internal(outline_glyph, bord); + if (!*bm_o) { + ass_free_bitmap(*bm_g); + return 1; + } + } + if (*bm_o) + resize_tmp(priv, (*bm_o)->w, (*bm_o)->h); + resize_tmp(priv, (*bm_g)->w, (*bm_g)->h); + + if (be) { + blur((*bm_g)->buffer, priv->tmp, (*bm_g)->w, (*bm_g)->h, (*bm_g)->w, (int*)priv->gt2, priv->g_r, priv->g_w); + if (*bm_o) + blur((*bm_o)->buffer, priv->tmp, (*bm_o)->w, (*bm_o)->h, (*bm_o)->w, (int*)priv->gt2, priv->g_r, priv->g_w); + } + + if (*bm_o) + *bm_s = fix_outline_and_shadow(*bm_g, *bm_o); + else + *bm_s = copy_bitmap(*bm_g); + + assert(bm_s); + return 0; +} + diff --git a/libass/ass_bitmap.h b/libass/ass_bitmap.h new file mode 100644 index 000000000..a4090636c --- /dev/null +++ b/libass/ass_bitmap.h @@ -0,0 +1,49 @@ +// -*- c-basic-offset: 8; indent-tabs-mode: t -*- +// vim:ts=8:sw=8:noet:ai: +/* + Copyright (C) 2006 Evgeniy Stepanov + + This program 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. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef __ASS_BITMAP_H__ +#define __ASS_BITMAP_H__ + +typedef struct ass_synth_priv_s ass_synth_priv_t; + +ass_synth_priv_t* ass_synth_init(); +void ass_synth_done(ass_synth_priv_t* priv); + +typedef struct bitmap_s { + int left, top; + int w, h; // width, height + unsigned char* buffer; // w x h buffer +} bitmap_t; + +/** + * \brief perform glyph rendering + * \param glyph original glyph + * \param outline_glyph "border" glyph, produced from original by FreeType's glyph stroker + * \param bm_g out: pointer to the bitmap of original glyph is returned here + * \param bm_o out: pointer to the bitmap of outline (border) glyph is returned here + * \param bm_g out: pointer to the bitmap of glyph shadow is returned here + * \param be 1 = produces blurred bitmaps, 0 = normal bitmaps + */ +int glyph_to_bitmap(ass_synth_priv_t* priv, FT_Glyph glyph, FT_Glyph outline_glyph, bitmap_t** bm_g, bitmap_t** bm_o, bitmap_t** bm_s, int be); + +void ass_free_bitmap(bitmap_t* bm); + +#endif + diff --git a/libass/ass_cache.c b/libass/ass_cache.c new file mode 100644 index 000000000..463fdb50a --- /dev/null +++ b/libass/ass_cache.c @@ -0,0 +1,220 @@ +// -*- c-basic-offset: 8; indent-tabs-mode: t -*- +// vim:ts=8:sw=8:noet:ai: +/* + Copyright (C) 2006 Evgeniy Stepanov + + This program 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. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "config.h" + +#include +#include +#include FT_FREETYPE_H +#include FT_GLYPH_H + +#include + +#include "mputils.h" +#include "ass.h" +#include "ass_fontconfig.h" +#include "ass_font.h" +#include "ass_bitmap.h" +#include "ass_cache.h" + +#define MAX_FONT_CACHE_SIZE 100 + +static ass_font_t** font_cache; +static int font_cache_size; + +static int font_compare(ass_font_desc_t* a, ass_font_desc_t* b) { + if (strcmp(a->family, b->family) != 0) + return 0; + if (a->bold != b->bold) + return 0; + if (a->italic != b->italic) + return 0; + return 1; +} + +/** + * \brief Get a face struct from cache. + * \param desc required face description + * \return font struct +*/ +ass_font_t* ass_font_cache_find(ass_font_desc_t* desc) +{ + int i; + + for (i=0; idesc))) + return font_cache[i]; + + return 0; +} + +/** + * \brief Add a face struct to cache. + * \param font font struct +*/ +void ass_font_cache_add(ass_font_t* font) +{ + if (font_cache_size == MAX_FONT_CACHE_SIZE) { + mp_msg(MSGT_ASS, MSGL_FATAL, MSGTR_LIBASS_TooManyFonts); + // FIXME: possible memory leak + return; + } + + font_cache[font_cache_size] = font; + font_cache_size++; +} + +void ass_font_cache_init(void) +{ + font_cache = calloc(MAX_FONT_CACHE_SIZE, sizeof(ass_font_t*)); + font_cache_size = 0; +} + +void ass_font_cache_done(void) +{ + int i; + for (i = 0; i < font_cache_size; ++i) { + ass_font_t* item = font_cache[i]; + ass_font_free(item); + } + free(font_cache); + font_cache_size = 0; +} + +//--------------------------------- +// glyph cache + +#define GLYPH_HASH_SIZE (0xFFFF + 13) + +typedef struct glyph_hash_item_s { + glyph_hash_key_t key; + glyph_hash_val_t val; + struct glyph_hash_item_s* next; +} glyph_hash_item_t; + +typedef glyph_hash_item_t* glyph_hash_item_p; + +static glyph_hash_item_p* glyph_hash_root; +static int glyph_hash_size; + +static int glyph_compare(glyph_hash_key_t* a, glyph_hash_key_t* b) { + if (memcmp(a, b, sizeof(glyph_hash_key_t)) == 0) + return 1; + else + return 0; +} + +static unsigned glyph_hash(glyph_hash_key_t* key) { + unsigned val = 0; + unsigned i; + for (i = 0; i < sizeof(key->font); ++i) + val += *(unsigned char *)(&(key->font) + i); + val <<= 21; + + if (key->bitmap) val &= 0x80000000; + if (key->be) val &= 0x40000000; + val += key->ch; + val += key->size << 8; + val += key->outline << 3; + val += key->advance.x << 10; + val += key->advance.y << 16; + val += key->bold << 1; + val += key->italic << 20; + val += key->frx; + val += key->fry << 1; + val += key->frz << 2; + return val; +} + +/** + * \brief Add a glyph to glyph cache. + * \param key hash key + * \param val hash val: 2 bitmap glyphs + some additional info +*/ +void cache_add_glyph(glyph_hash_key_t* key, glyph_hash_val_t* val) +{ + unsigned hash = glyph_hash(key); + glyph_hash_item_t** next = glyph_hash_root + (hash % GLYPH_HASH_SIZE); + while (*next) { + if (glyph_compare(key, &((*next)->key))) + return; + next = &((*next)->next); + assert(next); + } + (*next) = malloc(sizeof(glyph_hash_item_t)); +// (*next)->desc = glyph_key_copy(key, &((*next)->key)); + memcpy(&((*next)->key), key, sizeof(glyph_hash_key_t)); + memcpy(&((*next)->val), val, sizeof(glyph_hash_val_t)); + (*next)->next = 0; + + glyph_hash_size ++; +/* if (glyph_hash_size && (glyph_hash_size % 25 == 0)) { + printf("\nGlyph cache: %d entries, %d bytes\n", glyph_hash_size, glyph_hash_size * sizeof(glyph_hash_item_t)); + } */ +} + +/** + * \brief Get a glyph from glyph cache. + * \param key hash key + * \return requested hash val or 0 if not found +*/ +glyph_hash_val_t* cache_find_glyph(glyph_hash_key_t* key) +{ + unsigned hash = glyph_hash(key); + glyph_hash_item_t* item = glyph_hash_root[hash % GLYPH_HASH_SIZE]; + while (item) { + if (glyph_compare(key, &(item->key))) { + return &(item->val); + } + item = item->next; + } + return 0; +} + +void ass_glyph_cache_init(void) +{ + glyph_hash_root = calloc(GLYPH_HASH_SIZE, sizeof(glyph_hash_item_p)); + glyph_hash_size = 0; +} + +void ass_glyph_cache_done(void) +{ + int i; + for (i = 0; i < GLYPH_HASH_SIZE; ++i) { + glyph_hash_item_t* item = glyph_hash_root[i]; + while (item) { + glyph_hash_item_t* next = item->next; + if (item->val.bm) ass_free_bitmap(item->val.bm); + if (item->val.bm_o) ass_free_bitmap(item->val.bm_o); + if (item->val.bm_s) ass_free_bitmap(item->val.bm_s); + free(item); + item = next; + } + } + free(glyph_hash_root); + glyph_hash_size = 0; +} + +void ass_glyph_cache_reset(void) +{ + ass_glyph_cache_done(); + ass_glyph_cache_init(); +} + diff --git a/libass/ass_cache.h b/libass/ass_cache.h new file mode 100644 index 000000000..a4a5bf4ce --- /dev/null +++ b/libass/ass_cache.h @@ -0,0 +1,62 @@ +// -*- c-basic-offset: 8; indent-tabs-mode: t -*- +// vim:ts=8:sw=8:noet:ai: +/* + Copyright (C) 2006 Evgeniy Stepanov + + This program 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. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef __ASS_CACHE_H__ +#define __ASS_CACHE_H__ + +void ass_font_cache_init(void); +ass_font_t* ass_font_cache_find(ass_font_desc_t* desc); +void ass_font_cache_add(ass_font_t* font); +void ass_font_cache_done(void); + + +// describes a glyph; glyphs with equivalents structs are considered identical +typedef struct glyph_hash_key_s { + char bitmap; // bool : true = bitmap, false = outline + ass_font_t* font; + int size; // font size + uint32_t ch; // character code + unsigned outline; // border width, 16.16 fixed point value + int bold, italic; + char be; // blur edges + + // the following affects bitmap glyphs only + unsigned scale_x, scale_y; // 16.16 + int frx, fry, frz; // signed 16.16 + + FT_Vector advance; // subpixel shift vector +} glyph_hash_key_t; + +typedef struct glyph_hash_val_s { + bitmap_t* bm; // the actual glyph bitmaps + bitmap_t* bm_o; + bitmap_t* bm_s; + FT_BBox bbox_scaled; // bbox after scaling, but before rotation + FT_Vector advance; // 26.6, advance distance to the next glyph in line +} glyph_hash_val_t; + +void ass_glyph_cache_init(void); +void cache_add_glyph(glyph_hash_key_t* key, glyph_hash_val_t* val); +glyph_hash_val_t* cache_find_glyph(glyph_hash_key_t* key); +void ass_glyph_cache_reset(void); +void ass_glyph_cache_done(void); + +#endif + diff --git a/libass/ass_font.c b/libass/ass_font.c new file mode 100644 index 000000000..285ecde63 --- /dev/null +++ b/libass/ass_font.c @@ -0,0 +1,327 @@ +// -*- c-basic-offset: 8; indent-tabs-mode: t -*- +// vim:ts=8:sw=8:noet:ai: +/* + Copyright (C) 2006 Evgeniy Stepanov + + This program 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. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "config.h" + +#include +#include +#include FT_FREETYPE_H +#include FT_SYNTHESIS_H +#include FT_GLYPH_H + +#include "ass.h" +#include "ass_library.h" +#include "ass_font.h" +#include "ass_bitmap.h" +#include "ass_cache.h" +#include "ass_fontconfig.h" +#include "mputils.h" + +/** + * Select Microfost Unicode CharMap, if the font has one. + * Otherwise, let FreeType decide. + */ +static void charmap_magic(FT_Face face) +{ + int i; + for (i = 0; i < face->num_charmaps; ++i) { + FT_CharMap cmap = face->charmaps[i]; + unsigned pid = cmap->platform_id; + unsigned eid = cmap->encoding_id; + if (pid == 3 /*microsoft*/ && (eid == 1 /*unicode bmp*/ || eid == 10 /*full unicode*/)) { + FT_Set_Charmap(face, cmap); + return; + } + } + + if (!face->charmap) { + if (face->num_charmaps == 0) { + mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_NoCharmaps); + return; + } + mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_NoCharmapAutodetected); + FT_Set_Charmap(face, face->charmaps[0]); + return; + } +} + +/** + * \brief find a memory font by name + */ +static int find_font(ass_library_t* library, char* name) +{ + int i; + for (i = 0; i < library->num_fontdata; ++i) + if (strcasecmp(name, library->fontdata[i].name) == 0) + return i; + return -1; +} + +/** + * \brief Create a new ass_font_t according to "desc" argument + */ +ass_font_t* ass_font_new(ass_library_t* library, FT_Library ftlibrary, void* fc_priv, ass_font_desc_t* desc) +{ + char* path; + int index; + FT_Face face; + int error; + ass_font_t* font; + int mem_idx; + + font = ass_font_cache_find(desc); + if (font) + return font; + + path = fontconfig_select(fc_priv, desc->family, desc->bold, desc->italic, &index); + + mem_idx = find_font(library, path); + if (mem_idx >= 0) { + error = FT_New_Memory_Face(ftlibrary, (unsigned char*)library->fontdata[mem_idx].data, + library->fontdata[mem_idx].size, 0, &face); + if (error) { + mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_ErrorOpeningMemoryFont, path); + return 0; + } + } else { + error = FT_New_Face(ftlibrary, path, index, &face); + if (error) { + mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_ErrorOpeningFont, path, index); + return 0; + } + } + + charmap_magic(face); + + font = calloc(1, sizeof(ass_font_t)); + font->ftlibrary = ftlibrary; + font->faces[0] = face; + font->n_faces = 1; + font->desc.family = strdup(desc->family); + font->desc.bold = desc->bold; + font->desc.italic = desc->italic; + + font->m.xx = font->m.yy = (FT_Fixed)0x10000L; + font->m.xy = font->m.yy = 0; + font->v.x = font->v.y = 0; + font->size = 0; + +#ifdef HAVE_FONTCONFIG + font->charset = FcCharSetCreate(); +#endif + + ass_font_cache_add(font); + + return font; +} + +/** + * \brief Set font transformation matrix and shift vector + **/ +void ass_font_set_transform(ass_font_t* font, FT_Matrix* m, FT_Vector* v) +{ + int i; + font->m.xx = m->xx; + font->m.xy = m->xy; + font->m.yx = m->yx; + font->m.yy = m->yy; + font->v.x = v->x; + font->v.y = v->y; + for (i = 0; i < font->n_faces; ++i) + FT_Set_Transform(font->faces[i], &font->m, &font->v); +} + +/** + * \brief Set font size + **/ +void ass_font_set_size(ass_font_t* font, int size) +{ + int i; + if (font->size != size) { + font->size = size; + for (i = 0; i < font->n_faces; ++i) + FT_Set_Pixel_Sizes(font->faces[i], 0, size); + } +} + +#ifdef HAVE_FONTCONFIG +/** + * \brief Select a new FT_Face with the given character + * The new face is added to the end of font->faces. + **/ +static void ass_font_reselect(void* fontconfig_priv, ass_font_t* font, uint32_t ch) +{ + char* path; + int index; + FT_Face face; + int error; + + if (font->n_faces == ASS_FONT_MAX_FACES) + return; + + path = fontconfig_select_with_charset(fontconfig_priv, font->desc.family, font->desc.bold, + font->desc.italic, &index, font->charset); + + error = FT_New_Face(font->ftlibrary, path, index, &face); + if (error) { + mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_ErrorOpeningFont, path, index); + return; + } + charmap_magic(face); + + error = FT_Get_Char_Index(face, ch); + if (error == 0) { // the new font face is not better then the old one + FT_Done_Face(face); + return; + } + + font->faces[font->n_faces++] = face; + + FT_Set_Transform(face, &font->m, &font->v); + FT_Set_Pixel_Sizes(face, 0, font->size); +} +#endif + +/** + * \brief Get maximal font ascender and descender. + * \param ch character code + * The values are extracted from the font face that provides glyphs for the given character + **/ +void ass_font_get_asc_desc(ass_font_t* font, uint32_t ch, int* asc, int* desc) +{ + int i; + for (i = 0; i < font->n_faces; ++i) { + FT_Face face = font->faces[i]; + if (FT_Get_Char_Index(face, ch)) { + int v, v2; + v = face->size->metrics.ascender; + v2 = FT_MulFix(face->bbox.yMax, face->size->metrics.y_scale); + *asc = (v > v2 * 0.9) ? v : v2; + + v = - face->size->metrics.descender; + v2 = - FT_MulFix(face->bbox.yMin, face->size->metrics.y_scale); + *desc = (v > v2 * 0.9) ? v : v2; + return; + } + } + + *asc = *desc = 0; +} + +/** + * \brief Get a glyph + * \param ch character code + **/ +FT_Glyph ass_font_get_glyph(void* fontconfig_priv, ass_font_t* font, uint32_t ch) +{ + int error; + int index = 0; + int i; + FT_Glyph glyph; + FT_Face face = 0; + + if (ch < 0x20) + return 0; + if (font->n_faces == 0) + return 0; + + for (i = 0; i < font->n_faces; ++i) { + face = font->faces[i]; + index = FT_Get_Char_Index(face, ch); + if (index) + break; + } + +#ifdef HAVE_FONTCONFIG + FcCharSetAddChar(font->charset, ch); + if (index == 0) { + mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_GlyphNotFoundReselectingFont, + ch, font->desc.family, font->desc.bold, font->desc.italic); + ass_font_reselect(fontconfig_priv, font, ch); + face = font->faces[font->n_faces - 1]; + index = FT_Get_Char_Index(face, ch); + if (index == 0) { + mp_msg(MSGT_ASS, MSGL_ERR, MSGTR_LIBASS_GlyphNotFound, + ch, font->desc.family, font->desc.bold, font->desc.italic); + } + } +#endif + + error = FT_Load_Glyph(face, index, FT_LOAD_NO_BITMAP ); + if (error) { + mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_ErrorLoadingGlyph); + return 0; + } + +#if (FREETYPE_MAJOR > 2) || \ + ((FREETYPE_MAJOR == 2) && (FREETYPE_MINOR >= 2)) || \ + ((FREETYPE_MAJOR == 2) && (FREETYPE_MINOR == 1) && (FREETYPE_PATCH >= 10)) +// FreeType >= 2.1.10 required + if (!(face->style_flags & FT_STYLE_FLAG_ITALIC) && + (font->desc.italic > 55)) { + FT_GlyphSlot_Oblique(face->glyph); + } +#endif + error = FT_Get_Glyph(face->glyph, &glyph); + if (error) { + mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_ErrorLoadingGlyph); + return 0; + } + + return glyph; +} + +/** + * \brief Get kerning for the pair of glyphs. + **/ +FT_Vector ass_font_get_kerning(ass_font_t* font, uint32_t c1, uint32_t c2) +{ + FT_Vector v = {0, 0}; + int i; + + for (i = 0; i < font->n_faces; ++i) { + FT_Face face = font->faces[i]; + int i1 = FT_Get_Char_Index(face, c1); + int i2 = FT_Get_Char_Index(face, c2); + if (i1 && i2) { + if (FT_HAS_KERNING(face)) + FT_Get_Kerning(face, i1, i2, FT_KERNING_DEFAULT, &v); + return v; + } + if (i1 || i2) // these glyphs are from different font faces, no kerning information + return v; + } + return v; +} + +/** + * \brief Deallocate ass_font_t + **/ +void ass_font_free(ass_font_t* font) +{ + int i; + for (i = 0; i < font->n_faces; ++i) + if (font->faces[i]) FT_Done_Face(font->faces[i]); + if (font->desc.family) free(font->desc.family); +#ifdef HAVE_FONTCONFIG + if (font->charset) FcCharSetDestroy(font->charset); +#endif + free(font); +} diff --git a/libass/ass_font.h b/libass/ass_font.h new file mode 100644 index 000000000..5d986e9f0 --- /dev/null +++ b/libass/ass_font.h @@ -0,0 +1,57 @@ +// -*- c-basic-offset: 8; indent-tabs-mode: t -*- +// vim:ts=8:sw=8:noet:ai: +/* + Copyright (C) 2006 Evgeniy Stepanov + + This program 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. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef __ASS_FONT_H__ +#define __ASS_FONT_H__ + +#ifdef HAVE_FONTCONFIG +#include +#endif + +typedef struct ass_font_desc_s { + char* family; + unsigned bold; + unsigned italic; +} ass_font_desc_t; + +#define ASS_FONT_MAX_FACES 10 + +typedef struct ass_font_s { + ass_font_desc_t desc; + FT_Library ftlibrary; + FT_Face faces[ASS_FONT_MAX_FACES]; + int n_faces; + FT_Matrix m; // current transformation + FT_Vector v; // current shift + int size; +#ifdef HAVE_FONTCONFIG + FcCharSet* charset; +#endif +} ass_font_t; + +ass_font_t* ass_font_new(ass_library_t* library, FT_Library ftlibrary, void* fc_priv, ass_font_desc_t* desc); +void ass_font_set_transform(ass_font_t* font, FT_Matrix* m, FT_Vector* v); +void ass_font_set_size(ass_font_t* font, int size); +void ass_font_get_asc_desc(ass_font_t* font, uint32_t ch, int* asc, int* desc); +FT_Glyph ass_font_get_glyph(void* fontconfig_priv, ass_font_t* font, uint32_t ch); +FT_Vector ass_font_get_kerning(ass_font_t* font, uint32_t c1, uint32_t c2); +void ass_font_free(ass_font_t* font); + +#endif diff --git a/libass/ass_fontconfig.c b/libass/ass_fontconfig.c new file mode 100644 index 000000000..3d4f103ed --- /dev/null +++ b/libass/ass_fontconfig.c @@ -0,0 +1,428 @@ +// -*- c-basic-offset: 8; indent-tabs-mode: t -*- +// vim:ts=8:sw=8:noet:ai: +/* + Copyright (C) 2006 Evgeniy Stepanov + + This program 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. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include FT_FREETYPE_H + +#include "mputils.h" +#include "ass.h" +#include "ass_library.h" +#include "ass_fontconfig.h" + +#ifdef HAVE_FONTCONFIG +#include +#include +#endif + +struct fc_instance_s { +#ifdef HAVE_FONTCONFIG + FcConfig* config; +#endif + char* family_default; + char* path_default; + int index_default; +}; + +#ifdef HAVE_FONTCONFIG +/** + * \brief Low-level font selection. + * \param priv private data + * \param family font family + * \param bold font weight value + * \param italic font slant value + * \param index out: font index inside a file + * \param charset: contains the characters that should be present in the font, can be NULL + * \return font file path +*/ +static char* _select_font(fc_instance_t* priv, const char* family, unsigned bold, unsigned italic, int* index, + FcCharSet* charset) +{ + FcBool rc; + FcResult result; + FcPattern *pat, *rpat; + int val_i; + FcChar8* val_s; + FcBool val_b; + FcCharSet* val_cs; + FcFontSet* fset; + int curf, bestf, bestdiff = 0; + + *index = 0; + + pat = FcPatternCreate(); + if (!pat) + return 0; + + FcPatternAddString(pat, FC_FAMILY, (const FcChar8*)family); + FcPatternAddBool(pat, FC_OUTLINE, FcTrue); + FcPatternAddInteger(pat, FC_SLANT, italic); + FcPatternAddInteger(pat, FC_WEIGHT, bold); + + FcDefaultSubstitute(pat); + + rc = FcConfigSubstitute(priv->config, pat, FcMatchPattern); + if (!rc) + return 0; + + fset = FcFontSort(priv->config, pat, FcTrue, NULL, &result); + + bestf = -1; + if (charset) + bestdiff = FcCharSetCount(charset) + 1; + for (curf = 0; curf < fset->nfont; ++curf) { + rpat = fset->fonts[curf]; + + result = FcPatternGetBool(rpat, FC_OUTLINE, 0, &val_b); + if (result != FcResultMatch) + continue; + if (val_b != FcTrue) + continue; + + if (charset) { + int diff; + result = FcPatternGetCharSet(rpat, FC_CHARSET, 0, &val_cs); + if (result != FcResultMatch) + continue; + diff = FcCharSetSubtractCount(charset, val_cs); + if (diff < bestdiff) { + bestdiff = diff; + bestf = curf; + } + if (diff == 0) + break; + } else { + bestf = curf; + break; + } + } + + if (bestf < 0) + return 0; + + rpat = fset->fonts[bestf]; + + result = FcPatternGetInteger(rpat, FC_INDEX, 0, &val_i); + if (result != FcResultMatch) + return 0; + *index = val_i; + + result = FcPatternGetString(rpat, FC_FAMILY, 0, &val_s); + if (result != FcResultMatch) + return 0; + + if (strcasecmp((const char*)val_s, family) != 0) + mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_SelectedFontFamilyIsNotTheRequestedOne, + (const char*)val_s, family); + + result = FcPatternGetString(rpat, FC_FILE, 0, &val_s); + if (result != FcResultMatch) + return 0; + + return strdup((const char*)val_s); +} + +/** + * \brief Find a font. Use default family or path if necessary. + * \param priv_ private data + * \param family font family + * \param bold font weight value + * \param italic font slant value + * \param index out: font index inside a file + * \param charset: contains the characters that should be present in the font, can be NULL + * \return font file path +*/ +char* fontconfig_select_with_charset(fc_instance_t* priv, const char* family, unsigned bold, unsigned italic, int* index, + FcCharSet* charset) +{ + char* res = 0; + if (family && *family) + res = _select_font(priv, family, bold, italic, index, charset); + if (!res && priv->family_default) { + res = _select_font(priv, priv->family_default, bold, italic, index, charset); + if (res) + mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_UsingDefaultFontFamily, + family, bold, italic, res, *index); + } + if (!res && priv->path_default) { + res = priv->path_default; + *index = priv->index_default; + mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_UsingDefaultFont, + family, bold, italic, res, *index); + } + if (!res) { + res = _select_font(priv, "Arial", bold, italic, index, charset); + if (res) + mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_UsingArialFontFamily, + family, bold, italic, res, *index); + } + if (res) + mp_msg(MSGT_ASS, MSGL_V, "fontconfig_select: (%s, %d, %d) -> %s, %d\n", + family, bold, italic, res, *index); + return res; +} + +char* fontconfig_select(fc_instance_t* priv, const char* family, unsigned bold, unsigned italic, int* index) +{ + return fontconfig_select_with_charset(priv, family, bold, italic, index, 0); +} + +static char* validate_fname(char* name) +{ + char* fname; + char* p; + char* q; + unsigned code; + int sz = strlen(name); + + q = fname = malloc(sz + 1); + p = name; + while (*p) { + code = utf8_get_char(&p); + if (code == 0) + break; + if ( (code > 0x7F) || + (code == '\\') || + (code == '/') || + (code == ':') || + (code == '*') || + (code == '?') || + (code == '<') || + (code == '>') || + (code == '|') || + (code == 0)) + { + *q++ = '_'; + } else { + *q++ = code; + } + if (p - name > sz) + break; + } + *q = 0; + return fname; +} + +/** + * \brief Process memory font. + * \param priv private data + * \param library library object + * \param ftlibrary freetype library object + * \param idx index of the processed font in library->fontdata + * With FontConfig >= 2.4.2, builds a font pattern in memory via FT_New_Memory_Face/FcFreeTypeQueryFace. + * With older FontConfig versions, save the font to ~/.mplayer/fonts. +*/ +static void process_fontdata(fc_instance_t* priv, ass_library_t* library, FT_Library ftlibrary, int idx) +{ + char buf[1000]; + FILE* fp = 0; + int rc; + struct stat st; + char* fname; + const char* name = library->fontdata[idx].name; + const char* data = library->fontdata[idx].data; + int data_size = library->fontdata[idx].size; + const char* fonts_dir = library->fonts_dir; + FT_Face face; + FcPattern* pattern; + FcFontSet* fset; + FcBool res; + +#if (FC_VERSION < 20402) + if (!fonts_dir) + return; + rc = stat(fonts_dir, &st); + if (rc) { + int res; +#ifndef __MINGW32__ + res = mkdir(fonts_dir, 0700); +#else + res = mkdir(fonts_dir); +#endif + if (res) { + mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FailedToCreateDirectory, fonts_dir); + } + } else if (!S_ISDIR(st.st_mode)) { + mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_NotADirectory, fonts_dir); + } + + fname = validate_fname((char*)name); + + snprintf(buf, 1000, "%s/%s", fonts_dir, fname); + free(fname); + + fp = fopen(buf, "wb"); + if (!fp) return; + + fwrite(data, data_size, 1, fp); + fclose(fp); + +#else // (FC_VERSION >= 20402) + + rc = FT_New_Memory_Face(ftlibrary, (unsigned char*)data, data_size, 0, &face); + if (rc) { + mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_ErrorOpeningMemoryFont, name); + return; + } + + pattern = FcFreeTypeQueryFace(face, (unsigned char*)name, 0, FcConfigGetBlanks(priv->config)); + if (!pattern) { + mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FunctionCallFailed, "FcFreeTypeQueryFace"); + FT_Done_Face(face); + return; + } + + fset = FcConfigGetFonts(priv->config, FcSetSystem); // somehow it failes when asked for FcSetApplication + if (!fset) { + mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FunctionCallFailed, "FcConfigGetFonts"); + FT_Done_Face(face); + return; + } + + res = FcFontSetAdd(fset, pattern); + if (!res) { + mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FunctionCallFailed, "FcFontSetAdd"); + FT_Done_Face(face); + return; + } + + FT_Done_Face(face); +#endif +} + +/** + * \brief Init fontconfig. + * \param library libass library object + * \param ftlibrary freetype library object + * \param family default font family + * \param path default font path + * \return pointer to fontconfig private data +*/ +fc_instance_t* fontconfig_init(ass_library_t* library, FT_Library ftlibrary, const char* family, const char* path) +{ + int rc; + struct stat st; + fc_instance_t* priv = calloc(1, sizeof(fc_instance_t)); + const char* dir = library->fonts_dir; + int i; + + rc = FcInit(); + assert(rc); + + priv->config = FcConfigGetCurrent(); + if (!priv->config) { + mp_msg(MSGT_ASS, MSGL_FATAL, MSGTR_LIBASS_FcInitLoadConfigAndFontsFailed); + return 0; + } + + for (i = 0; i < library->num_fontdata; ++i) + process_fontdata(priv, library, ftlibrary, i); + + if (FcDirCacheValid((const FcChar8 *)dir) == FcFalse) + { + mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_UpdatingFontCache); + if (FcGetVersion() >= 20390 && FcGetVersion() < 20400) + mp_msg(MSGT_ASS, MSGL_WARN, + MSGTR_LIBASS_BetaVersionsOfFontconfigAreNotSupported); + // FontConfig >= 2.4.0 updates cache automatically in FcConfigAppFontAddDir() + if (FcGetVersion() < 20390) { + FcFontSet* fcs; + FcStrSet* fss; + fcs = FcFontSetCreate(); + fss = FcStrSetCreate(); + rc = FcStrSetAdd(fss, (const FcChar8*)dir); + if (!rc) { + mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FcStrSetAddFailed); + goto ErrorFontCache; + } + + rc = FcDirScan(fcs, fss, NULL, FcConfigGetBlanks(priv->config), (const FcChar8 *)dir, FcFalse); + if (!rc) { + mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FcDirScanFailed); + goto ErrorFontCache; + } + + rc = FcDirSave(fcs, fss, (const FcChar8 *)dir); + if (!rc) { + mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FcDirSave); + goto ErrorFontCache; + } + ErrorFontCache: + ; + } + } + + rc = FcConfigAppFontAddDir(priv->config, (const FcChar8*)dir); + if (!rc) { + mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FcConfigAppFontAddDirFailed); + } + + priv->family_default = family ? strdup(family) : 0; + priv->index_default = 0; + + rc = stat(path, &st); + if (!rc && S_ISREG(st.st_mode)) + priv->path_default = path ? strdup(path) : 0; + else + priv->path_default = 0; + + return priv; +} + +#else // HAVE_FONTCONFIG + +char* fontconfig_select(fc_instance_t* priv, const char* family, unsigned bold, unsigned italic, int* index) +{ + *index = priv->index_default; + return priv->path_default; +} + +fc_instance_t* fontconfig_init(ass_library_t* library, FT_Library ftlibrary, const char* family, const char* path) +{ + fc_instance_t* priv; + + mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FontconfigDisabledDefaultFontWillBeUsed); + + priv = calloc(1, sizeof(fc_instance_t)); + + priv->path_default = strdup(path); + priv->index_default = 0; + return priv; +} + +#endif + +void fontconfig_done(fc_instance_t* priv) +{ + // don't call FcFini() here, library can still be used by some code + if (priv && priv->path_default) free(priv->path_default); + if (priv && priv->family_default) free(priv->family_default); + if (priv) free(priv); +} + + diff --git a/libass/ass_fontconfig.h b/libass/ass_fontconfig.h new file mode 100644 index 000000000..a296bcef3 --- /dev/null +++ b/libass/ass_fontconfig.h @@ -0,0 +1,39 @@ +// -*- c-basic-offset: 8; indent-tabs-mode: t -*- +// vim:ts=8:sw=8:noet:ai: +/* + Copyright (C) 2006 Evgeniy Stepanov + + This program 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. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef __ASS_FONTCONFIG_H__ +#define __ASS_FONTCONFIG_H__ + +#ifdef HAVE_FONTCONFIG +#include +#endif + +typedef struct fc_instance_s fc_instance_t; + +fc_instance_t* fontconfig_init(ass_library_t* library, FT_Library ftlibrary, const char* family, const char* path); +char* fontconfig_select(fc_instance_t* priv, const char* family, unsigned bold, unsigned italic, int* index); +void fontconfig_done(fc_instance_t* priv); + +#ifdef HAVE_FONTCONFIG +char* fontconfig_select_with_charset(fc_instance_t* priv, const char* family, unsigned bold, unsigned italic, int* index, FcCharSet* charset); +#endif + +#endif + diff --git a/libass/ass_library.c b/libass/ass_library.c new file mode 100644 index 000000000..107a6eda3 --- /dev/null +++ b/libass/ass_library.c @@ -0,0 +1,93 @@ +// -*- c-basic-offset: 8; indent-tabs-mode: t -*- +// vim:ts=8:sw=8:noet:ai: +/* + Copyright (C) 2006 Evgeniy Stepanov + + This program 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. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include +#include +#include +#include + +#include "ass.h" +#include "ass_library.h" + + +ass_library_t* ass_library_init(void) +{ + return calloc(1, sizeof(ass_library_t)); +} + +void ass_library_done(ass_library_t* priv) +{ + if (priv) { + ass_set_fonts_dir(priv, NULL); + ass_set_style_overrides(priv, NULL); + free(priv); + } +} + +void ass_set_fonts_dir(ass_library_t* priv, const char* fonts_dir) +{ + if (priv->fonts_dir) + free(priv->fonts_dir); + + priv->fonts_dir = fonts_dir ? strdup(fonts_dir) : 0; +} + +void ass_set_extract_fonts(ass_library_t* priv, int extract) +{ + priv->extract_fonts = !!extract; +} + +void ass_set_style_overrides(ass_library_t* priv, char** list) +{ + char** p; + char** q; + int cnt; + + if (priv->style_overrides) { + for (p = priv->style_overrides; *p; ++p) + free(*p); + free(priv->style_overrides); + } + + if (!list) return; + + for (p = list, cnt = 0; *p; ++p, ++cnt) {} + + priv->style_overrides = malloc((cnt + 1) * sizeof(char*)); + for (p = list, q = priv->style_overrides; *p; ++p, ++q) + *q = strdup(*p); + priv->style_overrides[cnt] = NULL; +} + +static void grow_array(void **array, int nelem, size_t elsize) +{ + if (!(nelem & 31)) + *array = realloc(*array, (nelem + 32) * elsize); +} + +void ass_add_font(ass_library_t* priv, char* name, char* data, int size) +{ + grow_array((void**)&priv->fontdata, priv->num_fontdata, sizeof(*priv->fontdata)); + priv->fontdata[priv->num_fontdata].name = name; + priv->fontdata[priv->num_fontdata].data = data; + priv->fontdata[priv->num_fontdata].size = size; + priv->num_fontdata ++; +} + diff --git a/libass/ass_library.h b/libass/ass_library.h new file mode 100644 index 000000000..34a8b7a03 --- /dev/null +++ b/libass/ass_library.h @@ -0,0 +1,40 @@ +// -*- c-basic-offset: 8; indent-tabs-mode: t -*- +// vim:ts=8:sw=8:noet:ai: +/* + Copyright (C) 2006 Evgeniy Stepanov + + This program 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. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef __ASS_LIBRARY_H__ +#define __ASS_LIBRARY_H__ + +typedef struct ass_fontdata_s { + char* name; + char* data; + int size; +} ass_fontdata_t; + +struct ass_library_s { + char* fonts_dir; + int extract_fonts; + char** style_overrides; + + ass_fontdata_t* fontdata; + int num_fontdata; +}; + +#endif + diff --git a/libass/ass_render.c b/libass/ass_render.c new file mode 100644 index 000000000..307bc2af1 --- /dev/null +++ b/libass/ass_render.c @@ -0,0 +1,2340 @@ +// -*- c-basic-offset: 8; indent-tabs-mode: t -*- +// vim:ts=8:sw=8:noet:ai: +/* + Copyright (C) 2006 Evgeniy Stepanov + + This program 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. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "config.h" + +#include +#include +#include +#include +#include FT_FREETYPE_H +#include FT_STROKER_H +#include FT_GLYPH_H +#include FT_SYNTHESIS_H + +#include "mputils.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" + +#define MAX_GLYPHS 1000 +#define MAX_LINES 100 + +static int last_render_id = 0; + +typedef struct ass_settings_s { + int frame_width; + int frame_height; + double font_size_coeff; // font size multiplier + double line_spacing; // additional line spacing (in frame pixels) + int top_margin; // height of top margin. Everything except toptitles is shifted down by top_margin. + int bottom_margin; // height of bottom margin. (frame_height - top_margin - bottom_margin) is original video height. + int left_margin; + int right_margin; + int use_margins; // 0 - place all subtitles inside original frame + // 1 - use margins for placing toptitles and subtitles + double aspect; // frame aspect ratio, d_width / d_height. + + char* default_font; + char* default_family; +} ass_settings_t; + +// a rendered event +typedef struct event_images_s { + ass_image_t* imgs; + int top, height; + int detect_collisions; + int shift_direction; + ass_event_t* event; +} event_images_t; + +struct ass_renderer_s { + ass_library_t* library; + FT_Library ftlibrary; + fc_instance_t* fontconfig_priv; + ass_settings_t settings; + int render_id; + ass_synth_priv_t* synth_priv; + + ass_image_t* images_root; // rendering result is stored here + ass_image_t* prev_images_root; + + event_images_t* eimg; // temporary buffer for sorting rendered events + int eimg_size; // allocated buffer size +}; + +typedef enum {EF_NONE = 0, EF_KARAOKE, EF_KARAOKE_KF, EF_KARAOKE_KO} effect_t; + +// describes a glyph +// glyph_info_t and text_info_t are used for text centering and word-wrapping operations +typedef struct glyph_info_s { + unsigned symbol; + FT_Glyph glyph; + FT_Glyph outline_glyph; + bitmap_t* bm; // glyph bitmap + bitmap_t* bm_o; // outline bitmap + bitmap_t* bm_s; // shadow bitmap + FT_BBox bbox; + FT_Vector pos; + char linebreak; // the first (leading) glyph of some line ? + uint32_t c[4]; // colors + FT_Vector advance; // 26.6 + effect_t effect_type; + int effect_timing; // time duration of current karaoke word + // after process_karaoke_effects: distance in pixels from the glyph origin. + // part of the glyph to the left of it is displayed in a different color. + int effect_skip_timing; // delay after the end of last karaoke word + int asc, desc; // font max ascender and descender +// int height; + int be; // blur edges + int shadow; + double frx, fry, frz; // rotation + + glyph_hash_key_t hash_key; +} glyph_info_t; + +typedef struct line_info_s { + int asc, desc; +} line_info_t; + +typedef struct text_info_s { + glyph_info_t* glyphs; + int length; + line_info_t lines[MAX_LINES]; + int n_lines; + int height; +} text_info_t; + + +// Renderer state. +// Values like current font face, color, screen position, clipping and so on are stored here. +typedef struct render_context_s { + ass_event_t* event; + ass_style_t* style; + + ass_font_t* font; + char* font_path; + int font_size; + + FT_Stroker stroker; + int alignment; // alignment overrides go here; if zero, style value will be used + double frx, fry, frz; + enum { EVENT_NORMAL, // "normal" top-, sub- or mid- title + EVENT_POSITIONED, // happens after pos(,), margins are ignored + EVENT_HSCROLL, // "Banner" transition effect, text_width is unlimited + EVENT_VSCROLL // "Scroll up", "Scroll down" transition effects + } evt_type; + int pos_x, pos_y; // position + int org_x, org_y; // origin + char have_origin; // origin is explicitly defined; if 0, get_base_point() is used + double scale_x, scale_y; + double hspacing; // distance between letters, in pixels + double border; // outline width + uint32_t c[4]; // colors(Primary, Secondary, so on) in RGBA + int clip_x0, clip_y0, clip_x1, clip_y1; + char detect_collisions; + uint32_t fade; // alpha from \fad + char be; // blur edges + int shadow; + + effect_t effect_type; + int effect_timing; + int effect_skip_timing; + + enum { SCROLL_LR, // left-to-right + SCROLL_RL, + SCROLL_TB, // top-to-bottom + SCROLL_BT + } scroll_direction; // for EVENT_HSCROLL, EVENT_VSCROLL + int scroll_shift; + + // face properties + char* family; + unsigned bold; + unsigned italic; + +} render_context_t; + +// frame-global data +typedef struct frame_context_s { + ass_renderer_t* ass_priv; + int width, height; // screen dimensions + int orig_height; // frame height ( = screen height - margins ) + int orig_width; // frame width ( = screen width - margins ) + ass_track_t* track; + long long time; // frame's timestamp, ms + double font_scale; + double font_scale_x; // x scale applied to all glyphs to preserve text aspect ratio + double border_scale; +} frame_context_t; + +static ass_renderer_t* ass_renderer; +static ass_settings_t* global_settings; +static text_info_t text_info; +static render_context_t render_context; +static frame_context_t frame_context; + +struct render_priv_s { + int top, height; + int render_id; +}; + +static void ass_lazy_track_init(void) +{ + ass_track_t* track = frame_context.track; + if (track->PlayResX && track->PlayResY) + return; + if (!track->PlayResX && !track->PlayResY) { + mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_NeitherPlayResXNorPlayResYDefined); + track->PlayResX = 384; + track->PlayResY = 288; + } else { + double orig_aspect = (global_settings->aspect * frame_context.height * frame_context.orig_width) / + frame_context.orig_height / frame_context.width; + if (!track->PlayResY) { + track->PlayResY = track->PlayResX / orig_aspect + .5; + mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_PlayResYUndefinedSettingY, track->PlayResY); + } else if (!track->PlayResX) { + track->PlayResX = track->PlayResY * orig_aspect + .5; + mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_PlayResXUndefinedSettingX, track->PlayResX); + } + } +} + +ass_renderer_t* ass_renderer_init(ass_library_t* library) +{ + int error; + FT_Library ft; + ass_renderer_t* priv = 0; + + memset(&render_context, 0, sizeof(render_context)); + memset(&frame_context, 0, sizeof(frame_context)); + memset(&text_info, 0, sizeof(text_info)); + + error = FT_Init_FreeType( &ft ); + if ( error ) { + mp_msg(MSGT_ASS, MSGL_FATAL, MSGTR_LIBASS_FT_Init_FreeTypeFailed); + goto ass_init_exit; + } + + priv = calloc(1, sizeof(ass_renderer_t)); + if (!priv) { + FT_Done_FreeType(ft); + goto ass_init_exit; + } + + priv->synth_priv = ass_synth_init(); + + priv->library = library; + priv->ftlibrary = ft; + // images_root and related stuff is zero-filled in calloc + + ass_font_cache_init(); + ass_glyph_cache_init(); + + text_info.glyphs = calloc(MAX_GLYPHS, sizeof(glyph_info_t)); + +ass_init_exit: + if (priv) mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_Init); + else mp_msg(MSGT_ASS, MSGL_ERR, MSGTR_LIBASS_InitFailed); + + return priv; +} + +void ass_renderer_done(ass_renderer_t* priv) +{ + ass_font_cache_done(); + ass_glyph_cache_done(); + if (render_context.stroker) { + FT_Stroker_Done(render_context.stroker); + render_context.stroker = 0; + } + if (priv && priv->ftlibrary) FT_Done_FreeType(priv->ftlibrary); + if (priv && priv->fontconfig_priv) fontconfig_done(priv->fontconfig_priv); + if (priv && priv->synth_priv) ass_synth_done(priv->synth_priv); + if (priv && priv->eimg) free(priv->eimg); + if (priv) free(priv); + if (text_info.glyphs) free(text_info.glyphs); +} + +/** + * \brief Create a new ass_image_t + * Parameters are the same as ass_image_t fields. + */ +static ass_image_t* my_draw_bitmap(unsigned char* bitmap, int bitmap_w, int bitmap_h, int stride, int dst_x, int dst_y, uint32_t color) +{ + ass_image_t* img = calloc(1, sizeof(ass_image_t)); + + img->w = bitmap_w; + img->h = bitmap_h; + img->stride = stride; + img->bitmap = bitmap; + img->color = color; + img->dst_x = dst_x; + img->dst_y = dst_y; + + return img; +} + +/** + * \brief convert bitmap glyph into ass_image_t struct(s) + * \param bit freetype bitmap glyph, FT_PIXEL_MODE_GRAY + * \param dst_x bitmap x coordinate in video frame + * \param dst_y bitmap y coordinate in video frame + * \param color first color, RGBA + * \param color2 second color, RGBA + * \param brk x coordinate relative to glyph origin, color is used to the left of brk, color2 - to the right + * \param tail pointer to the last image's next field, head of the generated list should be stored here + * \return pointer to the new list tail + * Performs clipping. Uses my_draw_bitmap for actual bitmap convertion. + */ +static ass_image_t** render_glyph(bitmap_t* bm, int dst_x, int dst_y, uint32_t color, uint32_t color2, int brk, ass_image_t** tail) +{ + // brk is relative to dst_x + // color = color left of brk + // color2 = color right of brk + int b_x0, b_y0, b_x1, b_y1; // visible part of the bitmap + int clip_x0, clip_y0, clip_x1, clip_y1; + int tmp; + ass_image_t* img; + + dst_x += bm->left; + dst_y += bm->top; + brk -= bm->left; + + // clipping + clip_x0 = render_context.clip_x0; + clip_y0 = render_context.clip_y0; + clip_x1 = render_context.clip_x1; + clip_y1 = render_context.clip_y1; + b_x0 = 0; + b_y0 = 0; + b_x1 = bm->w; + b_y1 = bm->h; + + tmp = dst_x - clip_x0; + if (tmp < 0) { + mp_msg(MSGT_ASS, MSGL_DBG2, "clip left\n"); + b_x0 = - tmp; + } + tmp = dst_y - clip_y0; + if (tmp < 0) { + mp_msg(MSGT_ASS, MSGL_DBG2, "clip top\n"); + b_y0 = - tmp; + } + tmp = clip_x1 - dst_x - bm->w; + if (tmp < 0) { + mp_msg(MSGT_ASS, MSGL_DBG2, "clip right\n"); + b_x1 = bm->w + tmp; + } + tmp = clip_y1 - dst_y - bm->h; + if (tmp < 0) { + mp_msg(MSGT_ASS, MSGL_DBG2, "clip bottom\n"); + b_y1 = bm->h + tmp; + } + + if ((b_y0 >= b_y1) || (b_x0 >= b_x1)) + return tail; + + if (brk > b_x0) { // draw left part + if (brk > b_x1) brk = b_x1; + img = my_draw_bitmap(bm->buffer + bm->w * b_y0 + b_x0, + brk - b_x0, b_y1 - b_y0, bm->w, + dst_x + b_x0, dst_y + b_y0, color); + *tail = img; + tail = &img->next; + } + if (brk < b_x1) { // draw right part + if (brk < b_x0) brk = b_x0; + img = my_draw_bitmap(bm->buffer + bm->w * b_y0 + brk, + b_x1 - brk, b_y1 - b_y0, bm->w, + dst_x + brk, dst_y + b_y0, color2); + *tail = img; + tail = &img->next; + } + return tail; +} + +/** + * \brief Render text_info_t struct into ass_image_t list + * Rasterize glyphs and put them in glyph cache. + */ +static ass_image_t* render_text(text_info_t* text_info, int dst_x, int dst_y) +{ + int pen_x, pen_y; + int i, error; + bitmap_t* bm; + glyph_hash_val_t hash_val; + ass_image_t* head; + ass_image_t** tail = &head; + + for (i = 0; i < text_info->length; ++i) { + if (text_info->glyphs[i].glyph) { + if ((text_info->glyphs[i].symbol == '\n') || (text_info->glyphs[i].symbol == 0)) + continue; + error = glyph_to_bitmap(ass_renderer->synth_priv, + text_info->glyphs[i].glyph, text_info->glyphs[i].outline_glyph, + &text_info->glyphs[i].bm, &text_info->glyphs[i].bm_o, + &text_info->glyphs[i].bm_s, text_info->glyphs[i].be); + if (error) + text_info->glyphs[i].symbol = 0; + FT_Done_Glyph(text_info->glyphs[i].glyph); + if (text_info->glyphs[i].outline_glyph) + FT_Done_Glyph(text_info->glyphs[i].outline_glyph); + + // cache + hash_val.bbox_scaled = text_info->glyphs[i].bbox; + hash_val.bm_o = text_info->glyphs[i].bm_o; + hash_val.bm = text_info->glyphs[i].bm; + hash_val.bm_s = text_info->glyphs[i].bm_s; + hash_val.advance.x = text_info->glyphs[i].advance.x; + hash_val.advance.y = text_info->glyphs[i].advance.y; + cache_add_glyph(&(text_info->glyphs[i].hash_key), &hash_val); + + } + } + + for (i = 0; i < text_info->length; ++i) { + glyph_info_t* info = text_info->glyphs + i; + if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm_s || (info->shadow == 0)) + continue; + + pen_x = dst_x + info->pos.x + info->shadow; + pen_y = dst_y + info->pos.y + info->shadow; + bm = info->bm_s; + + tail = render_glyph(bm, pen_x, pen_y, info->c[3], 0, 1000000, tail); + } + + for (i = 0; i < text_info->length; ++i) { + glyph_info_t* info = text_info->glyphs + i; + if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm_o) + continue; + + pen_x = dst_x + info->pos.x; + pen_y = dst_y + info->pos.y; + bm = info->bm_o; + + if ((info->effect_type == EF_KARAOKE_KO) && (info->effect_timing <= info->bbox.xMax)) { + // do nothing + } else + tail = render_glyph(bm, pen_x, pen_y, info->c[2], 0, 1000000, tail); + } + for (i = 0; i < text_info->length; ++i) { + glyph_info_t* info = text_info->glyphs + i; + if ((info->symbol == 0) || (info->symbol == '\n') || !info->bm) + continue; + + pen_x = dst_x + info->pos.x; + pen_y = dst_y + info->pos.y; + bm = info->bm; + + if ((info->effect_type == EF_KARAOKE) || (info->effect_type == EF_KARAOKE_KO)) { + if (info->effect_timing > info->bbox.xMax) + tail = render_glyph(bm, pen_x, pen_y, info->c[0], 0, 1000000, tail); + else + tail = render_glyph(bm, pen_x, pen_y, info->c[1], 0, 1000000, tail); + } else if (info->effect_type == EF_KARAOKE_KF) { + tail = render_glyph(bm, pen_x, pen_y, info->c[0], info->c[1], info->effect_timing, tail); + } else + tail = render_glyph(bm, pen_x, pen_y, info->c[0], 0, 1000000, tail); + } + + *tail = 0; + return head; +} + +/** + * \brief Mapping between script and screen coordinates + */ +static int x2scr(int x) { + return x*frame_context.orig_width / frame_context.track->PlayResX + global_settings->left_margin; +} +/** + * \brief Mapping between script and screen coordinates + */ +static int y2scr(int y) { + return y * frame_context.orig_height / frame_context.track->PlayResY + global_settings->top_margin; +} +// the same for toptitles +static int y2scr_top(int y) { + if (global_settings->use_margins) + return y * frame_context.orig_height / frame_context.track->PlayResY; + else + return y * frame_context.orig_height / frame_context.track->PlayResY + global_settings->top_margin; +} +// the same for subtitles +static int y2scr_sub(int y) { + if (global_settings->use_margins) + return y * frame_context.orig_height / frame_context.track->PlayResY + + global_settings->top_margin + global_settings->bottom_margin; + else + return y * frame_context.orig_height / frame_context.track->PlayResY + global_settings->top_margin; +} + +static void compute_string_bbox( text_info_t* info, FT_BBox *abbox ) { + FT_BBox bbox; + int i; + + if (text_info.length > 0) { + bbox.xMin = 32000; + bbox.xMax = -32000; + bbox.yMin = - d6_to_int(text_info.lines[0].asc) + text_info.glyphs[0].pos.y; + bbox.yMax = d6_to_int(text_info.height - text_info.lines[0].asc) + text_info.glyphs[0].pos.y; + + for (i = 0; i < text_info.length; ++i) { + int s = text_info.glyphs[i].pos.x; + int e = s + d6_to_int(text_info.glyphs[i].advance.x); + bbox.xMin = FFMIN(bbox.xMin, s); + bbox.xMax = FFMAX(bbox.xMax, e); + } + } else + bbox.xMin = bbox.xMax = bbox.yMin = bbox.yMax = 0; + + /* return string bbox */ + *abbox = bbox; +} + + +/** + * \brief Check if starting part of (*p) matches sample. If true, shift p to the first symbol after the matching part. + */ +static inline int mystrcmp(char** p, const char* sample) { + int len = strlen(sample); + if (strncmp(*p, sample, len) == 0) { + (*p) += len; + return 1; + } else + return 0; +} + +double ass_internal_font_size_coeff = 0.8; + +static void change_font_size(int sz) +{ + double size = sz * frame_context.font_scale; + + if (size < 1) + size = 1; + else if (size > frame_context.height * 2) + size = frame_context.height * 2; + + ass_font_set_size(render_context.font, size); + + render_context.font_size = sz; +} + +/** + * \brief Change current font, using setting from render_context. + */ +static void update_font(void) +{ + unsigned val; + ass_renderer_t* priv = frame_context.ass_priv; + ass_font_desc_t desc; + desc.family = strdup(render_context.family); + + val = render_context.bold; + // 0 = normal, 1 = bold, >1 = exact weight + if (val == 0) val = 80; // normal + else if (val == 1) val = 200; // bold + desc.bold = val; + + val = render_context.italic; + if (val == 0) val = 0; // normal + else if (val == 1) val = 110; //italic + desc.italic = val; + + render_context.font = ass_font_new(priv->library, priv->ftlibrary, priv->fontconfig_priv, &desc); + + if (render_context.font) + change_font_size(render_context.font_size); +} + +/** + * \brief Change border width + * negative value resets border to style value + */ +static void change_border(double border) +{ + int b; + if (!render_context.font) return; + + if (border < 0) { + if (render_context.style->BorderStyle == 1) { + if (render_context.style->Outline == 0 && render_context.style->Shadow > 0) + border = 1.; + else + border = render_context.style->Outline; + } else + border = 1.; + } + render_context.border = border; + + b = 64 * border * frame_context.border_scale; + if (b > 0) { + if (!render_context.stroker) { + int error; +#if (FREETYPE_MAJOR > 2) || ((FREETYPE_MAJOR == 2) && (FREETYPE_MINOR > 1)) + error = FT_Stroker_New( ass_renderer->ftlibrary, &render_context.stroker ); +#else // < 2.2 + error = FT_Stroker_New( render_context.font->faces[0]->memory, &render_context.stroker ); +#endif + if (error) { + mp_msg(MSGT_ASS, MSGL_V, "failed to get stroker\n"); + render_context.stroker = 0; + } + } + if (render_context.stroker) + FT_Stroker_Set( render_context.stroker, b, + FT_STROKER_LINECAP_ROUND, + FT_STROKER_LINEJOIN_ROUND, + 0 ); + } else { + FT_Stroker_Done(render_context.stroker); + render_context.stroker = 0; + } +} + +#define _r(c) ((c)>>24) +#define _g(c) (((c)>>16)&0xFF) +#define _b(c) (((c)>>8)&0xFF) +#define _a(c) ((c)&0xFF) + +/** + * \brief Calculate a weighted average of two colors + * calculates c1*(1-a) + c2*a, but separately for each component except alpha + */ +static void change_color(uint32_t* var, uint32_t new, double pwr) +{ + (*var)= ((uint32_t)(_r(*var) * (1 - pwr) + _r(new) * pwr) << 24) + + ((uint32_t)(_g(*var) * (1 - pwr) + _g(new) * pwr) << 16) + + ((uint32_t)(_b(*var) * (1 - pwr) + _b(new) * pwr) << 8) + + _a(*var); +} + +// like change_color, but for alpha component only +static void change_alpha(uint32_t* var, uint32_t new, double pwr) +{ + *var = (_r(*var) << 24) + (_g(*var) << 16) + (_b(*var) << 8) + (_a(*var) * (1 - pwr) + _a(new) * pwr); +} + +/** + * \brief Multiply two alpha values + * \param a first value + * \param b second value + * \return result of multiplication + * Parameters and result are limited by 0xFF. + */ +static uint32_t mult_alpha(uint32_t a, uint32_t b) +{ + return 0xFF - (0xFF - a) * (0xFF - b) / 0xFF; +} + +/** + * \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; +} + +static void reset_render_context(); + +/** + * \brief Parse style override tag. + * \param p string to parse + * \param pwr multiplier for some tag effects (comes from \t tags) + */ +static char* parse_tag(char* p, double pwr) { +#define skip_all(x) if (*p == (x)) ++p; else { \ + while ((*p != (x)) && (*p != '}') && (*p != 0)) {++p;} } +#define skip(x) if (*p == (x)) ++p; else { return p; } + + skip_all('\\'); + if ((*p == '}') || (*p == 0)) + return p; + + if (mystrcmp(&p, "fsc")) { + char tp = *p++; + double val; + if (tp == 'x') { + if (mystrtod(&p, &val)) { + val /= 100; + render_context.scale_x = render_context.scale_x * ( 1 - pwr) + val * pwr; + } else + render_context.scale_x = render_context.style->ScaleX; + } else if (tp == 'y') { + if (mystrtod(&p, &val)) { + val /= 100; + render_context.scale_y = render_context.scale_y * ( 1 - pwr) + val * pwr; + } else + render_context.scale_y = render_context.style->ScaleY; + } + } else if (mystrcmp(&p, "fsp")) { + double val; + if (mystrtod(&p, &val)) + render_context.hspacing = render_context.hspacing * ( 1 - pwr ) + val * pwr; + else + render_context.hspacing = render_context.style->Spacing; + } else if (mystrcmp(&p, "fs")) { + int val; + if (mystrtoi(&p, 10, &val)) + val = render_context.font_size * ( 1 - pwr ) + val * pwr; + else + val = render_context.style->FontSize; + if (render_context.font) + change_font_size(val); + } else if (mystrcmp(&p, "bord")) { + double val; + if (mystrtod(&p, &val)) + val = render_context.border * ( 1 - pwr ) + val * pwr; + else + val = -1.; // reset to default + change_border(val); + } else if (mystrcmp(&p, "move")) { + int x1, x2, y1, y2; + long long t1, t2, delta_t, t; + int x, y; + double k; + skip('('); + x1 = strtol(p, &p, 10); + skip(','); + y1 = strtol(p, &p, 10); + skip(','); + x2 = strtol(p, &p, 10); + skip(','); + y2 = strtol(p, &p, 10); + if (*p == ',') { + skip(','); + t1 = strtoll(p, &p, 10); + skip(','); + t2 = strtoll(p, &p, 10); + mp_msg(MSGT_ASS, MSGL_DBG2, "movement6: (%d, %d) -> (%d, %d), (%" PRId64 " .. %" PRId64 ")\n", + x1, y1, x2, y2, (int64_t)t1, (int64_t)t2); + } else { + t1 = 0; + t2 = render_context.event->Duration; + mp_msg(MSGT_ASS, MSGL_DBG2, "movement: (%d, %d) -> (%d, %d)\n", x1, y1, x2, y2); + } + skip(')'); + delta_t = t2 - t1; + t = frame_context.time - render_context.event->Start; + if (t < t1) + k = 0.; + else if (t > t2) + k = 1.; + else k = ((double)(t - t1)) / delta_t; + x = k * (x2 - x1) + x1; + y = k * (y2 - y1) + y1; + render_context.pos_x = x; + render_context.pos_y = y; + render_context.detect_collisions = 0; + render_context.evt_type = EVENT_POSITIONED; + } else if (mystrcmp(&p, "frx")) { + double val; + if (mystrtod(&p, &val)) { + val *= M_PI / 180; + render_context.frx = val * pwr + render_context.frx * (1-pwr); + } else + render_context.frx = 0.; + } else if (mystrcmp(&p, "fry")) { + double val; + if (mystrtod(&p, &val)) { + val *= M_PI / 180; + render_context.fry = val * pwr + render_context.fry * (1-pwr); + } else + render_context.fry = 0.; + } else if (mystrcmp(&p, "frz") || mystrcmp(&p, "fr")) { + double val; + if (mystrtod(&p, &val)) { + val *= M_PI / 180; + render_context.frz = val * pwr + render_context.frz * (1-pwr); + } else + render_context.frz = M_PI * render_context.style->Angle / 180.; + } else if (mystrcmp(&p, "fn")) { + char* start = p; + char* family; + skip_all('\\'); + if (p > start) { + family = malloc(p - start + 1); + strncpy(family, start, p - start); + family[p - start] = '\0'; + } else + family = strdup(render_context.style->FontName); + if (render_context.family) + free(render_context.family); + render_context.family = family; + update_font(); + } else if (mystrcmp(&p, "alpha")) { + uint32_t val; + int i; + if (strtocolor(&p, &val)) { + unsigned char a = val >> 24; + for (i = 0; i < 4; ++i) + change_alpha(&render_context.c[i], a, pwr); + } else { + change_alpha(&render_context.c[0], render_context.style->PrimaryColour, pwr); + change_alpha(&render_context.c[1], render_context.style->SecondaryColour, pwr); + change_alpha(&render_context.c[2], render_context.style->OutlineColour, pwr); + change_alpha(&render_context.c[3], render_context.style->BackColour, pwr); + } + // FIXME: simplify + } else if (mystrcmp(&p, "an")) { + int val; + if (mystrtoi(&p, 10, &val) && val) { + int v = (val - 1) / 3; // 0, 1 or 2 for vertical alignment + mp_msg(MSGT_ASS, MSGL_DBG2, "an %d\n", val); + if (v != 0) v = 3 - v; + val = ((val - 1) % 3) + 1; // horizontal alignment + val += v*4; + mp_msg(MSGT_ASS, MSGL_DBG2, "align %d\n", val); + render_context.alignment = val; + } else + render_context.alignment = render_context.style->Alignment; + } else if (mystrcmp(&p, "a")) { + int val = strtol(p, &p, 10); + if (mystrtoi(&p, 10, &val) && val) + render_context.alignment = val; + else + render_context.alignment = render_context.style->Alignment; + } else if (mystrcmp(&p, "pos")) { + int v1, v2; + skip('('); + v1 = strtol(p, &p, 10); + skip(','); + v2 = strtol(p, &p, 10); + skip(')'); + mp_msg(MSGT_ASS, MSGL_DBG2, "pos(%d, %d)\n", v1, v2); + render_context.evt_type = EVENT_POSITIONED; + render_context.detect_collisions = 0; + render_context.pos_x = v1; + render_context.pos_y = v2; + } else if (mystrcmp(&p, "fad")) { + int a1, a2, a3; + long long t1, t2, t3, t4; + if (*p == 'e') ++p; // either \fad or \fade + skip('('); + a1 = strtol(p, &p, 10); + skip(','); + a2 = strtol(p, &p, 10); + if (*p == ')') { + // 2-argument version (\fad, according to specs) + // a1 and a2 are fade-in and fade-out durations + t1 = 0; + t4 = render_context.event->Duration; + t2 = a1; + t3 = t4 - a2; + a1 = 0xFF; + a2 = 0; + a3 = 0xFF; + } else { + // 6-argument version (\fade) + // a1 and a2 (and a3) are opacity values + skip(','); + a3 = strtol(p, &p, 10); + skip(','); + t1 = strtoll(p, &p, 10); + skip(','); + t2 = strtoll(p, &p, 10); + skip(','); + t3 = strtoll(p, &p, 10); + skip(','); + t4 = strtoll(p, &p, 10); + } + skip(')'); + render_context.fade = interpolate_alpha(frame_context.time - render_context.event->Start, t1, t2, t3, t4, a1, a2, a3); + } else if (mystrcmp(&p, "org")) { + int v1, v2; + skip('('); + v1 = strtol(p, &p, 10); + skip(','); + v2 = strtol(p, &p, 10); + skip(')'); + mp_msg(MSGT_ASS, MSGL_DBG2, "org(%d, %d)\n", v1, v2); + // render_context.evt_type = EVENT_POSITIONED; + render_context.org_x = v1; + render_context.org_y = v2; + render_context.have_origin = 1; + } else if (mystrcmp(&p, "t")) { + double v[3]; + int v1, v2; + double v3; + int cnt; + long long t1, t2, t, delta_t; + double k; + skip('('); + for (cnt = 0; cnt < 3; ++cnt) { + if (*p == '\\') + break; + v[cnt] = strtod(p, &p); + skip(','); + } + if (cnt == 3) { + v1 = v[0]; v2 = v[1]; v3 = v[2]; + } else if (cnt == 2) { + v1 = v[0]; v2 = v[1]; v3 = 1.; + } else if (cnt == 1) { + v1 = 0; v2 = render_context.event->Duration; v3 = v[0]; + } else { // cnt == 0 + v1 = 0; v2 = render_context.event->Duration; v3 = 1.; + } + render_context.detect_collisions = 0; + t1 = v1; + t2 = v2; + delta_t = v2 - v1; + if (v3 < 0.) + v3 = 0.; + t = frame_context.time - render_context.event->Start; // FIXME: move to render_context + if (t < t1) + k = 0.; + else if (t > t2) + k = 1.; + else k = pow(((double)(t - t1)) / delta_t, v3); + while (*p == '\\') + p = parse_tag(p, k); // maybe k*pwr ? no, specs forbid nested \t's + skip_all(')'); // FIXME: better skip(')'), but much more tags support required + } else if (mystrcmp(&p, "clip")) { + int x0, y0, x1, y1; + int res = 1; + skip('('); + res &= mystrtoi(&p, 10, &x0); + skip(','); + res &= mystrtoi(&p, 10, &y0); + skip(','); + res &= mystrtoi(&p, 10, &x1); + skip(','); + res &= mystrtoi(&p, 10, &y1); + skip(')'); + if (res) { + render_context.clip_x0 = render_context.clip_x0 * (1-pwr) + x0 * pwr; + render_context.clip_x1 = render_context.clip_x1 * (1-pwr) + x1 * pwr; + render_context.clip_y0 = render_context.clip_y0 * (1-pwr) + y0 * pwr; + render_context.clip_y1 = render_context.clip_y1 * (1-pwr) + y1 * pwr; + } else { + render_context.clip_x0 = 0; + render_context.clip_y0 = 0; + render_context.clip_x1 = frame_context.track->PlayResX; + render_context.clip_y1 = frame_context.track->PlayResY; + } + } else if (mystrcmp(&p, "c")) { + uint32_t val; + if (!strtocolor(&p, &val)) + val = render_context.style->PrimaryColour; + mp_msg(MSGT_ASS, MSGL_DBG2, "color: %X\n", val); + change_color(&render_context.c[0], val, pwr); + } else if ((*p >= '1') && (*p <= '4') && (++p) && (mystrcmp(&p, "c") || mystrcmp(&p, "a"))) { + char n = *(p-2); + int cidx = n - '1'; + char cmd = *(p-1); + uint32_t val; + assert((n >= '1') && (n <= '4')); + if (!strtocolor(&p, &val)) + switch(n) { + case '1': val = render_context.style->PrimaryColour; break; + case '2': val = render_context.style->SecondaryColour; break; + case '3': val = render_context.style->OutlineColour; break; + case '4': val = render_context.style->BackColour; break; + default : val = 0; break; // impossible due to assert; avoid compilation warning + } + switch (cmd) { + case 'c': change_color(render_context.c + cidx, val, pwr); break; + case 'a': change_alpha(render_context.c + cidx, val >> 24, pwr); break; + default: mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_BadCommand, n, cmd); break; + } + mp_msg(MSGT_ASS, MSGL_DBG2, "single c/a at %f: %c%c = %X \n", pwr, n, cmd, render_context.c[cidx]); + } else if (mystrcmp(&p, "r")) { + reset_render_context(); + } else if (mystrcmp(&p, "be")) { + int val; + if (mystrtoi(&p, 10, &val)) + render_context.be = val ? 1 : 0; + else + render_context.be = 0; + } else if (mystrcmp(&p, "b")) { + int b; + if (mystrtoi(&p, 10, &b)) { + if (pwr >= .5) + render_context.bold = b; + } else + render_context.bold = render_context.style->Bold; + update_font(); + } else if (mystrcmp(&p, "i")) { + int i; + if (mystrtoi(&p, 10, &i)) { + if (pwr >= .5) + render_context.italic = i; + } else + render_context.italic = render_context.style->Italic; + update_font(); + } else if (mystrcmp(&p, "kf") || mystrcmp(&p, "K")) { + int val = strtol(p, &p, 10); + render_context.effect_type = EF_KARAOKE_KF; + if (render_context.effect_timing) + render_context.effect_skip_timing += render_context.effect_timing; + render_context.effect_timing = val * 10; + } else if (mystrcmp(&p, "ko")) { + int val = strtol(p, &p, 10); + render_context.effect_type = EF_KARAOKE_KO; + if (render_context.effect_timing) + render_context.effect_skip_timing += render_context.effect_timing; + render_context.effect_timing = val * 10; + } else if (mystrcmp(&p, "k")) { + int val = strtol(p, &p, 10); + render_context.effect_type = EF_KARAOKE; + if (render_context.effect_timing) + render_context.effect_skip_timing += render_context.effect_timing; + render_context.effect_timing = val * 10; + } else if (mystrcmp(&p, "shad")) { + int val; + if (mystrtoi(&p, 10, &val)) + render_context.shadow = val; + else + render_context.shadow = render_context.style->Shadow; + } + + return p; + +#undef skip +#undef skip_all +} + +/** + * \brief Get next ucs4 char from string, parsing and executing style overrides + * \param str string pointer + * \return ucs4 code of the next char + * On return str points to the unparsed part of the string + */ +static unsigned get_next_char(char** str) +{ + char* p = *str; + unsigned chr; + if (*p == '{') { // '\0' goes here + p++; + while (1) { + p = parse_tag(p, 1.); + if (*p == '}') { // end of tag + p++; + if (*p == '{') { + p++; + continue; + } else + break; + } else if (*p != '\\') + mp_msg(MSGT_ASS, MSGL_V, "Unable to parse: \"%s\" \n", p); + if (*p == 0) + break; + } + } + if (*p == '\t') { + ++p; + *str = p; + return ' '; + } + if (*p == '\\') { + if ((*(p+1) == 'N') || ((*(p+1) == 'n') && (frame_context.track->WrapStyle == 2))) { + p += 2; + *str = p; + return '\n'; + } else if (*(p+1) == 'n') { + p += 2; + *str = p; + return ' '; + } + } + chr = utf8_get_char(&p); + *str = p; + return chr; +} + +static void apply_transition_effects(ass_event_t* event) +{ + int v[4]; + int cnt; + char* p = event->Effect; + + if (!p || !*p) return; + + cnt = 0; + while (cnt < 4 && (p = strchr(p, ';'))) { + v[cnt++] = atoi(++p); + } + + if (strncmp(event->Effect, "Banner;", 7) == 0) { + int delay; + if (cnt < 1) { + mp_msg(MSGT_ASS, MSGL_V, "Error parsing effect: %s \n", event->Effect); + return; + } + if (cnt >= 2 && v[1] == 0) // right-to-left + render_context.scroll_direction = SCROLL_RL; + else // left-to-right + render_context.scroll_direction = SCROLL_LR; + + delay = v[0]; + if (delay == 0) delay = 1; // ? + render_context.scroll_shift = (frame_context.time - render_context.event->Start) / delay; + render_context.evt_type = EVENT_HSCROLL; + return; + } + + if (strncmp(event->Effect, "Scroll up;", 10) == 0) { + render_context.scroll_direction = SCROLL_BT; + } else if (strncmp(event->Effect, "Scroll down;", 12) == 0) { + render_context.scroll_direction = SCROLL_TB; + } else { + mp_msg(MSGT_ASS, MSGL_V, "Unknown transition effect: %s \n", event->Effect); + return; + } + // parse scroll up/down parameters + { + int delay; + int y0, y1; + if (cnt < 3) { + mp_msg(MSGT_ASS, MSGL_V, "Error parsing effect: %s \n", event->Effect); + return; + } + delay = v[2]; + if (delay == 0) delay = 1; // ? + render_context.scroll_shift = (frame_context.time - render_context.event->Start) / delay; + if (v[0] < v[1]) { + y0 = v[0]; y1 = v[1]; + } else { + y0 = v[1]; y1 = v[0]; + } + if (y1 == 0) + y1 = frame_context.track->PlayResY; // y0=y1=0 means fullscreen scrolling + render_context.clip_y0 = y0; + render_context.clip_y1 = y1; + render_context.evt_type = EVENT_VSCROLL; + render_context.detect_collisions = 0; + } + +} + +/** + * \brief partially reset render_context to style values + * Works like {\r}: resets some style overrides + */ +static void reset_render_context(void) +{ + render_context.c[0] = render_context.style->PrimaryColour; + render_context.c[1] = render_context.style->SecondaryColour; + render_context.c[2] = render_context.style->OutlineColour; + render_context.c[3] = render_context.style->BackColour; + render_context.font_size = render_context.style->FontSize; + + if (render_context.family) + free(render_context.family); + render_context.family = strdup(render_context.style->FontName); + render_context.bold = render_context.style->Bold; + render_context.italic = render_context.style->Italic; + update_font(); + + change_border(-1.); + render_context.scale_x = render_context.style->ScaleX; + render_context.scale_y = render_context.style->ScaleY; + render_context.hspacing = render_context.style->Spacing; + render_context.be = 0; + render_context.shadow = render_context.style->Shadow; + render_context.frx = render_context.fry = 0.; + render_context.frz = M_PI * render_context.style->Angle / 180.; + + // FIXME: does not reset unsupported attributes. +} + +/** + * \brief Start new event. Reset render_context. + */ +static void init_render_context(ass_event_t* event) +{ + render_context.event = event; + render_context.style = frame_context.track->styles + event->Style; + + reset_render_context(); + + render_context.evt_type = EVENT_NORMAL; + render_context.alignment = render_context.style->Alignment; + render_context.pos_x = 0; + render_context.pos_y = 0; + render_context.org_x = 0; + render_context.org_y = 0; + render_context.have_origin = 0; + render_context.clip_x0 = 0; + render_context.clip_y0 = 0; + render_context.clip_x1 = frame_context.track->PlayResX; + render_context.clip_y1 = frame_context.track->PlayResY; + render_context.detect_collisions = 1; + render_context.fade = 0; + render_context.effect_type = EF_NONE; + render_context.effect_timing = 0; + render_context.effect_skip_timing = 0; + + apply_transition_effects(event); +} + +static void free_render_context(void) +{ +} + +/** + * \brief Get normal and outline glyphs from cache (if possible) or font face + * \param index face glyph index + * \param symbol ucs4 char + * \param info out: struct filled with extracted data + * \param advance advance vector of the extracted glyph + * \return 0 on success + */ +static int get_glyph(int symbol, glyph_info_t* info, FT_Vector* advance) +{ + int error; + glyph_hash_val_t* val; + glyph_hash_key_t* key = &(info->hash_key); + + key->font = render_context.font; + key->size = render_context.font_size; + key->ch = symbol; + key->outline = (render_context.border * 0xFFFF); // convert to 16.16 + key->scale_x = (render_context.scale_x * 0xFFFF); + key->scale_y = (render_context.scale_y * 0xFFFF); + key->frx = (render_context.frx * 0xFFFF); + key->fry = (render_context.fry * 0xFFFF); + key->frz = (render_context.frz * 0xFFFF); + key->advance = *advance; + key->bold = render_context.bold; + key->italic = render_context.italic; + key->be = render_context.be; + + val = cache_find_glyph(key); +// val = 0; + + if (val) { + info->glyph = info->outline_glyph = 0; + info->bm = val->bm; + info->bm_o = val->bm_o; + info->bm_s = val->bm_s; + info->bbox = val->bbox_scaled; + info->advance.x = val->advance.x; + info->advance.y = val->advance.y; + + return 0; + } + + // not found, get a new outline glyph from face +// mp_msg(MSGT_ASS, MSGL_INFO, "miss, index = %d, symbol = %c, adv = (%d, %d)\n", index, symbol, advance->x, advance->y); + + info->outline_glyph = 0; + info->bm = info->bm_o = info->bm_s = 0; + + info->glyph = ass_font_get_glyph(frame_context.ass_priv->fontconfig_priv, render_context.font, symbol); + if (!info->glyph) + return 0; + + info->advance.x = d16_to_d6(info->glyph->advance.x); + info->advance.y = d16_to_d6(info->glyph->advance.y); + + if (render_context.stroker) { + info->outline_glyph = info->glyph; + error = FT_Glyph_Stroke( &(info->outline_glyph), render_context.stroker, 0 ); // don't destroy original + if (error) { + mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FT_Glyph_Stroke_Error, error); + } + } + + return 0; +} + +/** + * This function goes through text_info and calculates text parameters. + * The following text_info fields are filled: + * n_lines + * height + * lines[].height + * lines[].asc + * lines[].desc + */ +static void measure_text() +{ + int cur_line = 0, max_asc = 0, max_desc = 0; + int i; + text_info.height = 0; + for (i = 0; i < text_info.length + 1; ++i) { + if ((i == text_info.length) || text_info.glyphs[i].linebreak) { + text_info.lines[cur_line].asc = max_asc; + text_info.lines[cur_line].desc = max_desc; + text_info.height += max_asc + max_desc; + cur_line ++; + max_asc = max_desc = 0; + } + if (i < text_info.length) { + glyph_info_t* cur = text_info.glyphs + i; + if (cur->asc > max_asc) + max_asc = cur->asc; + if (cur->desc > max_desc) + max_desc = cur->desc; + } + } +} + +/** + * \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: + * 1. Place text, wrapping it when current line is full + * 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. + */ +static void wrap_lines_smart(int max_text_width) +{ + int i, j; + glyph_info_t *cur, *s1, *e1, *s2, *s3, *w; + int last_space; + int break_type; + int exit; + int pen_shift_x; + int pen_shift_y; + int cur_line; + + last_space = -1; + text_info.n_lines = 1; + break_type = 0; + s1 = text_info.glyphs; // current line start + for (i = 0; i < text_info.length; ++i) { + int break_at, s_offset, len; + cur = text_info.glyphs + i; + break_at = -1; + s_offset = s1->bbox.xMin + s1->pos.x; + len = (cur->bbox.xMax + cur->pos.x) - s_offset; + + if (cur->symbol == '\n') { + break_type = 2; + break_at = i; + mp_msg(MSGT_ASS, MSGL_DBG2, "forced line break at %d\n", break_at); + } + + if (len >= max_text_width) { + break_type = 1; + break_at = last_space; + if (break_at == -1) + break_at = i - 1; + if (break_at == -1) + break_at = 0; + mp_msg(MSGT_ASS, MSGL_DBG2, "overfill at %d\n", i); + mp_msg(MSGT_ASS, MSGL_DBG2, "line break at %d\n", break_at); + } + + if (break_at != -1) { + // need to use one more line + // marking break_at+1 as start of a new line + int lead = break_at + 1; // the first symbol of the new line + if (text_info.n_lines >= MAX_LINES) { + // to many lines ! + // no more linebreaks + for (j = lead; j < text_info.length; ++j) + text_info.glyphs[j].linebreak = 0; + break; + } + if (lead < text_info.length) + text_info.glyphs[lead].linebreak = break_type; + last_space = -1; + s1 = text_info.glyphs + lead; + s_offset = s1->bbox.xMin + s1->pos.x; + text_info.n_lines ++; + } + + if (cur->symbol == ' ') + last_space = i; + } +#define DIFF(x,y) (((x) < (y)) ? (y - x) : (x - y)) + exit = 0; + while (!exit) { + exit = 1; + w = s3 = text_info.glyphs; + s1 = s2 = 0; + for (i = 0; i <= text_info.length; ++i) { + cur = text_info.glyphs + i; + if ((i == text_info.length) || cur->linebreak) { + s1 = s2; + s2 = s3; + s3 = cur; + if (s1 && (s2->linebreak == 1)) { // have at least 2 lines, and linebreak is 'soft' + int l1, l2, l1_new, l2_new; + + w = s2; + do { --w; } while ((w > s1) && (w->symbol == ' ')); + while ((w > s1) && (w->symbol != ' ')) { --w; } + e1 = w; + while ((e1 > s1) && (e1->symbol == ' ')) { --e1; } + if (w->symbol == ' ') ++w; + + l1 = ((s2-1)->bbox.xMax + (s2-1)->pos.x) - (s1->bbox.xMin + s1->pos.x); + l2 = ((s3-1)->bbox.xMax + (s3-1)->pos.x) - (s2->bbox.xMin + s2->pos.x); + l1_new = (e1->bbox.xMax + e1->pos.x) - (s1->bbox.xMin + s1->pos.x); + l2_new = ((s3-1)->bbox.xMax + (s3-1)->pos.x) - (w->bbox.xMin + w->pos.x); + + if (DIFF(l1_new, l2_new) < DIFF(l1, l2)) { + w->linebreak = 1; + s2->linebreak = 0; + exit = 0; + } + } + } + if (i == text_info.length) + break; + } + + } + assert(text_info.n_lines >= 1); +#undef DIFF + + measure_text(); + + pen_shift_x = 0; + pen_shift_y = 0; + cur_line = 1; + for (i = 0; i < text_info.length; ++i) { + cur = text_info.glyphs + i; + if (cur->linebreak) { + int height = text_info.lines[cur_line - 1].desc + text_info.lines[cur_line].asc; + cur_line ++; + pen_shift_x = - cur->pos.x; + pen_shift_y += d6_to_int(height) + global_settings->line_spacing; + mp_msg(MSGT_ASS, MSGL_DBG2, "shifting from %d to %d by (%d, %d)\n", i, text_info.length - 1, pen_shift_x, pen_shift_y); + } + cur->pos.x += pen_shift_x; + cur->pos.y += pen_shift_y; + } +} + +/** + * \brief determine karaoke effects + * Karaoke effects cannot be calculated during parse stage (get_next_char()), + * so they are done in a separate step. + * Parse stage: when karaoke style override is found, its parameters are stored in the next glyph's + * (the first glyph of the karaoke word)'s effect_type and effect_timing. + * This function: + * 1. sets effect_type for all glyphs in the word (_karaoke_ word) + * 2. sets effect_timing for all glyphs to x coordinate of the border line between the left and right karaoke parts + * (left part is filled with PrimaryColour, right one - with SecondaryColour). + */ +static void process_karaoke_effects(void) +{ + glyph_info_t *cur, *cur2; + glyph_info_t *s1, *e1; // start and end of the current word + glyph_info_t *s2; // start of the next word + int i; + int timing; // current timing + int tm_start, tm_end; // timings at start and end of the current word + int tm_current; + double dt; + int x; + int x_start, x_end; + + tm_current = frame_context.time - render_context.event->Start; + timing = 0; + s1 = s2 = 0; + for (i = 0; i <= text_info.length; ++i) { + cur = text_info.glyphs + i; + if ((i == text_info.length) || (cur->effect_type != EF_NONE)) { + s1 = s2; + s2 = cur; + if (s1) { + e1 = s2 - 1; + tm_start = timing + s1->effect_skip_timing; + tm_end = tm_start + s1->effect_timing; + timing = tm_end; + x_start = 1000000; + x_end = -1000000; + for (cur2 = s1; cur2 <= e1; ++cur2) { + x_start = FFMIN(x_start, cur2->bbox.xMin + cur2->pos.x); + x_end = FFMAX(x_end, cur2->bbox.xMax + cur2->pos.x); + } + + dt = (tm_current - tm_start); + if ((s1->effect_type == EF_KARAOKE) || (s1->effect_type == EF_KARAOKE_KO)) { + if (dt > 0) + x = x_end + 1; + else + x = x_start; + } else if (s1->effect_type == EF_KARAOKE_KF) { + dt /= (tm_end - tm_start); + x = x_start + (x_end - x_start) * dt; + } else { + mp_msg(MSGT_ASS, MSGL_ERR, MSGTR_LIBASS_UnknownEffectType_InternalError); + continue; + } + + for (cur2 = s1; cur2 <= e1; ++cur2) { + cur2->effect_type = s1->effect_type; + cur2->effect_timing = x - cur2->pos.x; + } + } + } + } +} + +/** + * \brief Calculate base point for positioning and rotation + * \param bbox text bbox + * \param alignment alignment + * \param bx, by out: base point coordinates + */ +static void get_base_point(FT_BBox bbox, int alignment, int* bx, int* by) +{ + const int halign = alignment & 3; + const int valign = alignment & 12; + if (bx) + switch(halign) { + case HALIGN_LEFT: + *bx = bbox.xMin; + break; + case HALIGN_CENTER: + *bx = (bbox.xMax + bbox.xMin) / 2; + break; + case HALIGN_RIGHT: + *bx = bbox.xMax; + break; + } + if (by) + switch(valign) { + case VALIGN_TOP: + *by = bbox.yMin; + break; + case VALIGN_CENTER: + *by = (bbox.yMax + bbox.yMin) / 2; + break; + case VALIGN_SUB: + *by = bbox.yMax; + break; + } +} + +/** + * \brief Multiply 4-vector by 4-matrix + * \param a 4-vector + * \param m 4-matrix] + * \param b out: 4-vector + * Calculates a * m and stores result in b + */ +static inline void transform_point_3d(double *a, double *m, double *b) +{ + b[0] = a[0] * m[0] + a[1] * m[4] + a[2] * m[8] + a[3] * m[12]; + b[1] = a[0] * m[1] + a[1] * m[5] + a[2] * m[9] + a[3] * m[13]; + b[2] = a[0] * m[2] + a[1] * m[6] + a[2] * m[10] + a[3] * m[14]; + b[3] = a[0] * m[3] + a[1] * m[7] + a[2] * m[11] + a[3] * m[15]; +} + +/** + * \brief Apply 3d transformation to a vector + * \param v FreeType vector (2d) + * \param m 4-matrix + * Transforms v by m, projects the result back to the screen plane + * Result is returned in v. + */ +static inline void transform_vector_3d(FT_Vector* v, double *m) { + double a[4], b[4]; + a[0] = d6_to_double(v->x); + a[1] = d6_to_double(v->y); + a[2] = 0.; + a[3] = 1.; + transform_point_3d(a, m, b); + if (b[3] < 0.001 && b[3] > -0.001) + b[3] = b[3] < 0. ? -0.001 : 0.001; + v->x = double_to_d6(b[0] / b[3]); + v->y = double_to_d6(b[1] / b[3]); +} + +/** + * \brief Apply 3d transformation to a glyph + * \param glyph FreeType glyph + * \param m 4-matrix + * Transforms glyph by m, projects the result back to the screen plane + * Result is returned in glyph. + */ +static inline void transform_glyph_3d(FT_Glyph glyph, double *m) { + int i; + FT_Outline* outline = &((FT_OutlineGlyph)glyph)->outline; + + for (i=0; in_points; i++) + transform_vector_3d(outline->points + i, m); + + transform_vector_3d(&glyph->advance, m); +} + +/** + * \brief Apply 3d transformation to several objects + * \param vec FreeType vector + * \param glyph FreeType glyph + * \param glyph2 FreeType glyph + * \param frx x-axis rotation angle + * \param fry y-axis rotation angle + * \param frz z-axis rotation angle + * Rotates the given vector and both glyphs by frx, fry and frz. + */ +void transform_3d(FT_Vector* vec, FT_Glyph* glyph, FT_Glyph* glyph2, double frx, double fry, double frz) +{ + if (frx != 0. || fry != 0. || frz != 0.) { + double m[16]; + double sx = sin(frx); + double sy = sin(fry); + double sz = sin(frz); + double cx = cos(frx); + double cy = cos(fry); + double cz = cos(frz); + m[0] = cy * cz; m[1] = cz*sx*sy + sz*cx; m[2] = sz*sx - cz*cx*sy; m[3] = 0.0; + m[4] = -sz*cy; m[5] = cz*cx - sz*sy*sx; m[6] = sz*sy*cx + cz*sx; m[7] = 0.0; + m[8] = sy; m[9] = -sx*cy; m[10] = cx*cy; m[11] = 0.0; + m[12] = 0.0; m[13] = 0.0; m[14] = 0.0; m[15] = 1.0; + + if (glyph && *glyph) + transform_glyph_3d(*glyph, m); + + if (glyph2 && *glyph2) + transform_glyph_3d(*glyph2, m); + + if (vec) + transform_vector_3d(vec, m); + } +} + +/** + * \brief Main ass rendering function, glues everything together + * \param event event to render + * Process event, appending resulting ass_image_t's to images_root. + */ +static int ass_render_event(ass_event_t* event, event_images_t* event_images) +{ + char* p; + FT_UInt previous; + FT_UInt num_glyphs; + FT_Vector pen; + int error; + unsigned code; + FT_BBox bbox; + int i, j; + FT_Vector shift; + int MarginL, MarginR, MarginV; + int last_break; + int alignment, halign, valign; + int device_x = 0, device_y = 0; + + if (event->Style >= frame_context.track->n_styles) { + mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_NoStyleFound); + return 1; + } + if (!event->Text) { + mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_EmptyEvent); + return 1; + } + + init_render_context(event); + + text_info.length = 0; + pen.x = 0; + pen.y = 0; + previous = 0; + num_glyphs = 0; + p = event->Text; + // Event parsing. + while (1) { + // get next char, executing style override + // this affects render_context + code = get_next_char(&p); + + // face could have been changed in get_next_char + if (!render_context.font) { + free_render_context(); + return 1; + } + + if (code == 0) + break; + + if (text_info.length >= MAX_GLYPHS) { + mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_MAX_GLYPHS_Reached, + (int)(event - frame_context.track->events), event->Start, event->Duration, event->Text); + break; + } + + if ( previous && code ) { + FT_Vector delta; + delta = ass_font_get_kerning(render_context.font, previous, code); + pen.x += delta.x * render_context.scale_x; + pen.y += delta.y * render_context.scale_y; + } + + shift.x = pen.x & 63; + shift.y = pen.y & 63; + + { + FT_Matrix matrix; + matrix.xx = (FT_Fixed)( render_context.scale_x * frame_context.font_scale_x * 0x10000L ); + matrix.xy = (FT_Fixed)( 0 * 0x10000L ); + matrix.yx = (FT_Fixed)( 0 * 0x10000L ); + matrix.yy = (FT_Fixed)( render_context.scale_y * 0x10000L ); + + ass_font_set_transform(render_context.font, &matrix, &shift ); + } + + error = get_glyph(code, text_info.glyphs + text_info.length, &shift); + + if (error) { + continue; + } + + text_info.glyphs[text_info.length].pos.x = d6_to_int(pen.x); + text_info.glyphs[text_info.length].pos.y = d6_to_int(pen.y); + + pen.x += text_info.glyphs[text_info.length].advance.x; + pen.x += double_to_d6(render_context.hspacing); + pen.y += text_info.glyphs[text_info.length].advance.y; + + // if it's an outline glyph, we still need to fill the bbox + if (text_info.glyphs[text_info.length].glyph) { + FT_Glyph_Get_CBox( text_info.glyphs[text_info.length].glyph, FT_GLYPH_BBOX_PIXELS, &(text_info.glyphs[text_info.length].bbox) ); + } + + previous = code; + + text_info.glyphs[text_info.length].symbol = code; + text_info.glyphs[text_info.length].linebreak = 0; + for (i = 0; i < 4; ++i) { + uint32_t clr = render_context.c[i]; + change_alpha(&clr, mult_alpha(_a(clr), render_context.fade), 1.); + text_info.glyphs[text_info.length].c[i] = clr; + } + text_info.glyphs[text_info.length].effect_type = render_context.effect_type; + text_info.glyphs[text_info.length].effect_timing = render_context.effect_timing; + text_info.glyphs[text_info.length].effect_skip_timing = render_context.effect_skip_timing; + text_info.glyphs[text_info.length].be = render_context.be; + text_info.glyphs[text_info.length].shadow = render_context.shadow; + text_info.glyphs[text_info.length].frx = render_context.frx; + text_info.glyphs[text_info.length].fry = render_context.fry; + text_info.glyphs[text_info.length].frz = render_context.frz; + ass_font_get_asc_desc(render_context.font, code, + &text_info.glyphs[text_info.length].asc, + &text_info.glyphs[text_info.length].desc); + text_info.glyphs[text_info.length].asc *= render_context.scale_y; + text_info.glyphs[text_info.length].desc *= render_context.scale_y; + + text_info.length++; + + render_context.effect_type = EF_NONE; + render_context.effect_timing = 0; + render_context.effect_skip_timing = 0; + } + + if (text_info.length == 0) { + // no valid symbols in the event; this can be smth like {comment} + free_render_context(); + return 1; + } + + // depends on glyph x coordinates being monotonous, so it should be done before line wrap + process_karaoke_effects(); + + // alignments + alignment = render_context.alignment; + halign = alignment & 3; + valign = alignment & 12; + + MarginL = (event->MarginL) ? event->MarginL : render_context.style->MarginL; + MarginR = (event->MarginR) ? event->MarginR : render_context.style->MarginR; + MarginV = (event->MarginV) ? event->MarginV : render_context.style->MarginV; + + if (render_context.evt_type != EVENT_HSCROLL) { + int max_text_width; + + // calculate max length of a line + max_text_width = x2scr(frame_context.track->PlayResX - MarginR) - x2scr(MarginL); + + // rearrange text in several lines + wrap_lines_smart(max_text_width); + + // align text + last_break = -1; + for (i = 1; i < text_info.length + 1; ++i) { // (text_info.length + 1) is the end of the last line + if ((i == text_info.length) || text_info.glyphs[i].linebreak) { + int width, shift; + glyph_info_t* first_glyph = text_info.glyphs + last_break + 1; + glyph_info_t* last_glyph = text_info.glyphs + i - 1; + + while ((last_glyph > first_glyph) && ((last_glyph->symbol == '\n') || (last_glyph->symbol == 0))) + last_glyph --; + + width = last_glyph->pos.x + last_glyph->bbox.xMax - first_glyph->pos.x - first_glyph->bbox.xMin; + shift = - first_glyph->bbox.xMin; // now text line starts exactly at 0 (left margin) + if (halign == HALIGN_LEFT) { // left aligned, no action + } else if (halign == HALIGN_RIGHT) { // right aligned + shift = max_text_width - width; + } else if (halign == HALIGN_CENTER) { // centered + shift = (max_text_width - width) / 2; + } + for (j = last_break + 1; j < i; ++j) { + text_info.glyphs[j].pos.x += shift; + } + last_break = i - 1; + } + } + } else { // render_context.evt_type == EVENT_HSCROLL + measure_text(); + } + + // determing text bounding box + compute_string_bbox(&text_info, &bbox); + + // determine device coordinates for text + + // x coordinate for everything except positioned events + if (render_context.evt_type == EVENT_NORMAL || + render_context.evt_type == EVENT_VSCROLL) { + device_x = x2scr(MarginL); + } else if (render_context.evt_type == EVENT_HSCROLL) { + if (render_context.scroll_direction == SCROLL_RL) + device_x = x2scr(frame_context.track->PlayResX - render_context.scroll_shift); + else if (render_context.scroll_direction == SCROLL_LR) + device_x = x2scr(render_context.scroll_shift) - (bbox.xMax - bbox.xMin); + } + + // y coordinate for everything except positioned events + if (render_context.evt_type == EVENT_NORMAL || + render_context.evt_type == EVENT_HSCROLL) { + if (valign == VALIGN_TOP) { // toptitle + device_y = y2scr_top(MarginV) + d6_to_int(text_info.lines[0].asc); + } else if (valign == VALIGN_CENTER) { // midtitle + int scr_y = y2scr(frame_context.track->PlayResY / 2); + device_y = scr_y - (bbox.yMax - bbox.yMin) / 2; + } else { // subtitle + int scr_y; + if (valign != VALIGN_SUB) + mp_msg(MSGT_ASS, MSGL_V, "Invalid valign, supposing 0 (subtitle)\n"); + scr_y = y2scr_sub(frame_context.track->PlayResY - MarginV); + device_y = scr_y; + device_y -= d6_to_int(text_info.height); + device_y += d6_to_int(text_info.lines[0].asc); + } + } else if (render_context.evt_type == EVENT_VSCROLL) { + if (render_context.scroll_direction == SCROLL_TB) + device_y = y2scr(render_context.clip_y0 + render_context.scroll_shift) - (bbox.yMax - bbox.yMin); + else if (render_context.scroll_direction == SCROLL_BT) + device_y = y2scr(render_context.clip_y1 - render_context.scroll_shift); + } + + // positioned events are totally different + if (render_context.evt_type == EVENT_POSITIONED) { + int base_x = 0; + int base_y = 0; + mp_msg(MSGT_ASS, MSGL_DBG2, "positioned event at %d, %d\n", render_context.pos_x, render_context.pos_y); + get_base_point(bbox, alignment, &base_x, &base_y); + device_x = x2scr(render_context.pos_x) - base_x; + device_y = y2scr(render_context.pos_y) - base_y; + } + + // fix clip coordinates (they depend on alignment) + render_context.clip_x0 = x2scr(render_context.clip_x0); + render_context.clip_x1 = x2scr(render_context.clip_x1); + if (render_context.evt_type == EVENT_NORMAL || + render_context.evt_type == EVENT_HSCROLL || + render_context.evt_type == EVENT_VSCROLL) { + if (valign == VALIGN_TOP) { + render_context.clip_y0 = y2scr_top(render_context.clip_y0); + render_context.clip_y1 = y2scr_top(render_context.clip_y1); + } else if (valign == VALIGN_CENTER) { + render_context.clip_y0 = y2scr(render_context.clip_y0); + render_context.clip_y1 = y2scr(render_context.clip_y1); + } else if (valign == VALIGN_SUB) { + render_context.clip_y0 = y2scr_sub(render_context.clip_y0); + render_context.clip_y1 = y2scr_sub(render_context.clip_y1); + } + } else if (render_context.evt_type == EVENT_POSITIONED) { + render_context.clip_y0 = y2scr(render_context.clip_y0); + render_context.clip_y1 = y2scr(render_context.clip_y1); + } + + // rotate glyphs if needed + { + FT_Vector center; + + if (render_context.have_origin) { + center.x = render_context.org_x; + center.y = render_context.org_y; + } else { + int bx, by; + get_base_point(bbox, alignment, &bx, &by); + center.x = device_x + bx; + center.y = device_y + by; + } + + for (i = 0; i < text_info.length; ++i) { + FT_Vector start; + FT_Vector start_old; + glyph_info_t* info = text_info.glyphs + i; + + // calculating shift vector + // shift = (position - center)*M - (position - center) + start.x = int_to_d6(info->pos.x + device_x - center.x); + start.y = - int_to_d6(info->pos.y + device_y - center.y); + start_old.x = start.x; + start_old.y = start.y; + + transform_3d(&start, &info->glyph, &info->outline_glyph, info->frx, info->fry, info->frz); + + start.x -= start_old.x; + start.y -= start_old.y; + + info->pos.x += d6_to_int(start.x); + info->pos.y -= d6_to_int(start.y); + + } + } + + event_images->top = device_y - d6_to_int(text_info.lines[0].asc); + event_images->height = d6_to_int(text_info.height); + event_images->detect_collisions = render_context.detect_collisions; + event_images->shift_direction = (valign == VALIGN_TOP) ? 1 : -1; + event_images->event = event; + event_images->imgs = render_text(&text_info, device_x, device_y); + + free_render_context(); + + return 0; +} + +/** + * \brief deallocate image list + * \param img list pointer + */ +void ass_free_images(ass_image_t* img) +{ + while (img) { + ass_image_t* next = img->next; + free(img); + img = next; + } +} + +static void ass_reconfigure(ass_renderer_t* priv) +{ + priv->render_id = ++last_render_id; + ass_glyph_cache_reset(); + ass_free_images(priv->prev_images_root); + priv->prev_images_root = 0; +} + +void ass_set_frame_size(ass_renderer_t* priv, int w, int h) +{ + if (priv->settings.frame_width != w || priv->settings.frame_height != h) { + priv->settings.frame_width = w; + priv->settings.frame_height = h; + if (priv->settings.aspect == 0.) + priv->settings.aspect = ((double)w) / h; + ass_reconfigure(priv); + } +} + +void ass_set_margins(ass_renderer_t* priv, int t, int b, int l, int r) +{ + if (priv->settings.left_margin != l || + priv->settings.right_margin != r || + priv->settings.top_margin != t || + priv->settings.bottom_margin != b) { + priv->settings.left_margin = l; + priv->settings.right_margin = r; + priv->settings.top_margin = t; + priv->settings.bottom_margin = b; + ass_reconfigure(priv); + } +} + +void ass_set_use_margins(ass_renderer_t* priv, int use) +{ + priv->settings.use_margins = use; +} + +void ass_set_aspect_ratio(ass_renderer_t* priv, double ar) +{ + if (priv->settings.aspect != ar) { + priv->settings.aspect = ar; + ass_reconfigure(priv); + } +} + +void ass_set_font_scale(ass_renderer_t* priv, double font_scale) +{ + if (priv->settings.font_size_coeff != font_scale) { + priv->settings.font_size_coeff = font_scale; + ass_reconfigure(priv); + } +} + +int ass_set_fonts(ass_renderer_t* priv, const char* default_font, const char* default_family) +{ + if (priv->settings.default_font) + free(priv->settings.default_font); + if (priv->settings.default_family) + free(priv->settings.default_family); + + priv->settings.default_font = default_font ? strdup(default_font) : 0; + priv->settings.default_family = default_family ? strdup(default_family) : 0; + + if (priv->fontconfig_priv) + fontconfig_done(priv->fontconfig_priv); + priv->fontconfig_priv = fontconfig_init(priv->library, priv->ftlibrary, default_family, default_font); + + return !!priv->fontconfig_priv; +} + +/** + * \brief Start a new frame + */ +static int ass_start_frame(ass_renderer_t *priv, ass_track_t* track, long long now) +{ + ass_renderer = priv; + global_settings = &priv->settings; + + if (!priv->settings.frame_width && !priv->settings.frame_height) + return 1; // library not initialized + + frame_context.ass_priv = priv; + frame_context.width = global_settings->frame_width; + frame_context.height = global_settings->frame_height; + frame_context.orig_width = global_settings->frame_width - global_settings->left_margin - global_settings->right_margin; + frame_context.orig_height = global_settings->frame_height - global_settings->top_margin - global_settings->bottom_margin; + frame_context.track = track; + frame_context.time = now; + + ass_lazy_track_init(); + + frame_context.font_scale = global_settings->font_size_coeff * ass_internal_font_size_coeff * + frame_context.orig_height / frame_context.track->PlayResY; + frame_context.border_scale = ((double)frame_context.orig_height) / frame_context.track->PlayResY; + + if (frame_context.orig_width * track->PlayResY == frame_context.orig_height * track->PlayResX) + frame_context.font_scale_x = 1.; + else + frame_context.font_scale_x = ((double)(frame_context.orig_width * track->PlayResY)) / (frame_context.orig_height * track->PlayResX); + + priv->prev_images_root = priv->images_root; + priv->images_root = 0; + + return 0; +} + +static int cmp_event_layer(const void* p1, const void* p2) +{ + ass_event_t* e1 = ((event_images_t*)p1)->event; + ass_event_t* e2 = ((event_images_t*)p2)->event; + if (e1->Layer < e2->Layer) + return -1; + if (e1->Layer > e2->Layer) + return 1; + if (e1->ReadOrder < e2->ReadOrder) + return -1; + if (e1->ReadOrder > e2->ReadOrder) + return 1; + return 0; +} + +#define MAX_EVENTS 100 + +static render_priv_t* get_render_priv(ass_event_t* event) +{ + if (!event->render_priv) + event->render_priv = calloc(1, sizeof(render_priv_t)); + // FIXME: check render_id + if (ass_renderer->render_id != event->render_priv->render_id) { + memset(event->render_priv, 0, sizeof(render_priv_t)); + event->render_priv->render_id = ass_renderer->render_id; + } + return event->render_priv; +} + +typedef struct segment_s { + int a, b; // top and height +} segment_t; + +static int overlap(segment_t* s1, segment_t* s2) +{ + if (s1->a >= s2->b || s2->a >= s1->b) + return 0; + return 1; +} + +static int cmp_segment(const void* p1, const void* p2) +{ + return ((segment_t*)p1)->a - ((segment_t*)p2)->a; +} + +static void shift_event(event_images_t* ei, int shift) +{ + ass_image_t* cur = ei->imgs; + while (cur) { + cur->dst_y += shift; + // clip top and bottom + if (cur->dst_y < 0) { + int clip = - cur->dst_y; + cur->h -= clip; + cur->bitmap += clip * cur->stride; + cur->dst_y = 0; + } + if (cur->dst_y + cur->h >= frame_context.height) { + int clip = cur->dst_y + cur->h - frame_context.height; + cur->h -= clip; + } + if (cur->h <= 0) { + cur->h = 0; + cur->dst_y = 0; + } + cur = cur->next; + } + ei->top += shift; +} + +// dir: 1 - move down +// -1 - move up +static int fit_segment(segment_t* s, segment_t* fixed, int* cnt, int dir) +{ + int i; + int shift = 0; + + if (dir == 1) // move down + for (i = 0; i < *cnt; ++i) { + if (s->b + shift <= fixed[i].a || s->a + shift >= fixed[i].b) + continue; + shift = fixed[i].b - s->a; + } + else // dir == -1, move up + for (i = *cnt-1; i >= 0; --i) { + if (s->b + shift <= fixed[i].a || s->a + shift >= fixed[i].b) + continue; + shift = fixed[i].a - s->b; + } + + fixed[*cnt].a = s->a + shift; + fixed[*cnt].b = s->b + shift; + (*cnt)++; + qsort(fixed, *cnt, sizeof(segment_t), cmp_segment); + + return shift; +} + +static void fix_collisions(event_images_t* imgs, int cnt) +{ + segment_t used[MAX_EVENTS]; + int cnt_used = 0; + int i, j; + + // fill used[] with fixed events + for (i = 0; i < cnt; ++i) { + render_priv_t* priv; + if (!imgs[i].detect_collisions) continue; + priv = get_render_priv(imgs[i].event); + if (priv->height > 0) { // it's a fixed event + segment_t s; + s.a = priv->top; + s.b = priv->top + priv->height; + if (priv->height != imgs[i].height) { // no, it's not + mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_EventHeightHasChanged); + priv->top = 0; + priv->height = 0; + } + for (j = 0; j < cnt_used; ++j) + if (overlap(&s, used + j)) { // no, it's not + priv->top = 0; + priv->height = 0; + } + if (priv->height > 0) { // still a fixed event + used[cnt_used].a = priv->top; + used[cnt_used].b = priv->top + priv->height; + cnt_used ++; + shift_event(imgs + i, priv->top - imgs[i].top); + } + } + } + qsort(used, cnt_used, sizeof(segment_t), cmp_segment); + + // try to fit other events in free spaces + for (i = 0; i < cnt; ++i) { + render_priv_t* priv; + if (!imgs[i].detect_collisions) continue; + priv = get_render_priv(imgs[i].event); + if (priv->height == 0) { // not a fixed event + int shift; + segment_t s; + s.a = imgs[i].top; + s.b = imgs[i].top + imgs[i].height; + shift = fit_segment(&s, used, &cnt_used, imgs[i].shift_direction); + if (shift) shift_event(imgs + i, shift); + // make it fixed + priv->top = imgs[i].top; + priv->height = imgs[i].height; + } + + } +} + +/** + * \brief compare two images + * \param i1 first image + * \param i2 second image + * \return 0 if identical, 1 if different positions, 2 if different content + */ +int ass_image_compare(ass_image_t *i1, ass_image_t *i2) +{ + if (i1->w != i2->w) return 2; + if (i1->h != i2->h) return 2; + if (i1->stride != i2->stride) return 2; + if (i1->color != i2->color) return 2; + if (i1->bitmap != i2->bitmap) + return 2; + if (i1->dst_x != i2->dst_x) return 1; + if (i1->dst_y != i2->dst_y) return 1; + return 0; +} + +/** + * \brief compare current and previous image list + * \param priv library handle + * \return 0 if identical, 1 if different positions, 2 if different content + */ +int ass_detect_change(ass_renderer_t *priv) +{ + ass_image_t* img, *img2; + int diff; + + img = priv->prev_images_root; + img2 = priv->images_root; + diff = 0; + while (img && diff < 2) { + ass_image_t* next, *next2; + next = img->next; + if (img2) { + int d = ass_image_compare(img, img2); + if (d > diff) diff = d; + next2 = img2->next; + } else { + // previous list is shorter + diff = 2; + break; + } + img = next; + img2 = next2; + } + + // is the previous list longer? + if (img2) + diff = 2; + + return diff; +} + +/** + * \brief render a frame + * \param priv library handle + * \param track track + * \param now current video timestamp (ms) + * \param detect_change a value describing how the new images differ from the previous ones will be written here: + * 0 if identical, 1 if different positions, 2 if different content. + * Can be NULL, in that case no detection is performed. + */ +ass_image_t* ass_render_frame(ass_renderer_t *priv, ass_track_t* track, long long now, int* detect_change) +{ + int i, cnt, rc; + event_images_t* last; + ass_image_t** tail; + + // init frame + rc = ass_start_frame(priv, track, now); + if (rc != 0) + return 0; + + // render events separately + cnt = 0; + for (i = 0; i < track->n_events; ++i) { + ass_event_t* event = track->events + i; + if ( (event->Start <= now) && (now < (event->Start + event->Duration)) ) { + if (cnt >= priv->eimg_size) { + priv->eimg_size += 100; + priv->eimg = realloc(priv->eimg, priv->eimg_size * sizeof(event_images_t)); + } + rc = ass_render_event(event, priv->eimg + cnt); + if (!rc) ++cnt; + } + } + + // sort by layer + qsort(priv->eimg, cnt, sizeof(event_images_t), cmp_event_layer); + + // call fix_collisions for each group of events with the same layer + last = priv->eimg; + for (i = 1; i < cnt; ++i) + if (last->event->Layer != priv->eimg[i].event->Layer) { + fix_collisions(last, priv->eimg + i - last); + last = priv->eimg + i; + } + if (cnt > 0) + fix_collisions(last, priv->eimg + cnt - last); + + // concat lists + tail = &ass_renderer->images_root; + for (i = 0; i < cnt; ++i) { + ass_image_t* cur = priv->eimg[i].imgs; + while (cur) { + *tail = cur; + tail = &cur->next; + cur = cur->next; + } + } + + if (detect_change) + *detect_change = ass_detect_change(priv); + + // free the previous image list + ass_free_images(priv->prev_images_root); + priv->prev_images_root = 0; + + return ass_renderer->images_root; +} + diff --git a/libass/ass_types.h b/libass/ass_types.h new file mode 100644 index 000000000..e39a24553 --- /dev/null +++ b/libass/ass_types.h @@ -0,0 +1,114 @@ +// -*- c-basic-offset: 8; indent-tabs-mode: t -*- +// vim:ts=8:sw=8:noet:ai: +/* + Copyright (C) 2006 Evgeniy Stepanov + + This program 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. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef __ASS_TYPES_H__ +#define __ASS_TYPES_H__ + +#define VALIGN_SUB 0 +#define VALIGN_CENTER 8 +#define VALIGN_TOP 4 +#define HALIGN_LEFT 1 +#define HALIGN_CENTER 2 +#define HALIGN_RIGHT 3 + +/// ass Style: line +typedef struct ass_style_s { + char* Name; + char* FontName; + int FontSize; + uint32_t PrimaryColour; + uint32_t SecondaryColour; + uint32_t OutlineColour; + uint32_t BackColour; + int Bold; + int Italic; + int Underline; + int StrikeOut; + double ScaleX; + double ScaleY; + double Spacing; + int Angle; + int BorderStyle; + double Outline; + double Shadow; + int Alignment; + int MarginL; + int MarginR; + int MarginV; +// int AlphaLevel; + int Encoding; +} ass_style_t; + +typedef struct render_priv_s render_priv_t; + +/// ass_event_t corresponds to a single Dialogue line +/// Text is stored as-is, style overrides will be parsed later +typedef struct ass_event_s { + long long Start; // ms + long long Duration; // ms + + int ReadOrder; + int Layer; + int Style; + char* Name; + int MarginL; + int MarginR; + int MarginV; + char* Effect; + char* Text; + + render_priv_t* render_priv; +} ass_event_t; + +typedef struct parser_priv_s parser_priv_t; + +typedef struct ass_library_s ass_library_t; + +/// ass track represent either an external script or a matroska subtitle stream (no real difference between them) +/// it can be used in rendering after the headers are parsed (i.e. events format line read) +typedef struct ass_track_s { + int n_styles; // amount used + int max_styles; // amount allocated + int n_events; + int max_events; + ass_style_t* styles; // array of styles, max_styles length, n_styles used + ass_event_t* events; // the same as styles + + char* style_format; // style format line (everything after "Format: ") + char* event_format; // event format line + + enum {TRACK_TYPE_UNKNOWN = 0, TRACK_TYPE_ASS, TRACK_TYPE_SSA} track_type; + + // script header fields + int PlayResX; + int PlayResY; + double Timer; + int WrapStyle; + + + int default_style; // index of default style + char* name; // file name in case of external subs, 0 for streams + + ass_library_t* library; + parser_priv_t* parser_priv; +} ass_track_t; + +#endif + diff --git a/libass/ass_utils.c b/libass/ass_utils.c new file mode 100644 index 000000000..7b7662e88 --- /dev/null +++ b/libass/ass_utils.c @@ -0,0 +1,83 @@ +// -*- c-basic-offset: 8; indent-tabs-mode: t -*- +// vim:ts=8:sw=8:noet:ai: +/* + Copyright (C) 2006 Evgeniy Stepanov + + This program 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. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "config.h" + +#include +#include +#include +#include + +#include "mputils.h" +#include "ass_utils.h" + +int mystrtoi(char** p, int base, int* res) +{ + char* start = *p; + *res = strtol(*p, p, base); + if (*p != start) return 1; + else return 0; +} + +int mystrtou32(char** p, int base, uint32_t* res) +{ + char* start = *p; + *res = strtoll(*p, p, base); + if (*p != start) return 1; + else return 0; +} + +int mystrtod(char** p, double* res) +{ + char* start = *p; + *res = strtod(*p, p); + if (*p != start) return 1; + else return 0; +} + +int strtocolor(char** q, uint32_t* res) +{ + uint32_t color = 0; + int result; + char* p = *q; + + if (*p == '&') ++p; + else mp_msg(MSGT_ASS, MSGL_DBG2, "suspicious color format: \"%s\"\n", p); + + if (*p == 'H' || *p == 'h') { + ++p; + result = mystrtou32(&p, 16, &color); + } else { + result = mystrtou32(&p, 0, &color); + } + + { + unsigned char* tmp = (unsigned char*)(&color); + unsigned char b; + b = tmp[0]; tmp[0] = tmp[3]; tmp[3] = b; + b = tmp[1]; tmp[1] = tmp[2]; tmp[2] = b; + } + if (*p == '&') ++p; + *q = p; + + *res = color; + return result; +} + diff --git a/libass/ass_utils.h b/libass/ass_utils.h new file mode 100644 index 000000000..6c4eccecd --- /dev/null +++ b/libass/ass_utils.h @@ -0,0 +1,55 @@ +// -*- c-basic-offset: 8; indent-tabs-mode: t -*- +// vim:ts=8:sw=8:noet:ai: +/* + Copyright (C) 2006 Evgeniy Stepanov + + This program 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. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef __ASS_UTILS_H__ +#define __ASS_UTILS_H__ + +int mystrtoi(char** p, int base, int* res); +int mystrtou32(char** p, int base, uint32_t* res); +int mystrtod(char** p, double* res); +int strtocolor(char** q, uint32_t* res); + +static inline int d6_to_int(int x) { + return (x + 32) >> 6; +} +static inline int d16_to_int(int x) { + return (x + 32768) >> 16; +} +static inline int int_to_d6(int x) { + return x << 6; +} +static inline int int_to_d16(int x) { + return x << 16; +} +static inline int d16_to_d6(int x) { + return (x + 512) >> 10; +} +static inline int d6_to_d16(int x) { + return x << 10; +} +static inline double d6_to_double(int x) { + return x / 64.; +} +static inline int double_to_d6(double x) { + return (int)(x * 64); +} + +#endif + diff --git a/libass/help_mp.h b/libass/help_mp.h new file mode 100644 index 000000000..33c79e802 --- /dev/null +++ b/libass/help_mp.h @@ -0,0 +1,55 @@ +#ifndef __LIBASS_HELP_MP_H__ +#define __LIBASS_HELP_MP_H__ +#define MSGTR_LIBASS_FT_Glyph_To_BitmapError "[ass] FT_Glyph_To_Bitmap error %d \n" +#define MSGTR_LIBASS_UnsupportedPixelMode "[ass] Unsupported pixel mode: %d\n" +#define MSGTR_LIBASS_NoStyleNamedXFoundUsingY "[ass] [%p] Warning: no style named '%s' found, using '%s'\n" +#define MSGTR_LIBASS_BadTimestamp "[ass] bad timestamp\n" +#define MSGTR_LIBASS_BadEncodedDataSize "[ass] bad encoded data size\n" +#define MSGTR_LIBASS_FontLineTooLong "[ass] Font line too long: %d, %s\n" +#define MSGTR_LIBASS_EventFormatHeaderMissing "[ass] Event format header missing\n" +#define MSGTR_LIBASS_ErrorOpeningIconvDescriptor "[ass] error opening iconv descriptor.\n" +#define MSGTR_LIBASS_ErrorRecodingFile "[ass] error recoding file.\n" +#define MSGTR_LIBASS_FopenFailed "[ass] ass_read_file(%s): fopen failed\n" +#define MSGTR_LIBASS_FseekFailed "[ass] ass_read_file(%s): fseek failed\n" +#define MSGTR_LIBASS_RefusingToLoadSubtitlesLargerThan10M "[ass] ass_read_file(%s): Refusing to load subtitles larger than 10M\n" +#define MSGTR_LIBASS_ReadFailed "Read failed, %d: %s\n" +#define MSGTR_LIBASS_AddedSubtitleFileMemory "[ass] Added subtitle file: (%d styles, %d events)\n" +#define MSGTR_LIBASS_AddedSubtitleFileFname "[ass] Added subtitle file: %s (%d styles, %d events)\n" +#define MSGTR_LIBASS_FailedToCreateDirectory "[ass] Failed to create directory %s\n" +#define MSGTR_LIBASS_NotADirectory "[ass] Not a directory: %s\n" +#define MSGTR_LIBASS_TooManyFonts "[ass] Too many fonts\n" +#define MSGTR_LIBASS_ErrorOpeningFont "[ass] Error opening font: %s, %d\n" +#define MSGTR_LIBASS_SelectedFontFamilyIsNotTheRequestedOne "[ass] fontconfig: Selected font family is not the requested one: '%s' != '%s'\n" +#define MSGTR_LIBASS_UsingDefaultFontFamily "[ass] fontconfig_select: Using default font family: (%s, %d, %d) -> %s, %d\n" +#define MSGTR_LIBASS_UsingDefaultFont "[ass] fontconfig_select: Using default font: (%s, %d, %d) -> %s, %d\n" +#define MSGTR_LIBASS_UsingArialFontFamily "[ass] fontconfig_select: Using 'Arial' font family: (%s, %d, %d) -> %s, %d\n" +#define MSGTR_LIBASS_FcInitLoadConfigAndFontsFailed "[ass] FcInitLoadConfigAndFonts failed.\n" +#define MSGTR_LIBASS_UpdatingFontCache "[ass] Updating font cache.\n" +#define MSGTR_LIBASS_BetaVersionsOfFontconfigAreNotSupported "[ass] Beta versions of fontconfig are not supported.\n[ass] Update before reporting any bugs.\n" +#define MSGTR_LIBASS_FcStrSetAddFailed "[ass] FcStrSetAdd failed.\n" +#define MSGTR_LIBASS_FcDirScanFailed "[ass] FcDirScan failed.\n" +#define MSGTR_LIBASS_FcDirSave "[ass] FcDirSave failed.\n" +#define MSGTR_LIBASS_FcConfigAppFontAddDirFailed "[ass] FcConfigAppFontAddDir failed\n" +#define MSGTR_LIBASS_FontconfigDisabledDefaultFontWillBeUsed "[ass] Fontconfig disabled, only default font will be used.\n" +#define MSGTR_LIBASS_FunctionCallFailed "[ass] %s failed\n" +#define MSGTR_LIBASS_NeitherPlayResXNorPlayResYDefined "[ass] Neither PlayResX nor PlayResY defined. Assuming 384x288.\n" +#define MSGTR_LIBASS_PlayResYUndefinedSettingY "[ass] PlayResY undefined, setting %d.\n" +#define MSGTR_LIBASS_PlayResXUndefinedSettingX "[ass] PlayResX undefined, setting %d.\n" +#define MSGTR_LIBASS_FT_Init_FreeTypeFailed "[ass] FT_Init_FreeType failed.\n" +#define MSGTR_LIBASS_Init "[ass] Init\n" +#define MSGTR_LIBASS_InitFailed "[ass] Init failed.\n" +#define MSGTR_LIBASS_BadCommand "[ass] Bad command: %c%c\n" +#define MSGTR_LIBASS_ErrorLoadingGlyph "[ass] Error loading glyph.\n" +#define MSGTR_LIBASS_FT_Glyph_Stroke_Error "[ass] FT_Glyph_Stroke error %d \n" +#define MSGTR_LIBASS_UnknownEffectType_InternalError "[ass] Unknown effect type (internal error)\n" +#define MSGTR_LIBASS_NoStyleFound "[ass] No style found!\n" +#define MSGTR_LIBASS_EmptyEvent "[ass] Empty event!\n" +#define MSGTR_LIBASS_MAX_GLYPHS_Reached "[ass] MAX_GLYPHS reached: event %d, start = %llu, duration = %llu\n Text = %s\n" +#define MSGTR_LIBASS_EventHeightHasChanged "[ass] Warning! Event height has changed! \n" +#define MSGTR_LIBASS_GlyphNotFoundReselectingFont "[ass] Glyph 0x%X not found, reselecting font for (%s, %d, %d)\n" +#define MSGTR_LIBASS_GlyphNotFound "[ass] Glyph 0x%X not found in font for (%s, %d, %d)\n" +#define MSGTR_LIBASS_ErrorOpeningMemoryFont "[ass] Error opening memory font: %s\n" +#define MSGTR_LIBASS_NoCharmaps "[ass] font face with no charmaps\n" +#define MSGTR_LIBASS_NoCharmapAutodetected "[ass] no charmap autodetected, trying the first one\n" +#endif + diff --git a/libass/mputils.c b/libass/mputils.c new file mode 100644 index 000000000..2c1a348e7 --- /dev/null +++ b/libass/mputils.c @@ -0,0 +1,218 @@ +#include "config.h" + +#include "mputils.h" + +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_ENCA +#include +#endif + +void my_mp_msg(int lvl, char *lvl_str, char *fmt, ...) { + if(lvl > MSGL_V) return; + printf("[ass] **%s**: ", lvl_str); + va_list va; + va_start(va, fmt); + vprintf(fmt, va); + va_end(va); +} + +unsigned utf8_get_char(char **str) { + uint8_t *strp = (uint8_t *)*str; + unsigned c = *strp++; + unsigned mask = 0x80; + int len = -1; + while (c & mask) { + mask >>= 1; + len++; + } + if (len <= 0 || len > 4) + goto no_utf8; + c &= mask - 1; + while ((*strp & 0xc0) == 0x80) { + if (len-- <= 0) + goto no_utf8; + c = (c << 6) | (*strp++ & 0x3f); + } + if (len) + goto no_utf8; + *str = (char *)strp; + return c; + +no_utf8: + strp = (uint8_t *)*str; + c = *strp++; + *str = (char *)strp; + return c; +} + +// gaussian blur +void blur( + unsigned char *buffer, + unsigned short *tmp2, + int width, + int height, + int stride, + int *m2, + int r, + int mwidth) { + + int x, y; + + unsigned char *s = buffer; + unsigned short *t = tmp2+1; + for(y=0; y>8; + unsigned *m3= m2 + src2*mwidth; + + int mx; + *srcp= 128; + for(mx=r-1; mx>8; + unsigned *m3= m2 + src2*mwidth; + + int mx; + *srcp= 128; + for(mx=0; mx>8; + unsigned *m3= m2 + src2*mwidth; + + int mx; + *srcp= 128; + for(mx=0; mx>8; + } + s+= stride; + t+= width + 1; + } +} + +#ifdef HAVE_ENCA +void* guess_buffer_cp(unsigned char* buffer, int buflen, char *preferred_language, char *fallback) +{ + const char **languages; + size_t langcnt; + EncaAnalyser analyser; + EncaEncoding encoding; + char *detected_sub_cp = NULL; + int i; + + languages = enca_get_languages(&langcnt); + mp_msg(MSGT_ASS, MSGL_V, "ENCA supported languages: "); + for (i = 0; i < langcnt; i++) { + mp_msg(MSGT_ASS, MSGL_V, "%s ", languages[i]); + } + mp_msg(MSGT_ASS, MSGL_V, "\n"); + + for (i = 0; i < langcnt; i++) { + const char *tmp; + + if (strcasecmp(languages[i], preferred_language) != 0) continue; + analyser = enca_analyser_alloc(languages[i]); + encoding = enca_analyse_const(analyser, buffer, buflen); + tmp = enca_charset_name(encoding.charset, ENCA_NAME_STYLE_ICONV); + if (tmp && encoding.charset != ENCA_CS_UNKNOWN) { + detected_sub_cp = strdup(tmp); + mp_msg(MSGT_ASS, MSGL_INFO, "ENCA detected charset: %s\n", tmp); + } + enca_analyser_free(analyser); + } + + free(languages); + + if (!detected_sub_cp) { + detected_sub_cp = strdup(fallback); + mp_msg(MSGT_ASS, MSGL_INFO, "ENCA detection failed: fallback to %s\n", fallback); + } + + return detected_sub_cp; +} +#endif diff --git a/libass/mputils.h b/libass/mputils.h new file mode 100644 index 000000000..3450b1c82 --- /dev/null +++ b/libass/mputils.h @@ -0,0 +1,29 @@ +#ifndef __MPUTILS_H__ +#define __MPUTILS_H__ + +#include "help_mp.h" + +unsigned utf8_get_char(char **str); + +void my_mp_msg(int lvl, char *lvl_str, char *fmt, ...); + +#define mp_msg(mod, level, args...) my_mp_msg(level, #level, args) + +#define MSGT_ASS 43 + +#define MSGL_FATAL 0 +#define MSGL_ERR 1 +#define MSGL_WARN 2 +#define MSGL_INFO 4 +#define MSGL_V 6 +#define MSGL_DBG2 7 + +void blur(unsigned char *buffer, unsigned short *tmp2, int width, int height, + int stride, int *m2, int r, int mwidth); + +void* guess_buffer_cp(unsigned char* buffer, int buflen, char *preferred_language, char *fallback); + +#define FFMAX(a,b) ((a) > (b) ? (a) : (b)) +#define FFMIN(a,b) ((a) > (b) ? (b) : (a)) + +#endif