diff options
-rw-r--r-- | configure.ac | 15 | ||||
-rw-r--r-- | libass/ass.c | 3 | ||||
-rw-r--r-- | libass/ass.h | 15 | ||||
-rw-r--r-- | libass/ass_library.c | 7 | ||||
-rw-r--r-- | libass/ass_render.c | 75 | ||||
-rw-r--r-- | libass/ass_render.h | 1 |
6 files changed, 100 insertions, 16 deletions
diff --git a/configure.ac b/configure.ac index 42b5a81c..543a44e1 100644 --- a/configure.ac +++ b/configure.ac @@ -30,6 +30,8 @@ AC_ARG_ENABLE([directwrite], AS_HELP_STRING([--disable-directwrite], [disable DirectWrite support (Windows only) @<:@default=check@:>@])) AC_ARG_ENABLE([coretext], AS_HELP_STRING([--disable-coretext], [disable CoreText support (OSX only) @<:@default=check@:>@])) +AC_ARG_ENABLE([libunibreak], AS_HELP_STRING([--disable-libunibreak], + [disable libunibreak support @<:@default=check@:>@])) AC_ARG_ENABLE([require-system-font-provider], AS_HELP_STRING([--disable-require-system-font-provider], [allow compilation even if no system font provider was found @<:@default=enabled:>@])) AC_ARG_ENABLE([asm], AS_HELP_STRING([--disable-asm], @@ -104,6 +106,19 @@ AS_IF([test "x$enable_test" = xyes || test "x$enable_compare" = xyes], [ ]) ]) +AS_IF([test "x$enable_libunibreak" != xno], [ + PKG_CHECK_MODULES([LIBUNIBREAK], [libunibreak >= 1.1], [ + pkg_requires="libunibreak >= 1.1, ${pkg_requires}" + CFLAGS="$CFLAGS $LIBUNIBREAK_CFLAGS" + LIBS="$LIBS $LIBUNIBREAK_LIBS" + AC_DEFINE(CONFIG_UNIBREAK, 1, [found libunibreak via pkg-config]) + ], [ + AS_IF([test "x$enable_libunibreak" = xyes], [ + AC_MSG_ERROR([libunibreak support was requested, but it was not found.]) + ]) + ]) +]) + ## Check for system font providers ### Fontconfig AS_IF([test "x$enable_fontconfig" != xno], [ diff --git a/libass/ass.c b/libass/ass.c index 991d31bc..41f72d66 100644 --- a/libass/ass.c +++ b/libass/ass.c @@ -1546,6 +1546,9 @@ int ass_track_set_feature(ASS_Track *track, ASS_Feature feature, int enable) #ifdef USE_FRIBIDI_EX_API FEATURE_MASK(ASS_FEATURE_BIDI_BRACKETS) | #endif +#ifdef CONFIG_UNIBREAK + FEATURE_MASK(ASS_FEATURE_WRAP_UNICODE) | +#endif FEATURE_MASK(ASS_FEATURE_WHOLE_TEXT_LAYOUT) | 0; uint32_t requested = 0; diff --git a/libass/ass.h b/libass/ass.h index 85dc1fe3..f0e54bce 100644 --- a/libass/ass.h +++ b/libass/ass.h @@ -24,7 +24,7 @@ #include <stdarg.h> #include "ass_types.h" -#define LIBASS_VERSION 0x01600000 +#define LIBASS_VERSION 0x01600010 #ifdef __cplusplus extern "C" { @@ -266,6 +266,19 @@ typedef enum { */ ASS_FEATURE_WHOLE_TEXT_LAYOUT, + /** + * Break lines according to the Unicode Line Breaking Algorithm. + * If the track language is set, some additional language-specific tweaks + * may be applied. Setting this enables more breaking opportunities + * compared to classic ASS. However, it is still possible for long words + * without breaking opportunities to cause overfull lines. + * This is incompatible with VSFilter and disabled by default. + * + * This feature may be unavailable at runtime if + * libass was compiled without libunibreak support. + */ + ASS_FEATURE_WRAP_UNICODE, + // New enum values can be added here in new ABI-compatible library releases. } ASS_Feature; diff --git a/libass/ass_library.c b/libass/ass_library.c index 745798ed..42cabbd0 100644 --- a/libass/ass_library.c +++ b/libass/ass_library.c @@ -24,6 +24,9 @@ #include <stdlib.h> #include <string.h> #include <stdarg.h> +#ifdef CONFIG_UNIBREAK +#include <linebreak.h> +#endif #include "ass.h" #include "ass_library.h" @@ -44,6 +47,10 @@ ASS_Library *ass_library_init(void) ASS_Library* lib = calloc(1, sizeof(*lib)); if (lib) lib->msg_callback = ass_msg_handler; + #ifdef CONFIG_UNIBREAK + // libunibreak works without, but its docs suggest this improves performance + init_linebreak(); + #endif return lib; } diff --git a/libass/ass_render.c b/libass/ass_render.c index 5fac76b4..7f31f2be 100644 --- a/libass/ass_render.c +++ b/libass/ass_render.c @@ -24,6 +24,10 @@ #include <string.h> #include <stdbool.h> +#ifdef CONFIG_UNIBREAK +#include <linebreak.h> +#endif + #include "ass.h" #include "ass_outline.h" #include "ass_render.h" @@ -108,8 +112,10 @@ ASS_Renderer *ass_renderer_init(ASS_Library *library) priv->text_info.combined_bitmaps = calloc(MAX_BITMAPS_INITIAL, sizeof(CombinedBitmapInfo)); priv->text_info.glyphs = calloc(MAX_GLYPHS_INITIAL, sizeof(GlyphInfo)); priv->text_info.event_text = calloc(MAX_GLYPHS_INITIAL, sizeof(FriBidiChar)); + priv->text_info.breaks = malloc(MAX_GLYPHS_INITIAL); priv->text_info.lines = calloc(MAX_LINES_INITIAL, sizeof(LineInfo)); - if (!priv->text_info.combined_bitmaps || !priv->text_info.glyphs || !priv->text_info.lines) + if (!priv->text_info.combined_bitmaps || !priv->text_info.glyphs || + !priv->text_info.lines || !priv->text_info.breaks) goto fail; priv->settings.font_size_coeff = 1.; @@ -155,6 +161,7 @@ void ass_renderer_done(ASS_Renderer *render_priv) free(render_priv->eimg); free(render_priv->text_info.glyphs); free(render_priv->text_info.event_text); + free(render_priv->text_info.breaks); free(render_priv->text_info.lines); free(render_priv->text_info.combined_bitmaps); @@ -1629,11 +1636,19 @@ static void trim_whitespace(ASS_Renderer *render_priv) } #undef IS_WHITESPACE +#ifdef CONFIG_UNIBREAK + #define ALLOWBREAK(glyph, index) (unibrks ? unibrks[index] == LINEBREAK_ALLOWBREAK : glyph == ' ') + #define FORCEBREAK(glyph, index) (unibrks ? unibrks[index] == LINEBREAK_MUSTBREAK : glyph == '\n') +#else + #define ALLOWBREAK(glyph, index) (glyph == ' ') + #define FORCEBREAK(glyph, index) (glyph == '\n') +#endif + /* * Starts a new line on the first breakable character after overflow */ static void -wrap_lines_naive(ASS_Renderer *render_priv, double max_text_width) +wrap_lines_naive(ASS_Renderer *render_priv, double max_text_width, char *unibrks) { TextInfo *text_info = &render_priv->text_info; GlyphInfo *s1 = text_info->glyphs; // current line start @@ -1647,21 +1662,23 @@ wrap_lines_naive(ASS_Renderer *render_priv, double max_text_width) double s_offset = d6_to_double(s1->bbox.x_min + s1->pos.x); double len = d6_to_double(cur->bbox.x_max + cur->pos.x) - s_offset; - if (cur->symbol == '\n') { + if (FORCEBREAK(cur->symbol, i)) { break_type = 2; break_at = i; ass_msg(render_priv->library, MSGL_DBG2, "forced line break at %d", break_at); - } else if (cur->symbol == ' ') { - last_breakable = i; - } else if (len >= max_text_width - && (render_priv->state.wrap_style != 2)) { + } else if (len >= max_text_width && + cur->symbol != ' ' /* get trimmed */ && + (render_priv->state.wrap_style != 2)) { break_type = 1; break_at = last_breakable; if (break_at >= 0) ass_msg(render_priv->library, MSGL_DBG2, "line break at %d", break_at); } + if (ALLOWBREAK(cur->symbol, i)) { + last_breakable = i; + } if (break_at != -1) { // need to use one more line @@ -1690,7 +1707,7 @@ wrap_lines_naive(ASS_Renderer *render_priv, double max_text_width) * FIXME: implement style 0 and 3 correctly */ static void -wrap_lines_rebalance(ASS_Renderer *render_priv, double max_text_width) +wrap_lines_rebalance(ASS_Renderer *render_priv, double max_text_width, char *unibrks) { TextInfo *text_info = &render_priv->text_info; int exit = 0; @@ -1711,10 +1728,12 @@ wrap_lines_rebalance(ASS_Renderer *render_priv, double max_text_width) double l1, l2, l1_new, l2_new; GlyphInfo *w = s2; + // Find last word of line and trim surrounding whitespace before measuring + // (whitespace ' ' will also get trimmed in rendering) do { --w; } while ((w > s1) && (w->symbol == ' ')); - while ((w > s1) && (w->symbol != ' ')) { + while ((w > s1) && (!ALLOWBREAK(w->symbol, w - text_info->glyphs))) { --w; } GlyphInfo *e1 = w; @@ -1755,7 +1774,7 @@ wrap_lines_rebalance(ASS_Renderer *render_priv, double max_text_width) } static void -wrap_lines_measure(ASS_Renderer *render_priv) +wrap_lines_measure(ASS_Renderer *render_priv, char *unibrks) { TextInfo *text_info = &render_priv->text_info; int cur_line = 1; @@ -1769,7 +1788,7 @@ wrap_lines_measure(ASS_Renderer *render_priv) for (i = 0; i < text_info->length; ++i) { GlyphInfo *cur = text_info->glyphs + i; if (cur->linebreak) { - while (i < text_info->length && cur->skip && cur->symbol != '\n') + while (i < text_info->length && cur->skip && !FORCEBREAK(cur->symbol, i)) cur = text_info->glyphs + ++i; double height = text_info->lines[cur_line - 1].desc + @@ -1788,6 +1807,9 @@ wrap_lines_measure(ASS_Renderer *render_priv) text_info->length - text_info->lines[cur_line - 1].offset; } +#undef ALLOWBREAK +#undef FORCEBREAK + /** * \brief rearrange text between lines * \param max_text_width maximal text line width in pixels @@ -1802,12 +1824,34 @@ wrap_lines_measure(ASS_Renderer *render_priv) static void wrap_lines_smart(ASS_Renderer *render_priv, double max_text_width) { - wrap_lines_naive(render_priv, max_text_width); - wrap_lines_rebalance(render_priv, max_text_width); + char *unibrks = NULL; + +#ifdef CONFIG_UNIBREAK + if (render_priv->track->parser_priv->feature_flags & FEATURE_MASK(ASS_FEATURE_WRAP_UNICODE)) { + unibrks = render_priv->text_info.breaks; + set_linebreaks_utf32( + render_priv->text_info.event_text, render_priv->text_info.length, + render_priv->track->Language, unibrks); +#if UNIBREAK_VERSION < 0x0500UL + // Prior to 5.0 libunibreaks always ended text with LINE_BREAKMUSTBREAK, matching + // Unicode spec, but messing with our text-overflow detection. + // Thus reevaluate the last char in a different context. + // (Later versions set either MUSTBREAK or the newly added INDETERMINATE) + unibrks[render_priv->text_info.length - 1] = is_line_breakable( + render_priv->text_info.event_text[render_priv->text_info.length - 1], + ' ', + render_priv->track->Language + ); +#endif + } +#endif + + wrap_lines_naive(render_priv, max_text_width, unibrks); + wrap_lines_rebalance(render_priv, max_text_width, unibrks); trim_whitespace(render_priv); measure_text(render_priv); - wrap_lines_measure(render_priv); + wrap_lines_measure(render_priv, unibrks); } /** @@ -1974,7 +2018,8 @@ static bool parse_events(ASS_Renderer *render_priv, ASS_Event *event) if (text_info->length >= new_max) goto fail; if (!ASS_REALLOC_ARRAY(text_info->glyphs, new_max) || - !ASS_REALLOC_ARRAY(text_info->event_text, new_max)) + !ASS_REALLOC_ARRAY(text_info->event_text, new_max) || + !ASS_REALLOC_ARRAY(text_info->breaks, new_max)) goto fail; text_info->max_glyphs = new_max; } diff --git a/libass/ass_render.h b/libass/ass_render.h index 58b6aa20..19d03f39 100644 --- a/libass/ass_render.h +++ b/libass/ass_render.h @@ -188,6 +188,7 @@ typedef struct { typedef struct { GlyphInfo *glyphs; FriBidiChar *event_text; + char *breaks; int length; LineInfo *lines; int n_lines; |