summaryrefslogtreecommitdiffstats
path: root/libass
diff options
context:
space:
mode:
authorGrigori Goronzy <greg@blackbox>2011-07-06 23:19:54 +0200
committerGrigori Goronzy <greg@blackbox>2011-07-06 23:24:30 +0200
commit3bb8743641c398e0807322ea74754d7e54fbbc3d (patch)
treee922f720492f52868fa7031fcbb63c18299714b1 /libass
parent87d472953d5eb58a0e0b8d6414e7ffcd3678d83a (diff)
downloadlibass-3bb8743641c398e0807322ea74754d7e54fbbc3d.tar.bz2
libass-3bb8743641c398e0807322ea74754d7e54fbbc3d.tar.xz
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.
Diffstat (limited to 'libass')
-rw-r--r--libass/Makefile.am3
-rw-r--r--libass/ass_render.c78
-rw-r--r--libass/ass_render.h1
-rw-r--r--libass/ass_shaper.c115
-rw-r--r--libass/ass_shaper.h28
5 files changed, 210 insertions, 15 deletions
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 <greg@chown.ath.cx>
+ *
+ * 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 <fribidi/fribidi.h>
+
+#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 <greg@chown.ath.cx>
+ *
+ * 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 <fribidi/fribidi.h>
+
+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