]> granicus.if.org Git - libass/commitdiff
Provisional bidi and shaping support
authorGrigori Goronzy <greg@blackbox>
Wed, 6 Jul 2011 21:19:54 +0000 (23:19 +0200)
committerGrigori Goronzy <greg@blackbox>
Wed, 6 Jul 2011 21:24:30 +0000 (23:24 +0200)
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
libass/ass_render.c
libass/ass_render.h
libass/ass_shaper.c [new file with mode: 0644]
libass/ass_shaper.h [new file with mode: 0644]

index 375f8e644d1f7bdb0e05231a0a5a8e04ce397fcd..142de68fbb429ce491a023754678c0c455690ff6 100644 (file)
@@ -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
 
index f4fd67c4cade45ca282349c64b790f018e099580..78c8606532272014f33a5e5f3fb09c97f3415261 100644 (file)
@@ -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;
index ea72cd0e740f898a3856de27bd53af7592c44cdf..8b446e627babec763f4b6369b59cd61cc7c750a8 100644 (file)
@@ -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 (file)
index 0000000..86814be
--- /dev/null
@@ -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 (file)
index 0000000..7dc2f27
--- /dev/null
@@ -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