From 3bb8743641c398e0807322ea74754d7e54fbbc3d Mon Sep 17 00:00:00 2001 From: Grigori Goronzy Date: Wed, 6 Jul 2011 23:19:54 +0200 Subject: Provisional bidi and shaping support Adds fully working bidirectional text and simple shaping support. The following works: - bidirectional text according to the Unicode Bidirectional Algorithm - simple shaper for mirrored forms (brackets, etc.) according to rule L4 of the Unicode Bidirectional Algorithm - reordering into visual order with correct line wrapping However, the implementation certainly needs efficiency improvements (caching, less malloc'ing), a proper shaper (HarfBuzz) needs to be hooked up and various bugs with karaoke and positioning need to be fixed. --- libass/Makefile.am | 3 +- libass/ass_render.c | 78 ++++++++++++++++++++++++++++------- libass/ass_render.h | 1 + libass/ass_shaper.c | 115 ++++++++++++++++++++++++++++++++++++++++++++++++++++ libass/ass_shaper.h | 28 +++++++++++++ 5 files changed, 210 insertions(+), 15 deletions(-) create mode 100644 libass/ass_shaper.c create mode 100644 libass/ass_shaper.h (limited to 'libass') diff --git a/libass/Makefile.am b/libass/Makefile.am index 375f8e6..142de68 100644 --- a/libass/Makefile.am +++ b/libass/Makefile.am @@ -10,7 +10,8 @@ libass_la_SOURCES = ass.c ass_cache.c ass_font.c ass_fontconfig.c ass_render.c \ ass_cache.h ass_fontconfig.h ass_font.h ass.h \ ass_library.h ass_types.h ass_utils.h ass_drawing.c \ ass_drawing.h ass_cache_template.h ass_render.h \ - ass_parse.c ass_parse.h ass_render_api.c ass_strtod.c + ass_parse.c ass_parse.h ass_render_api.c ass_shaper.c \ + ass_shaper.h ass_strtod.c libass_la_LDFLAGS = -version-info $(LIBASS_LT_CURRENT):$(LIBASS_LT_REVISION):$(LIBASS_LT_AGE) libass_la_LDFLAGS += -export-symbols $(srcdir)/libass.sym diff --git a/libass/ass_render.c b/libass/ass_render.c index f4fd67c..78c8606 100644 --- a/libass/ass_render.c +++ b/libass/ass_render.c @@ -23,6 +23,7 @@ #include "ass_render.h" #include "ass_parse.h" +#include "ass_shaper.h" #define MAX_GLYPHS_INITIAL 1024 #define MAX_LINES_INITIAL 64 @@ -1572,6 +1573,9 @@ wrap_lines_smart(ASS_Renderer *render_priv, double max_text_width) double height = text_info->lines[cur_line - 1].desc + text_info->lines[cur_line].asc; + text_info->lines[cur_line - 1].len = i - + text_info->lines[cur_line - 1].offset; + text_info->lines[cur_line].offset = i; cur_line++; run_offset++; pen_shift_x = d6_to_double(-cur->pos.x); @@ -1584,6 +1588,16 @@ wrap_lines_smart(ASS_Renderer *render_priv, double max_text_width) cur->pos.x += double_to_d6(pen_shift_x); cur->pos.y += double_to_d6(pen_shift_y); } + text_info->lines[cur_line - 1].len = + text_info->length - text_info->lines[cur_line - 1].offset; + +#if 0 + // print line info + for (i = 0; i < text_info->n_lines; i++) { + printf("line %d offset %d length %d\n", i, text_info->lines[i].offset, + text_info->lines[i].len); + } +#endif } /** @@ -1774,13 +1788,42 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event, } - // Retrieve and layout outline glyphs into a line + if (text_info->length == 0) { + // no valid symbols in the event; this can be smth like {comment} + free_render_context(render_priv); + return 1; + } + + // Allocate bidi work arrays + FriBidiCharType *ctypes = calloc(sizeof(*ctypes), text_info->length); + FriBidiLevel *emblevels = calloc(sizeof(*emblevels), text_info->length); + FriBidiStrIndex *cmap = calloc(sizeof(*cmap), text_info->length); + + // Shape text + ass_shaper_shape(text_info, ctypes, emblevels); + + // Retrieve glyphs + for (i = 0; i < text_info->length; i++) { + GlyphInfo *info = glyphs + i; + get_outline_glyph(render_priv, info); + + // add displacement for vertical shearing + info->advance.y += (info->fay * info->scale_y) * info->advance.x; + + // add horizontal letter spacing + info->advance.x += double_to_d6(render_priv->state.hspacing * + render_priv->font_scale * info->scale_x); + + } + + // Preliminary layout (for line wrapping) previous = 0; pen.x = 0; pen.y = 0; for (i = 0; i < text_info->length; i++) { GlyphInfo *info = glyphs + i; +#if 0 // Add kerning to pen if (kern && previous && info->symbol && !info->drawing) { FT_Vector delta; @@ -1789,9 +1832,6 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event, pen.y += delta.y * info->scale_y; } - // Retrieve outline - get_outline_glyph(render_priv, info); - // Add additional space after italic to non-italic style changes if (i && glyphs[i - 1].italic && !info->italic) { int back = i - 1; @@ -1804,15 +1844,13 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event, pen.x += og->bbox.yMax * 0.375; } } +#endif info->pos.x = pen.x; info->pos.y = pen.y; pen.x += info->advance.x; - pen.x += double_to_d6(render_priv->state.hspacing * - render_priv->font_scale * info->scale_x); pen.y += info->advance.y; - pen.y += (info->fay * info->scale_y) * info->advance.x; previous = info->symbol; @@ -1821,11 +1859,6 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event, fill_bitmap_hash(render_priv, info, &info->hash_key.u.outline); } - if (text_info->length == 0) { - // no valid symbols in the event; this can be smth like {comment} - free_render_context(render_priv); - return 1; - } // depends on glyph x coordinates being monotonous, so it should be done before line wrap process_karaoke_effects(render_priv); @@ -1854,11 +1887,24 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event, // rearrange text in several lines wrap_lines_smart(render_priv, max_text_width); + // Reorder text into visual order + ass_shaper_reorder(text_info, ctypes, emblevels, cmap); + + // Reposition according to the map + // FIXME: y coordinate for shearing, etc. + pen.x = 0; + for (i = 0; i < text_info->length; i++) { + GlyphInfo *info = glyphs + cmap[i]; + if (glyphs[i].linebreak) + pen.x = 0; + info->pos.x = pen.x; + pen.x += info->advance.x; + } + // 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) - || glyphs[i].linebreak) { + if ((i == text_info->length) || glyphs[i].linebreak) { double width, shift = 0; GlyphInfo *first_glyph = glyphs + last_break + 1; @@ -2080,6 +2126,10 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event, event_images->event = event; event_images->imgs = render_text(render_priv, (int) device_x, (int) device_y); + free(ctypes); + free(emblevels); + free(cmap); + free_render_context(render_priv); return 0; diff --git a/libass/ass_render.h b/libass/ass_render.h index ea72cd0..8b446e6 100644 --- a/libass/ass_render.h +++ b/libass/ass_render.h @@ -137,6 +137,7 @@ typedef struct { typedef struct { double asc, desc; + int offset, len; } LineInfo; typedef struct { diff --git a/libass/ass_shaper.c b/libass/ass_shaper.c new file mode 100644 index 0000000..86814be --- /dev/null +++ b/libass/ass_shaper.c @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2011 Grigori Goronzy + * + * This file is part of libass. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include "ass_render.h" +#include "ass_shaper.h" + +/** + * \brief Shape an event's text. Calculates directional runs and shapes them. + * \param text_info event's text + * \param ctypes returns character types + * \param emblevels returns embedding levels (directional runs) + */ +void ass_shaper_shape(TextInfo *text_info, FriBidiCharType *ctypes, + FriBidiLevel *emblevels) +{ + int i, last_break; + FriBidiParType dir; + FriBidiChar *event_text = calloc(sizeof(*event_text), text_info->length); + GlyphInfo *glyphs = text_info->glyphs; + + // Get bidi character types and embedding levels + last_break = 0; + for (i = 0; i < text_info->length; i++) { + event_text[i] = glyphs[i].symbol; + // embedding levels should be calculated paragraph by paragraph + if (glyphs[i].symbol == '\n' || i == text_info->length - 1) { + //printf("paragraph from %d to %d\n", last_break, i); + dir = FRIBIDI_PAR_ON; + fribidi_get_bidi_types(event_text + last_break, i - last_break + 1, + ctypes + last_break); + fribidi_get_par_embedding_levels(ctypes + last_break, + i - last_break + 1, &dir, emblevels + last_break); + last_break = i + 1; + } + } + +#if 0 + printf("levels "); + for (i = 0; i < text_info->length; i++) { + printf("%d:%d ", ctypes[i], emblevels[i]); + } + printf("\n"); +#endif + + // Call FriBidi's glyph mirroring shaper. + // This shaper implements rule L4 of the bidi algorithm + fribidi_shape_mirroring(emblevels, text_info->length, event_text); + for (i = 0; i < text_info->length; i++) { + glyphs[i].symbol = event_text[i]; + } + + // XXX: insert HarfBuzz shaper here + + // Skip direction override characters + // NOTE: Behdad said HarfBuzz is supposed to remove these, but this hasn't + // been implemented yet + for (i = 0; i < text_info->length; i++) { + if (glyphs[i].symbol <= 0x202F && glyphs[i].symbol >= 0x202a) { + glyphs[i].symbol = 0; + glyphs[i].skip++; + } + } + + free(event_text); +} + +void ass_shaper_reorder(TextInfo *text_info, FriBidiCharType *ctypes, + FriBidiLevel *emblevels, FriBidiStrIndex *cmap) +{ + int i; + FriBidiParType dir = FRIBIDI_PAR_LTR; + + // Initialize reorder map + for (i = 0; i < text_info->length; i++) + cmap[i] = i; + + // Create reorder map line-by-line + for (i = 0; i < text_info->n_lines; i++) { + LineInfo *line = text_info->lines + i; + int level; + + // FIXME: we should actually specify + // the correct paragraph base direction + level = fribidi_reorder_line(FRIBIDI_FLAGS_DEFAULT, + ctypes + line->offset, line->len, 0, dir, + emblevels + line->offset, NULL, cmap + line->offset); + //printf("reorder line %d to level %d\n", i, level); + } + +#if 0 + printf("map "); + for (i = 0; i < text_info->length; i++) { + printf("%d ", cmap[i]); + } + printf("\n"); +#endif + +} diff --git a/libass/ass_shaper.h b/libass/ass_shaper.h new file mode 100644 index 0000000..7dc2f27 --- /dev/null +++ b/libass/ass_shaper.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2011 Grigori Goronzy + * + * This file is part of libass. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef ASS_SHAPER_H +#define ASS_SHAPER_H + +#include + +void ass_shaper_shape(TextInfo *text_info, FriBidiCharType *ctypes, + FriBidiLevel *emblevels); +void ass_shaper_reorder(TextInfo *text_info, FriBidiCharType *ctypes, + FriBidiLevel *emblevels, FriBidiStrIndex *cmap); +#endif -- cgit v1.2.3