[sushi] font: add SushiFontLoader and SushiFontWidget objects



commit ba196fdb24a12f9c4c91813f31184d8a4a3e0802
Author: Cosimo Cecchi <cosimoc gnome org>
Date:   Wed May 11 00:54:28 2011 -0400

    font: add SushiFontLoader and SushiFontWidget objects
    
    These are helper objects to load a FT_Face from a generic URI and a
    GtkDrawingArea that draws the font face using cairo.
    Rendering code adapted from the font-viewer module in gnome-utils.

 src/Makefile-sushi.am            |    4 +
 src/libsushi/sushi-font-loader.c |  162 ++++++++++++++
 src/libsushi/sushi-font-loader.h |   16 ++
 src/libsushi/sushi-font-widget.c |  432 ++++++++++++++++++++++++++++++++++++++
 src/libsushi/sushi-font-widget.h |   38 ++++
 5 files changed, 652 insertions(+), 0 deletions(-)
---
diff --git a/src/Makefile-sushi.am b/src/Makefile-sushi.am
index b9fd42c..883f107 100644
--- a/src/Makefile-sushi.am
+++ b/src/Makefile-sushi.am
@@ -18,6 +18,8 @@ sushi_source_h = \
     libsushi/sushi-pdf-loader.h \
     libsushi/sushi-sound-player.h \
     libsushi/sushi-file-loader.h \
+    libsushi/sushi-font-loader.h \
+    libsushi/sushi-font-widget.h \
     libsushi/sushi-utils.h
 
 sushi_source_c = \
@@ -25,6 +27,8 @@ sushi_source_c = \
     libsushi/sushi-pdf-loader.c \
     libsushi/sushi-sound-player.c \
     libsushi/sushi-file-loader.c \
+    libsushi/sushi-font-loader.c \
+    libsushi/sushi-font-widget.c \
     libsushi/sushi-utils.c
 
 sushi-enum-types.h: stamp-sushi-enum-types.h Makefile
diff --git a/src/libsushi/sushi-font-loader.c b/src/libsushi/sushi-font-loader.c
new file mode 100644
index 0000000..eb49c42
--- /dev/null
+++ b/src/libsushi/sushi-font-loader.c
@@ -0,0 +1,162 @@
+#include "sushi-font-loader.h"
+
+#include <stdlib.h>
+#include <ft2build.h>
+#include FT_FREETYPE_H
+
+#include <gio/gio.h>
+
+typedef struct {
+  FT_Library library;
+  FT_Long face_index;
+  GFile *file;
+  GSimpleAsyncResult *result;
+
+  gchar *face_contents;
+  gsize face_length;
+} FontLoadJob;
+
+static FontLoadJob *
+font_load_job_new (const gchar *uri,
+                   GAsyncReadyCallback callback,
+                   gpointer user_data)
+{
+  FontLoadJob *job = NULL;
+  FT_Library library;
+  FT_Error res;
+  GError *error = NULL;
+
+  res = FT_Init_FreeType (&library);
+
+  if (res != 0) {
+    g_set_error_literal (&error,
+                         G_IO_ERROR, 0,
+                         "Can't initialize FreeType");
+    g_simple_async_report_take_gerror_in_idle (NULL,
+                                               callback, user_data,
+                                               error);
+    goto out;
+  }
+
+  job = g_slice_new0 (FontLoadJob);
+
+  job->library = library;
+  job->face_index = 0;
+  job->file = g_file_new_for_uri (uri);
+  job->result = g_simple_async_result_new
+    (NULL, callback, user_data,
+     sushi_new_ft_face_from_uri_async);
+
+  g_simple_async_result_set_op_res_gpointer (job->result, job, NULL);
+
+ out:
+  return job;
+}
+
+static void
+font_load_job_free (FontLoadJob *job)
+{
+  g_clear_object (&job->result);
+  g_clear_object (&job->file);
+
+  g_slice_free (FontLoadJob, job);
+}
+
+static FT_Face
+create_face_from_contents (FontLoadJob *job,
+                           gchar **contents,
+                           GError **error)
+{
+  FT_Error ft_error;
+  FT_Face retval;
+
+  ft_error = FT_New_Memory_Face (job->library,
+                                 job->face_contents,
+                                 (FT_Long) job->face_length,
+                                 job->face_index,
+                                 &retval);
+
+  if (ft_error != 0) {
+    g_set_error_literal (error, G_IO_ERROR, 0,
+                         "Unable to read the font face file");
+    retval = NULL;
+    g_free (job->face_contents);
+  } else {
+    *contents = job->face_contents;
+  }
+
+  return retval;
+}
+
+static gboolean
+font_load_job_callback (gpointer user_data)
+{
+  FontLoadJob *job = user_data;
+
+  g_simple_async_result_complete (job->result);
+  font_load_job_free (job);
+
+  return FALSE;
+}
+
+static gboolean
+font_load_job (GIOSchedulerJob *sched_job,
+               GCancellable *cancellable,
+               gpointer user_data)
+{
+  FontLoadJob *job = user_data;
+  GError *error = NULL;
+  gchar *contents;
+  gsize length;
+  gboolean res;
+
+  res = g_file_load_contents (job->file, NULL,
+                              &contents, &length, NULL, &error);
+
+  if (error != NULL) {
+    g_simple_async_result_take_error (job->result, error);
+  } else {
+    job->face_contents = contents;
+    job->face_length = length;
+  }
+
+  g_io_scheduler_job_send_to_mainloop_async (sched_job,
+                                             font_load_job_callback,
+                                             job, NULL);
+
+  return FALSE;
+}
+
+void
+sushi_new_ft_face_from_uri_async (const gchar *uri,
+                                  GAsyncReadyCallback callback,
+                                  gpointer user_data)
+{
+  FontLoadJob *job = NULL;
+
+  job = font_load_job_new (uri, callback, user_data);
+
+  if (!job)
+    return;
+
+  g_io_scheduler_push_job (font_load_job,
+                           job, NULL,
+                           G_PRIORITY_DEFAULT,
+                           NULL);
+}
+
+FT_Face
+sushi_new_ft_face_from_uri_finish (GAsyncResult *result,
+                                   gchar **contents,
+                                   GError **error)
+{
+  FontLoadJob *job;
+
+  if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result),
+                                             error))
+    return NULL;
+
+  job = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result));
+
+  return create_face_from_contents (job, contents, error);
+}
diff --git a/src/libsushi/sushi-font-loader.h b/src/libsushi/sushi-font-loader.h
new file mode 100644
index 0000000..682e227
--- /dev/null
+++ b/src/libsushi/sushi-font-loader.h
@@ -0,0 +1,16 @@
+#ifndef __SUSHI_FONT_LOADER_H__
+#define __SUSHI_FONT_LOADER_H__
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include <gio/gio.h>
+
+void sushi_new_ft_face_from_uri_async (const gchar *uri,
+                                       GAsyncReadyCallback callback,
+                                       gpointer user_data);
+
+FT_Face sushi_new_ft_face_from_uri_finish (GAsyncResult *result,
+                                           gchar **contents,
+                                           GError **error);
+
+#endif /* __SUSHI_FONT_LOADER_H__ */
diff --git a/src/libsushi/sushi-font-widget.c b/src/libsushi/sushi-font-widget.c
new file mode 100644
index 0000000..ea0ae13
--- /dev/null
+++ b/src/libsushi/sushi-font-widget.c
@@ -0,0 +1,432 @@
+#include "sushi-font-widget.h"
+#include "sushi-font-loader.h"
+
+#include <cairo/cairo-ft.h>
+#include <math.h>
+
+enum {
+  PROP_URI = 1,
+  NUM_PROPERTIES
+};
+
+enum {
+  LOADED,
+  NUM_SIGNALS
+};
+
+struct _SushiFontWidgetPrivate {
+  gchar *uri;
+
+  FT_Face face;
+  gchar *face_contents;
+};
+
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
+static guint signals[NUM_SIGNALS] = { 0, };
+
+G_DEFINE_TYPE (SushiFontWidget, sushi_font_widget, GTK_TYPE_DRAWING_AREA);
+
+#define SECTION_SPACING 16
+
+static const gchar lowercase_text[] = "abcdefghijklmnopqrstuvwxyz";
+static const gchar uppercase_text[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+static const gchar punctuation_text[] = "0123456789.:,;(*!?')";
+
+/* adapted from gnome-utils:font-viewer/font-view.c */
+static void
+draw_string (cairo_t *cr,
+             GtkBorder padding,
+	     const gchar *text,
+	     gint *pos_y)
+{
+  cairo_text_extents_t extents;
+  gdouble cur_x, cur_y;
+
+  cairo_text_extents (cr, text, &extents);
+
+  if (pos_y != NULL)
+    *pos_y += extents.height + extents.y_advance + padding.top;
+
+  cairo_move_to (cr, padding.left, *pos_y);
+  cairo_show_text (cr, text);
+
+  *pos_y += padding.bottom;
+}
+
+static gboolean
+check_font_contain_text (FT_Face face, const gchar *text)
+{
+  while (text && *text) {
+    gunichar wc = g_utf8_get_char (text);
+
+    if (!FT_Get_Char_Index (face, wc))
+      return FALSE;
+
+    text = g_utf8_next_char (text);
+  }
+
+  return TRUE;
+}
+
+static const gchar *
+get_sample_string (FT_Face face)
+{
+  const gchar *text;
+
+  text = pango_language_get_sample_string (NULL);
+
+  if (!check_font_contain_text (face, text)) {
+    text = pango_language_get_sample_string (pango_language_from_string ("en_US"));
+  }
+
+  return text;
+}
+
+static gint *
+build_sizes_table (FT_Face face,
+		   gint *n_sizes,
+		   gint *alpha_size)
+{
+  gint *sizes = NULL;
+  gint i;
+
+  /* work out what sizes to render */
+  if (FT_IS_SCALABLE (face)) {
+    *n_sizes = 8;
+    sizes = g_new (gint, *n_sizes);
+    sizes[0] = 8;
+    sizes[1] = 10;
+    sizes[2] = 12;
+    sizes[3] = 18;
+    sizes[4] = 24;
+    sizes[5] = 36;
+    sizes[6] = 48;
+    sizes[7] = 72;
+    *alpha_size = 24;
+  } else {
+    /* use fixed sizes */
+    *n_sizes = face->num_fixed_sizes;
+    sizes = g_new (gint, *n_sizes);
+    *alpha_size = 0;
+
+    for (i = 0; i < face->num_fixed_sizes; i++) {
+      sizes[i] = face->available_sizes[i].height;
+
+      /* work out which font size to render */
+      if (face->available_sizes[i].height <= 24)
+        *alpha_size = face->available_sizes[i].height;
+    }
+  }
+
+  return sizes;
+}
+
+static void
+sushi_font_widget_size_request (GtkWidget *drawing_area,
+                                gint *width,
+                                gint *height)
+{
+  SushiFontWidgetPrivate *priv = SUSHI_FONT_WIDGET (drawing_area)->priv;
+  gint i, pixmap_width, pixmap_height;
+  const gchar *text;
+  gchar *font_name;
+  cairo_text_extents_t extents;
+  cairo_font_face_t *font;
+  gint *sizes = NULL, n_sizes, alpha_size;
+  cairo_t *cr;
+  FT_Face face = priv->face;
+  GtkStyleContext *context;
+  GtkStateFlags state;
+  GtkBorder padding;
+
+  if (face == NULL) {
+    if (width != NULL)
+      *width = 1;
+    if (height != NULL)
+      *height = 1;
+
+    return;
+  }
+
+  cr = gdk_cairo_create (gtk_widget_get_window (drawing_area));
+  context = gtk_widget_get_style_context (drawing_area);
+  state = gtk_style_context_get_state (context);
+  gtk_style_context_get_padding (context, state, &padding);
+
+  text = get_sample_string (face);
+  sizes = build_sizes_table (face, &n_sizes, &alpha_size);
+
+  /* calculate size of pixmap to use */
+  pixmap_width = padding.left + padding.right;
+  pixmap_height = 0;
+
+  font = cairo_ft_font_face_create_for_ft_face (face, 0);
+  cairo_set_font_face (cr, font);
+  cairo_set_font_size (cr, alpha_size + 6);
+  cairo_font_face_destroy (font);
+
+  font_name =  g_strconcat (face->family_name, " ",
+                            face->style_name, NULL);
+
+  cairo_text_extents (cr, font_name, &extents);
+  pixmap_height += extents.height + extents.y_advance + padding.top + padding.bottom;
+  pixmap_width = MAX (pixmap_width, extents.width + padding.left + padding.right);
+
+  g_free (font_name);
+
+  pixmap_height += SECTION_SPACING / 2;
+
+  cairo_set_font_size (cr, alpha_size);
+  cairo_text_extents (cr, lowercase_text, &extents);
+  pixmap_height += extents.height + extents.y_advance + padding.top + padding.bottom;
+  pixmap_width = MAX (pixmap_width, extents.width + padding.left + padding.right);
+
+  cairo_text_extents (cr, uppercase_text, &extents);
+  pixmap_height += extents.height + extents.y_advance + padding.top + padding.bottom;
+  pixmap_width = MAX (pixmap_width, extents.width + padding.left + padding.right);
+
+  cairo_text_extents (cr, punctuation_text, &extents);
+  pixmap_height += extents.height + extents.y_advance + padding.top + padding.bottom;
+  pixmap_width = MAX (pixmap_width, extents.width + padding.left + padding.right);
+
+  pixmap_height += SECTION_SPACING;
+
+  for (i = 0; i < n_sizes; i++) {
+    cairo_set_font_size (cr, sizes[i]);
+    cairo_text_extents (cr, text, &extents);
+    pixmap_height += extents.height + extents.y_advance + padding.top + padding.bottom;
+    pixmap_width = MAX (pixmap_width, extents.width + padding.left + padding.right);
+  }
+
+  pixmap_height += padding.bottom + SECTION_SPACING;
+
+  if (width != NULL)
+    *width = pixmap_width;
+
+  if (height != NULL)
+    *height = pixmap_height;
+
+  cairo_destroy (cr);
+  g_free (sizes);
+}
+
+static void
+sushi_font_widget_get_preferred_width (GtkWidget *drawing_area,
+                                       gint *minimum_width,
+                                       gint *natural_width)
+{
+  gint width;
+
+  sushi_font_widget_size_request (drawing_area, &width, NULL);
+
+  *minimum_width = *natural_width = width;
+}
+
+static void
+sushi_font_widget_get_preferred_height (GtkWidget *drawing_area,
+                                        gint *minimum_height,
+                                        gint *natural_height)
+{
+  gint height;
+
+  sushi_font_widget_size_request (drawing_area, NULL, &height);
+
+  *minimum_height = *natural_height = height;
+}
+
+static gboolean
+sushi_font_widget_draw (GtkWidget *drawing_area,
+                        cairo_t *cr)
+{
+  SushiFontWidgetPrivate *priv = SUSHI_FONT_WIDGET (drawing_area)->priv;
+  gint *sizes = NULL, n_sizes, alpha_size, pos_y = 0, i;
+  const gchar *text;
+  cairo_font_face_t *font;
+  FT_Face face = priv->face;
+  gboolean res;
+  GtkStyleContext *context;
+  GdkRGBA color;
+  GtkBorder padding;
+  GtkStateFlags state;
+  gint max_x = 0;
+  gchar *font_name;
+
+  if (face == NULL)
+    goto end;
+
+  context = gtk_widget_get_style_context (drawing_area);
+  state = gtk_style_context_get_state (context);
+  gtk_style_context_get_color (context, state, &color);
+  gtk_style_context_get_padding (context, state, &padding);
+
+  gdk_cairo_set_source_rgba (cr, &color);
+
+  sizes = build_sizes_table (face, &n_sizes, &alpha_size);
+
+  font = cairo_ft_font_face_create_for_ft_face (face, 0);
+  cairo_set_font_face (cr, font);
+  cairo_font_face_destroy (font);
+
+  font_name =  g_strconcat (face->family_name, " ",
+                            face->style_name, NULL);
+
+  /* draw text */
+  cairo_set_font_size (cr, alpha_size + 6);
+  draw_string (cr, padding, font_name, &pos_y);
+
+  pos_y += SECTION_SPACING / 2;
+
+  cairo_set_font_size (cr, alpha_size);
+  draw_string (cr, padding, lowercase_text, &pos_y);
+  draw_string (cr, padding, uppercase_text, &pos_y);
+  draw_string (cr, padding, punctuation_text, &pos_y);
+
+  pos_y += SECTION_SPACING;
+
+  text = get_sample_string (face);
+  for (i = 0; i < n_sizes; i++) {
+    cairo_set_font_size (cr, sizes[i]);
+    draw_string (cr, padding, text, &pos_y);
+  }
+
+ end:
+  g_free (sizes);
+
+  return FALSE;
+}
+
+static void
+font_face_async_ready_cb (GObject *object,
+                          GAsyncResult *res,
+                          gpointer user_data)
+{
+  SushiFontWidget *self = user_data;
+  FT_Face font_face;
+  gchar *contents = NULL;
+  GError *error = NULL;
+
+  self->priv->face = sushi_new_ft_face_from_uri_finish (res,
+                                                        &self->priv->face_contents,
+                                                        &error);
+
+  if (error != NULL) {
+    /* FIXME: need to signal the error */
+    g_print ("Can't load the font face: %s\n", error->message);
+    return;
+  }
+
+  gtk_widget_queue_resize (GTK_WIDGET (self));
+  g_signal_emit (self, signals[LOADED], 0);
+}
+
+static void
+load_font_face (SushiFontWidget *self)
+{
+  sushi_new_ft_face_from_uri_async (self->priv->uri,
+                                    font_face_async_ready_cb,
+                                    self);
+}
+
+static void
+sushi_font_widget_set_uri (SushiFontWidget *self,
+                           const gchar *uri)
+{
+  g_free (self->priv->uri);
+  self->priv->uri = g_strdup (uri);
+
+  load_font_face (self);
+}
+
+static void
+sushi_font_widget_init (SushiFontWidget *self)
+{
+  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, SUSHI_TYPE_FONT_WIDGET,
+                                            SushiFontWidgetPrivate);
+
+  self->priv->face = NULL;
+}
+
+static void
+sushi_font_widget_get_property (GObject *object,
+                                guint       prop_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+  SushiFontWidget *self = SUSHI_FONT_WIDGET (object);
+
+  switch (prop_id) {
+  case PROP_URI:
+    g_value_set_string (value, self->priv->uri);
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    break;
+  }
+}
+
+static void
+sushi_font_widget_set_property (GObject *object,
+                               guint       prop_id,
+                               const GValue *value,
+                               GParamSpec *pspec)
+{
+  SushiFontWidget *self = SUSHI_FONT_WIDGET (object);
+
+  switch (prop_id) {
+  case PROP_URI:
+    sushi_font_widget_set_uri (self, g_value_get_string (value));
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    break;
+  }
+}
+
+static void
+sushi_font_widget_finalize (GObject *object)
+{
+  SushiFontWidget *self = SUSHI_FONT_WIDGET (object);
+
+  g_free (self->priv->uri);
+
+  G_OBJECT_CLASS (sushi_font_widget_parent_class)->finalize (object);
+}
+
+static void
+sushi_font_widget_class_init (SushiFontWidgetClass *klass)
+{
+  GObjectClass *oclass = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *wclass = GTK_WIDGET_CLASS (klass);
+
+  oclass->finalize = sushi_font_widget_finalize;
+  oclass->set_property = sushi_font_widget_set_property;
+  oclass->get_property = sushi_font_widget_get_property;
+
+  wclass->draw = sushi_font_widget_draw;
+  wclass->get_preferred_width = sushi_font_widget_get_preferred_width;
+  wclass->get_preferred_height = sushi_font_widget_get_preferred_height;
+
+  properties[PROP_URI] =
+    g_param_spec_string ("uri",
+                         "Uri", "Uri",
+                         NULL, G_PARAM_READWRITE);
+
+  signals[LOADED] =
+    g_signal_new ("loaded",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_FIRST,
+                  0, NULL, NULL,
+                  g_cclosure_marshal_VOID__VOID,
+                  G_TYPE_NONE, 0);
+
+  g_object_class_install_properties (oclass, NUM_PROPERTIES, properties);
+  g_type_class_add_private (klass, sizeof (SushiFontWidgetPrivate));
+}
+
+SushiFontWidget *
+sushi_font_widget_new (const gchar *uri)
+{
+  return g_object_new (SUSHI_TYPE_FONT_WIDGET,
+                       "uri", uri,
+                       NULL);
+}
diff --git a/src/libsushi/sushi-font-widget.h b/src/libsushi/sushi-font-widget.h
new file mode 100644
index 0000000..080ed23
--- /dev/null
+++ b/src/libsushi/sushi-font-widget.h
@@ -0,0 +1,38 @@
+#ifndef __SUSHI_FONT_WIDGET_H__
+#define __SUSHI_FONT_WIDGET_H__
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define SUSHI_TYPE_FONT_WIDGET            (sushi_font_widget_get_type ())
+#define SUSHI_FONT_WIDGET(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SUSHI_TYPE_FONT_WIDGET, SushiFontWidget))
+#define SUSHI_IS_FONT_WIDGET(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SUSHI_TYPE_FONT_WIDGET))
+#define SUSHI_FONT_WIDGET_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  SUSHI_TYPE_FONT_WIDGET, SushiFontWidgetClass))
+#define SUSHI_IS_FONT_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  SUSHI_TYPE_FONT_WIDGET))
+#define SUSHI_FONT_WIDGET_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  SUSHI_TYPE_FONT_WIDGET, SushiFontWidgetClass))
+
+typedef struct _SushiFontWidget          SushiFontWidget;
+typedef struct _SushiFontWidgetPrivate   SushiFontWidgetPrivate;
+typedef struct _SushiFontWidgetClass     SushiFontWidgetClass;
+
+struct _SushiFontWidget
+{
+  GtkDrawingArea parent_instance;
+
+  SushiFontWidgetPrivate *priv;
+};
+
+struct _SushiFontWidgetClass
+{
+  GtkDrawingAreaClass parent_class;
+};
+
+GType    sushi_font_widget_get_type     (void) G_GNUC_CONST;
+
+SushiFontWidget *sushi_font_widget_new (const gchar *uri);
+
+G_END_DECLS
+
+#endif /* __SUSHI_FONT_WIDGET_H__ */



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