[sushi] font: add SushiFontLoader and SushiFontWidget objects
- From: Cosimo Cecchi <cosimoc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [sushi] font: add SushiFontLoader and SushiFontWidget objects
- Date: Wed, 11 May 2011 04:57:06 +0000 (UTC)
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]