[gnome-font-viewer] Add support for rendering complex scripts



commit 1dcbdd55a55fcc3ffc1e8f3d7b3ef9ff924005a0
Author: Khaled Hosny <khaledhosny eglug org>
Date:   Tue Sep 17 21:32:23 2013 +0200

    Add support for rendering complex scripts
    
    Since Pango does not (yet) have a API to use a cairo_font_face_t for
    rendering, HarfBuzz is used directly instead to do the actual text
    layout, but Pango is still (ab)use to do text itemization.
    
    The thumbnailer still does not do complex text, but I don’t think it
    actually need to, so it was left alone.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=346942

 configure.ac            |    1 +
 src/sushi-font-widget.c |  129 ++++++++++++++++++++++++++++++++++++++++++++---
 src/sushi-font-widget.h |    1 +
 3 files changed, 124 insertions(+), 7 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index d6ff8c9..27fca32 100644
--- a/configure.ac
+++ b/configure.ac
@@ -39,6 +39,7 @@ GTK_MIN_VERSION=3.11.4
 PKG_CHECK_MODULES(FONTVIEW,
                   fontconfig
                   freetype2
+                  harfbuzz >= 0.9.9
                   gnome-desktop-3.0
                   gio-2.0 >= $GLIB_MIN_VERSION
                   glib-2.0 >= $GLIB_MIN_VERSION
diff --git a/src/sushi-font-widget.c b/src/sushi-font-widget.c
index 76634fc..9faa10a 100644
--- a/src/sushi-font-widget.c
+++ b/src/sushi-font-widget.c
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2011 Red Hat, Inc.
+ * Copyright (C) 2014 Khaled Hosny <khaledhosny eglug org>.
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -70,6 +71,108 @@ static const gchar lowercase_text_stock[] = "abcdefghijklmnopqrstuvwxyz";
 static const gchar uppercase_text_stock[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
 static const gchar punctuation_text_stock[] = "0123456789.:,;(*!?')";
 
+static void
+text_to_glyphs (cairo_t *cr,
+                const gchar *text,
+                cairo_glyph_t **glyphs,
+                int *num_glyphs)
+{
+  PangoAttribute *fallback_attr;
+  PangoAttrList *attr_list;
+  PangoContext *context;
+  PangoDirection base_dir;
+  GList *items;
+  GList *visual_items;
+  FT_Face ft_face;
+  hb_font_t *hb_font;
+  gdouble x = 0, y = 0;
+  gint i;
+
+  *num_glyphs = 0;
+  *glyphs = NULL;
+
+  base_dir = pango_find_base_dir (text, -1);
+
+  cairo_scaled_font_t *cr_font = cairo_get_scaled_font (cr);
+  ft_face = cairo_ft_scaled_font_lock_face (cr_font);
+  hb_font = hb_ft_font_create (ft_face, NULL);
+
+  /* We abuse pango itemazation to split text into script and direction
+   * runs, since we use our fonts directly no through pango, we don't
+   * bother changing the default font, but we disable font fallback as
+   * pango will split runs at font change */
+  context = pango_cairo_create_context (cr);
+  attr_list = pango_attr_list_new ();
+  fallback_attr = pango_attr_fallback_new (FALSE);
+  pango_attr_list_insert (attr_list, fallback_attr);
+  items = pango_itemize_with_base_dir (context, base_dir,
+                                       text, 0, strlen (text),
+                                       attr_list, NULL);
+  g_object_unref (context);
+  pango_attr_list_unref (attr_list);
+
+  /* reorder the items in the visual order */
+  visual_items = pango_reorder_items (items);
+
+  while (visual_items) {
+    PangoItem *item;
+    PangoAnalysis analysis;
+    hb_buffer_t *hb_buffer;
+    hb_glyph_info_t *hb_glyphs;
+    hb_glyph_position_t *hb_positions;
+    gint n;
+
+    item = visual_items->data;
+    analysis = item->analysis;
+
+    hb_buffer = hb_buffer_create ();
+    hb_buffer_add_utf8 (hb_buffer, text, -1, item->offset, item->length);
+    hb_buffer_set_script (hb_buffer, hb_glib_script_to_script (analysis.script));
+    hb_buffer_set_language (hb_buffer, hb_language_from_string (pango_language_to_string 
(analysis.language), -1));
+    hb_buffer_set_direction (hb_buffer, analysis.level % 2 ? HB_DIRECTION_RTL : HB_DIRECTION_LTR);
+
+    hb_shape (hb_font, hb_buffer, NULL, 0);
+
+    n = hb_buffer_get_length (hb_buffer);
+    hb_glyphs = hb_buffer_get_glyph_infos (hb_buffer, NULL);
+    hb_positions = hb_buffer_get_glyph_positions (hb_buffer, NULL);
+
+    *glyphs = g_renew (cairo_glyph_t, *glyphs, *num_glyphs + n);
+
+    for (i = 0; i < n; i++) {
+      (*glyphs)[*num_glyphs + i].index = hb_glyphs[i].codepoint;
+      (*glyphs)[*num_glyphs + i].x = x + (hb_positions[i].x_offset / 64.);
+      (*glyphs)[*num_glyphs + i].y = y - (hb_positions[i].y_offset / 64.);
+      x += (hb_positions[i].x_advance / 64.);
+      y -= (hb_positions[i].y_advance / 64.);
+    }
+
+    *num_glyphs += n;
+
+    hb_buffer_destroy (hb_buffer);
+
+    visual_items = visual_items->next;
+  }
+
+  g_list_free_full (visual_items, (GDestroyNotify) pango_item_free);
+  g_list_free_full (items, (GDestroyNotify) pango_item_free);
+
+  hb_font_destroy (hb_font);
+  cairo_ft_scaled_font_unlock_face (cr_font);
+}
+
+static void
+text_extents (cairo_t *cr,
+              const char *text,
+              cairo_text_extents_t *extents)
+{
+  cairo_glyph_t *glyphs;
+  gint num_glyphs;
+  text_to_glyphs (cr, text, &glyphs, &num_glyphs);
+  cairo_glyph_extents (cr, glyphs, num_glyphs, extents);
+  g_free (glyphs);
+}
+
 /* adapted from gnome-utils:font-viewer/font-view.c
  *
  * Copyright (C) 2002-2003  James Henstridge <james daa com au>
@@ -86,13 +189,18 @@ draw_string (SushiFontWidget *self,
 {
   cairo_font_extents_t font_extents;
   cairo_text_extents_t extents;
+  cairo_glyph_t *glyphs;
   GtkTextDirection text_dir;
   gint pos_x;
+  gint num_glyphs;
+  gint i;
 
   text_dir = gtk_widget_get_direction (GTK_WIDGET (self));
 
+  text_to_glyphs (cr, text, &glyphs, &num_glyphs);
+
   cairo_font_extents (cr, &font_extents);
-  cairo_text_extents (cr, text, &extents);
+  cairo_glyph_extents (cr, glyphs, num_glyphs, &extents);
 
   if (pos_y != NULL)
     *pos_y += font_extents.ascent + font_extents.descent +
@@ -104,8 +212,15 @@ draw_string (SushiFontWidget *self,
       extents.x_advance - padding.right;
   }
 
+  for (i = 0; i < num_glyphs; i++) {
+    glyphs[i].x += pos_x;
+    glyphs[i].y += *pos_y;
+  }
+
   cairo_move_to (cr, pos_x, *pos_y);
-  cairo_show_text (cr, text);
+  cairo_show_glyphs (cr, glyphs, num_glyphs);
+
+  g_free (glyphs);
 
   *pos_y += LINE_SPACING / 2;
 }
@@ -373,7 +488,7 @@ sushi_font_widget_size_request (GtkWidget *drawing_area,
   if (self->priv->font_name != NULL) {
       cairo_set_font_size (cr, title_size);
       cairo_font_extents (cr, &font_extents);
-      cairo_text_extents (cr, self->priv->font_name, &extents);
+      text_extents (cr, self->priv->font_name, &extents);
       pixmap_height += font_extents.ascent + font_extents.descent +
         extents.y_advance + LINE_SPACING;
       pixmap_width = MAX (pixmap_width, extents.width + padding.left + padding.right);
@@ -384,21 +499,21 @@ sushi_font_widget_size_request (GtkWidget *drawing_area,
   cairo_font_extents (cr, &font_extents);
 
   if (self->priv->lowercase_text != NULL) {
-    cairo_text_extents (cr, self->priv->lowercase_text, &extents);
+    text_extents (cr, self->priv->lowercase_text, &extents);
     pixmap_height += font_extents.ascent + font_extents.descent + 
       extents.y_advance + LINE_SPACING;
     pixmap_width = MAX (pixmap_width, extents.width + padding.left + padding.right);
   }
 
   if (self->priv->uppercase_text != NULL) {
-    cairo_text_extents (cr, self->priv->uppercase_text, &extents);
+    text_extents (cr, self->priv->uppercase_text, &extents);
     pixmap_height += font_extents.ascent + font_extents.descent +
       extents.y_advance + LINE_SPACING;
     pixmap_width = MAX (pixmap_width, extents.width + padding.left + padding.right);
   }
 
   if (self->priv->punctuation_text != NULL) {
-    cairo_text_extents (cr, self->priv->punctuation_text, &extents);
+    text_extents (cr, self->priv->punctuation_text, &extents);
     pixmap_height += font_extents.ascent + font_extents.descent +
       extents.y_advance + LINE_SPACING;
     pixmap_width = MAX (pixmap_width, extents.width + padding.left + padding.right);
@@ -410,7 +525,7 @@ sushi_font_widget_size_request (GtkWidget *drawing_area,
     for (i = 0; i < n_sizes; i++) {
       cairo_set_font_size (cr, sizes[i]);
       cairo_font_extents (cr, &font_extents);
-      cairo_text_extents (cr, self->priv->sample_string, &extents);
+      text_extents (cr, self->priv->sample_string, &extents);
       pixmap_height += font_extents.ascent + font_extents.descent +
         extents.y_advance + LINE_SPACING;
       pixmap_width = MAX (pixmap_width, extents.width + padding.left + padding.right);
diff --git a/src/sushi-font-widget.h b/src/sushi-font-widget.h
index e60815f..dd179dd 100644
--- a/src/sushi-font-widget.h
+++ b/src/sushi-font-widget.h
@@ -31,6 +31,7 @@
 #include <glib-object.h>
 #include <gtk/gtk.h>
 #include <cairo/cairo-ft.h>
+#include <hb-ft.h>
 
 G_BEGIN_DECLS
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]