]> granicus.if.org Git - libass/commitdiff
HarfBuzz shaping support
authorGrigori Goronzy <greg@blackbox>
Mon, 11 Jul 2011 11:00:08 +0000 (13:00 +0200)
committerGrigori Goronzy <greg@blackbox>
Mon, 11 Jul 2011 11:05:52 +0000 (13:05 +0200)
Split up text into runs with the same direction, font face and font
size, shape these runs with HarfBuzz and reorder accordingly.

This noticeably improves Arabic shaping and should make shaping for many
other scripts work. HarfBuzz also does kerning for Latin text.

libass/ass_cache_template.h
libass/ass_font.c
libass/ass_render.c
libass/ass_render.h
libass/ass_shaper.c

index 2339e2bcaba018906a643366b84dd06aaab8d25f..d6f7d1fc6cfa5012ab7923b94fa2d3485f1acb10 100644 (file)
@@ -87,7 +87,8 @@ END(ClipMaskHashKey)
 START(glyph, glyph_hash_key)
     GENERIC(ASS_Font *, font)
     GENERIC(double, size) // font size
-    GENERIC(uint32_t, ch) // character code
+    GENERIC(int, face_index)
+    GENERIC(int, glyph_index)
     GENERIC(int, bold)
     GENERIC(int, italic)
     GENERIC(unsigned, scale_x) // 16.16
index af1f350dc60fe2f9b7ecfc3e420dcef0c9bf8c8a..14790b49dfbf9463c26ac03d02534ee7d1f35d22 100644 (file)
@@ -433,24 +433,32 @@ int ass_font_get_index(void *fcpriv, ASS_Font *font, uint32_t symbol,
     int i;
     FT_Face face = 0;
 
-    *face_index = 0;
-    *face_index = 0;
+    *glyph_index = 0;
 
-    if (symbol < 0x20)
+    if (symbol < 0x20) {
+        *face_index = 0;
         return 0;
+    }
     // Handle NBSP like a regular space when rendering the glyph
     if (symbol == 0xa0)
         symbol = ' ';
-    if (font->n_faces == 0)
+    if (font->n_faces == 0) {
+        *face_index = 0;
         return 0;
+    }
 
-    for (i = 0; i < font->n_faces; ++i) {
+    // try with the requested face
+    if (*face_index < font->n_faces) {
+        face = font->faces[i];
+        index = FT_Get_Char_Index(face, symbol);
+    }
+
+    // not found in requested face, try all others
+    for (i = 0; i < font->n_faces && index == 0; ++i) {
         face = font->faces[i];
         index = FT_Get_Char_Index(face, symbol);
-        if (index) {
+        if (index)
             *face_index = i;
-            break;
-        }
     }
 
 #ifdef CONFIG_FONTCONFIG
index 3aaf94358a6d704aab847034c5c9fbd3aa891941..b634577a9f5c06369df8a284282bfcae9a699771 100644 (file)
@@ -1050,7 +1050,8 @@ fill_glyph_hash(ASS_Renderer *priv, OutlineHashKey *outline_key,
         outline_key->type = OUTLINE_GLYPH;
         key->font = info->font;
         key->size = info->font_size;
-        key->ch = info->symbol;
+        key->face_index = info->face_index;
+        key->glyph_index = info->glyph_index;
         key->bold = info->bold;
         key->italic = info->italic;
         key->scale_x = double_to_d16(info->scale_x);
@@ -1085,8 +1086,11 @@ get_outline_glyph(ASS_Renderer *render_priv, GlyphInfo *info)
         info->outline = val->outline;
         info->border = val->border;
         info->bbox = val->bbox_scaled;
-        info->advance.x = val->advance.x;
-        info->advance.y = val->advance.y;
+        // XXX: more elegant solution?
+        if (info->drawing) {
+            info->advance.x = info->drawing->advance.x;
+            info->advance.y = info->drawing->advance.y;
+        }
         info->asc = val->asc;
         info->desc = val->desc;
     } else {
@@ -1106,22 +1110,22 @@ get_outline_glyph(ASS_Renderer *render_priv, GlyphInfo *info)
         } else {
             double size_scaled = ensure_font_size(render_priv,
                     info->font_size * render_priv->font_scale);
-            int face_index = 0;
-            int index = 0;
             ass_font_set_size(info->font, size_scaled);
             ass_font_set_transform(info->font, info->scale_x,
                     info->scale_y, NULL);
-            ass_font_get_index(render_priv->fontconfig_priv, info->font,
-                    info->symbol, &face_index, &index);
+            // symbol might have been changed. re-get it.
+            //if (info->face_index < 0)
+            //    ass_font_get_index(render_priv->fontconfig_priv, info->font,
+            //            info->symbol, &info->face_index, &info->glyph_index);
             FT_Glyph glyph =
                 ass_font_get_glyph(render_priv->fontconfig_priv, info->font,
-                        info->symbol, face_index, index,
+                        info->symbol, info->face_index, info->glyph_index,
                         render_priv->settings.hinting, info->flags);
             if (glyph != NULL) {
                 outline_copy(render_priv->ftlibrary,
                         &((FT_OutlineGlyph)glyph)->outline, &info->outline);
-                info->advance.x = d16_to_d6(glyph->advance.x);
-                info->advance.y = d16_to_d6(glyph->advance.y);
+                //info->advance.x = d16_to_d6(glyph->advance.x);
+                //info->advance.y = d16_to_d6(glyph->advance.y);
                 FT_Done_Glyph(glyph);
                 ass_font_get_asc_desc(info->font, info->symbol,
                         &info->asc, &info->desc);
@@ -1795,6 +1799,35 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event,
 
     }
 
+    // Determine shape runs
+    int shape_run = 0;
+    for (i = 0; i < text_info->length; i++) {
+        GlyphInfo *last = glyphs + i - 1;
+        GlyphInfo *info = glyphs + i;
+        // skip drawings
+        if (info->symbol == 0xfffc)
+            continue;
+        // initialize face_index to continue with the same face, if possible
+        // XXX: can be problematic in some cases, for example if a font misses
+        // a single glyph, like space (U+0020)
+        if (i > 0)
+            info->face_index = last->face_index;
+        // set size and get glyph index
+        double size_scaled = ensure_font_size(render_priv,
+                info->font_size * render_priv->font_scale);
+        ass_font_set_size(info->font, size_scaled);
+        ass_font_get_index(render_priv->fontconfig_priv, info->font,
+                info->symbol, &info->face_index, &info->glyph_index);
+        // shape runs share the same font face and size
+        if (i > 0 && (last->font != info->font ||
+                      last->font_size != info->font_size ||
+                      last->face_index != info->face_index))
+            shape_run++;
+        info->shape_run_id = shape_run;
+        //printf("glyph '%c' shape run id %d face %d\n", info->symbol, info->shape_run_id,
+        //        info->face_index);
+    }
+
     if (text_info->length == 0) {
         // no valid symbols in the event; this can be smth like {comment}
         free_render_context(render_priv);
@@ -1831,14 +1864,6 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event,
         GlyphInfo *info = glyphs + i;
 
 #if 0
-        // Add kerning to pen
-        if (kern && previous && info->symbol && !info->drawing) {
-            FT_Vector delta;
-            delta = ass_font_get_kerning(info->font, previous, info->symbol);
-            pen.x += delta.x * info->scale_x;
-            pen.y += delta.y * info->scale_y;
-        }
-
         // Add additional space after italic to non-italic style changes
         if (i && glyphs[i - 1].italic && !info->italic) {
             int back = i - 1;
@@ -1916,8 +1941,8 @@ ass_render_event(ASS_Renderer *render_priv, ASS_Event *event,
             lineno++;
         }
         if (info->skip) continue;
-        info->pos.x = pen.x;
-        info->pos.y = pen.y;
+        info->pos.x = info->offset.x + pen.x;
+        info->pos.y = info->offset.y + pen.y;
         pen.x += info->advance.x;
         pen.y += info->advance.y;
     }
index 8b446e627babec763f4b6369b59cd61cc7c750a8..ea29c793df5e76488270ccc971e57dccf251cc85 100644 (file)
@@ -100,6 +100,8 @@ typedef struct {
     unsigned symbol;
     unsigned skip;              // skip glyph when layouting text
     ASS_Font *font;
+    int face_index;
+    int glyph_index;
     double font_size;
     ASS_Drawing *drawing;
     FT_Outline *outline;
@@ -109,6 +111,7 @@ typedef struct {
     Bitmap *bm_s;               // shadow bitmap
     FT_BBox bbox;
     FT_Vector pos;
+    FT_Vector offset;
     char linebreak;             // the first (leading) glyph of some line ?
     uint32_t c[4];              // colors
     FT_Vector advance;          // 26.6
@@ -131,6 +134,7 @@ typedef struct {
     int flags;
 
     int bm_run_id;
+    int shape_run_id;
 
     BitmapHashKey hash_key;
 } GlyphInfo;
index 6efc177d6fb85ce5779368e64c31f50e38d82cb8..911732c06f83b7691592098cee08f27e71bf5074 100644 (file)
  */
 
 #include <fribidi/fribidi.h>
+#include <hb-ft.h>
 
 #include "ass_render.h"
 #include "ass_shaper.h"
 
+#define MAX_RUNS 30
+
 /**
  * \brief Print version information
  */
 void ass_shaper_info(ASS_Library *lib)
 {
     ass_msg(lib, MSGL_V, "Complex text layout enabled, using FriBidi "
-            FRIBIDI_VERSION);
+            FRIBIDI_VERSION " HarfBuzz-ng %s", hb_version_string());
 }
 
 /**
@@ -39,11 +42,18 @@ void ass_shaper_info(ASS_Library *lib)
 void ass_shaper_shape(TextInfo *text_info, FriBidiCharType *ctypes,
                       FriBidiLevel *emblevels)
 {
-    int i, last_break;
+    int i, j, last_break;
     FriBidiParType dir;
     FriBidiChar *event_text = calloc(sizeof(*event_text), text_info->length);
     FriBidiJoiningType *joins = calloc(sizeof(*joins), text_info->length);
     GlyphInfo *glyphs = text_info->glyphs;
+    // XXX: dynamically allocate
+    struct {
+        int offset;
+        int end;
+        hb_buffer_t *buf;
+        hb_font_t *font;
+    } runs[MAX_RUNS];
 
     // Get bidi character types and embedding levels
     last_break = 0;
@@ -61,26 +71,89 @@ void ass_shaper_shape(TextInfo *text_info, FriBidiCharType *ctypes,
         }
     }
 
+    // add embedding levels to shape runs for final runs
+    for (i = 0; i < text_info->length; i++) {
+        glyphs[i].shape_run_id += emblevels[i];
+    }
+
 #if 0
     printf("levels ");
     for (i = 0; i < text_info->length; i++) {
-        printf("%d:%d ", ctypes[i], emblevels[i]);
+        printf("%d ", glyphs[i].shape_run_id);
     }
     printf("\n");
 #endif
 
+#if 0
     // Use FriBidi's shaper for mirroring and simple Arabic shaping
     fribidi_get_joining_types(event_text, text_info->length, joins);
     fribidi_join_arabic(ctypes, text_info->length, emblevels, joins);
     fribidi_shape(FRIBIDI_FLAGS_DEFAULT | FRIBIDI_FLAGS_ARABIC, emblevels,
             text_info->length, joins, event_text);
+#endif
+
+    // Shape runs with HarfBuzz-ng
+    int run = 0;
+    for (i = 0; i < text_info->length && run < MAX_RUNS; i++, run++) {
+        // get length and level of the current run
+        int k = i;
+        int level = glyphs[i].shape_run_id;
+        while (i < (text_info->length - 1) && level == glyphs[i+1].shape_run_id)
+            i++;
+        //printf("run %d from %d to %d with level %d\n", run, k, i, level);
+        FT_Face run_font = glyphs[k].font->faces[glyphs[k].face_index];
+        runs[run].offset = k;
+        runs[run].end    = i;
+        runs[run].buf    = hb_buffer_create(i - k + 1);
+        runs[run].font   = hb_ft_font_create(run_font, NULL);
+        hb_buffer_set_direction(runs[run].buf, (level % 2) ? HB_DIRECTION_RTL :
+                HB_DIRECTION_LTR);
+        hb_buffer_add_utf32(runs[run].buf, event_text + k, i - k + 1,
+                0, i - k + 1);
+        hb_shape(runs[run].font, runs[run].buf, NULL, 0);
+    }
+    //printf("shaped %d runs\n", run);
 
-    // XXX: insert HarfBuzz shaper here
+    // Initialize: skip all glyphs, this is undone later as needed
+    for (i = 0; i < text_info->length; i++)
+        glyphs[i].skip = 1;
+
+    // Update glyph indexes, positions and advances from the shaped runs
+    for (i = 0; i < run; i++) {
+        int num_glyphs = hb_buffer_get_length(runs[i].buf);
+        hb_glyph_info_t *glyph_info = hb_buffer_get_glyph_infos(runs[i].buf, NULL);
+        hb_glyph_position_t *pos    = hb_buffer_get_glyph_positions(runs[i].buf, NULL);
+        //printf("run text len %d num_glyphs %d\n", runs[i].end - runs[i].offset + 1,
+        //        num_glyphs);
+        // Update glyphs
+        for (j = 0; j < num_glyphs; j++) {
+            int idx = glyph_info[j].cluster + runs[i].offset;
+#if 0
+            printf("run %d cluster %d codepoint %d -> '%c'\n", i, idx,
+                    glyph_info[j].codepoint, event_text[idx]);
+            printf("position %d %d advance %d %d\n",
+                    pos[j].x_offset, pos[j].y_offset,
+                    pos[j].x_advance, pos[j].y_advance);
+#endif
+            glyphs[idx].skip = 0;
+            glyphs[idx].glyph_index = glyph_info[j].codepoint;
+            glyphs[idx].offset.x    = pos[j].x_offset * glyphs[idx].scale_x;
+            glyphs[idx].offset.y    = pos[j].y_offset * glyphs[idx].scale_y;
+            glyphs[idx].advance.x   = pos[j].x_advance * glyphs[idx].scale_x;
+            glyphs[idx].advance.y   = pos[j].y_advance * glyphs[idx].scale_y;
+        }
+    }
+
+    // Free runs and associated data
+    for (i = 0; i < run; i++) {
+        hb_buffer_destroy(runs[i].buf);
+        hb_font_destroy(runs[i].font);
+    }
 
     // Update glyphs
     for (i = 0; i < text_info->length; i++) {
         glyphs[i].symbol = event_text[i];
-        // Skip direction override characters
+        // Skip direction override control characters
         // NOTE: Behdad said HarfBuzz is supposed to remove these, but this hasn't
         // been implemented yet
         if (glyphs[i].symbol <= 0x202F && glyphs[i].symbol >= 0x202a) {