summaryrefslogtreecommitdiff
path: root/pango2/pango-userface.c
diff options
context:
space:
mode:
Diffstat (limited to 'pango2/pango-userface.c')
-rw-r--r--pango2/pango-userface.c481
1 files changed, 481 insertions, 0 deletions
diff --git a/pango2/pango-userface.c b/pango2/pango-userface.c
new file mode 100644
index 00000000..d4cb60f5
--- /dev/null
+++ b/pango2/pango-userface.c
@@ -0,0 +1,481 @@
+/* Pango2
+ *
+ * Copyright (C) 2022 Matthias Clasen
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include "pango-font-face-private.h"
+#include "pango-userface-private.h"
+#include "pango-userfont-private.h"
+#include "pango-utils.h"
+#include "pango-item-private.h"
+#include "pango-impl-utils.h"
+
+#include <string.h>
+#include <hb-ot.h>
+
+/**
+ * Pango2UserFace:
+ *
+ * `Pango2UserFace` is a `Pango2FontFace` implementation that uses callbacks.
+ *
+ * It allows to draw the glyphs in a font using custom code. This can
+ * be used to implement fonts in non-standard formats, but can also be
+ * used by games and other application to draw "funky" fonts.
+ *
+ * To get a font instance at a specific size from a `Pango2UserFace`,
+ * use [ctor@Pango2.UserFont.new].
+ */
+
+/* {{{ Utilities */
+
+static void
+ensure_faceid (Pango2UserFace *self)
+{
+ Pango2FontFace *face = PANGO2_FONT_FACE (self);
+ char *psname;
+ char *p;
+
+ if (self->faceid)
+ return;
+
+ psname = g_strconcat (pango2_font_description_get_family (face->description), "_", face->name, NULL);
+
+ /* PostScript name should not contain problematic chars, but just in case,
+ * make sure we don't have any ' ', '=' or ',' that would give us parsing
+ * problems.
+ */
+ p = psname;
+ while ((p = strpbrk (p, " =,")) != NULL)
+ *p = '?';
+
+ self->faceid = g_strconcat ("user:", psname, NULL);
+
+ g_free (psname);
+}
+
+static const char *
+style_from_font_description (const Pango2FontDescription *desc)
+{
+ Pango2Style style = pango2_font_description_get_style (desc);
+ Pango2Weight weight = pango2_font_description_get_weight (desc);
+
+ switch (style)
+ {
+ case PANGO2_STYLE_ITALIC:
+ if (weight == PANGO2_WEIGHT_BOLD)
+ return "Bold Italic";
+ else
+ return "Italic";
+ break;
+ case PANGO2_STYLE_OBLIQUE:
+ if (weight == PANGO2_WEIGHT_BOLD)
+ return "Bold Oblique";
+ else
+ return "Oblique";
+ break;
+ case PANGO2_STYLE_NORMAL:
+ if (weight == PANGO2_WEIGHT_BOLD)
+ return "Bold";
+ else
+ return "Regular";
+ break;
+ default: ;
+ }
+
+ return NULL;
+}
+
+static gboolean
+default_shape_func (Pango2UserFace *face,
+ int size,
+ const char *text,
+ int length,
+ const Pango2Analysis *analysis,
+ Pango2GlyphString *glyphs,
+ Pango2ShapeFlags flags,
+ gpointer user_data)
+{
+ int n_chars;
+ const char *p;
+ int cluster = 0;
+ int i;
+ int last_cluster;
+ gboolean is_color;
+ hb_glyph_extents_t ext;
+ hb_position_t dummy;
+ hb_font_t *hb_font;
+ hb_font_extents_t font_extents;
+
+ n_chars = g_utf8_strlen (text, length);
+
+ pango2_glyph_string_set_size (glyphs, n_chars);
+
+ last_cluster = -1;
+
+ hb_font = pango2_font_get_hb_font (analysis->font);
+ hb_font_get_h_extents (hb_font, &font_extents);
+
+ p = text;
+ for (i = 0; i < n_chars; i++)
+ {
+ gunichar wc;
+ Pango2Glyph glyph;
+ Pango2Rectangle ink_rect;
+ Pango2Rectangle logical_rect;
+
+ wc = g_utf8_get_char (p);
+
+ if (g_unichar_type (wc) != G_UNICODE_NON_SPACING_MARK)
+ cluster = p - text;
+
+ if (pango2_is_zero_width (wc))
+ glyph = PANGO2_GLYPH_EMPTY;
+ else if (!face->glyph_func (face, wc, &glyph, face->user_data))
+ glyph = PANGO2_GET_UNKNOWN_GLYPH (wc);
+
+ face->glyph_info_func (face, size, glyph, &ext, &dummy, &dummy, &is_color, face->user_data);
+ pango2_font_get_glyph_extents (analysis->font, glyph, &ink_rect, &logical_rect);
+
+ glyphs->glyphs[i].glyph = glyph;
+
+ glyphs->glyphs[i].attr.is_cluster_start = cluster != last_cluster;
+ glyphs->glyphs[i].attr.is_color = is_color;
+
+ if (analysis->gravity == PANGO2_GRAVITY_EAST)
+ {
+ glyphs->glyphs[i].geometry.x_offset = font_extents.ascender;
+ glyphs->glyphs[i].geometry.y_offset = - logical_rect.y - (logical_rect.height - ink_rect.height) / 2;
+ glyphs->glyphs[i].geometry.width = logical_rect.width;
+ }
+ else if (analysis->gravity == PANGO2_GRAVITY_WEST)
+ {
+ glyphs->glyphs[i].geometry.x_offset = font_extents.descender;
+ glyphs->glyphs[i].geometry.y_offset = logical_rect.y + (logical_rect.height - ink_rect.height) / 2;
+ glyphs->glyphs[i].geometry.width = logical_rect.width;
+ }
+ else if (analysis->gravity == PANGO2_GRAVITY_SOUTH)
+ {
+ glyphs->glyphs[i].geometry.x_offset = 0;
+ glyphs->glyphs[i].geometry.y_offset = 0;
+ glyphs->glyphs[i].geometry.width = logical_rect.width;
+ }
+ else if (analysis->gravity == PANGO2_GRAVITY_NORTH)
+ {
+ glyphs->glyphs[i].geometry.x_offset = 0;
+ glyphs->glyphs[i].geometry.y_offset = 0;
+ glyphs->glyphs[i].geometry.width = - logical_rect.width;
+ }
+
+ glyphs->log_clusters[i] = cluster;
+ last_cluster = cluster;
+
+ p = g_utf8_next_char (p);
+ }
+
+ if (analysis->level & 1)
+ pango2_glyph_string_reverse_range (glyphs, 0, glyphs->num_glyphs);
+
+ return TRUE;
+}
+
+static gboolean
+default_render_func (Pango2UserFace *face,
+ int size,
+ hb_codepoint_t glyph,
+ gpointer user_data,
+ const char *backend_id,
+ gpointer backend_data)
+{
+ /* Draw nothing... not very exciting */
+ return TRUE;
+}
+
+/* }}} */
+/* {{{ Pango2FontFace implementation */
+
+struct _Pango2UserFaceClass
+{
+ Pango2FontFaceClass parent_class;
+};
+
+G_DEFINE_FINAL_TYPE (Pango2UserFace, pango2_user_face, PANGO2_TYPE_FONT_FACE)
+
+static void
+pango2_user_face_init (Pango2UserFace *self)
+{
+}
+
+static void
+pango2_user_face_finalize (GObject *object)
+{
+ Pango2UserFace *self = PANGO2_USER_FACE (object);
+
+ g_free (self->faceid);
+ if (self->destroy)
+ self->destroy (self->user_data);
+
+ G_OBJECT_CLASS (pango2_user_face_parent_class)->finalize (object);
+}
+
+static gboolean
+pango2_user_face_is_synthesized (Pango2FontFace *face)
+{
+ return TRUE;
+}
+
+static gboolean
+pango2_user_face_is_monospace (Pango2FontFace *face)
+{
+ return FALSE;
+}
+
+static gboolean
+pango2_user_face_is_variable (Pango2FontFace *face)
+{
+ return FALSE;
+}
+
+static gboolean
+pango2_user_face_has_char (Pango2FontFace *face,
+ gunichar wc)
+{
+ Pango2UserFace *self = PANGO2_USER_FACE (face);
+ hb_codepoint_t glyph;
+
+ return self->glyph_func (self, wc, &glyph, self->user_data);
+}
+
+static const char *
+pango2_user_face_get_faceid (Pango2FontFace *face)
+{
+ Pango2UserFace *self = PANGO2_USER_FACE (face);
+
+ ensure_faceid (self);
+
+ return self->faceid;
+}
+
+static Pango2Font *
+pango2_user_face_create_font (Pango2FontFace *face,
+ const Pango2FontDescription *desc,
+ float dpi,
+ const Pango2Matrix *ctm)
+{
+ Pango2UserFace *self = PANGO2_USER_FACE (face);
+
+ return PANGO2_FONT (pango2_user_font_new_for_description (self, desc, dpi, ctm));
+}
+
+static void
+pango2_user_face_class_init (Pango2UserFaceClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+ Pango2FontFaceClass *face_class = PANGO2_FONT_FACE_CLASS (class);
+
+ object_class->finalize = pango2_user_face_finalize;
+
+ face_class->is_synthesized = pango2_user_face_is_synthesized;
+ face_class->is_monospace = pango2_user_face_is_monospace;
+ face_class->is_variable = pango2_user_face_is_variable;
+ face_class->has_char = pango2_user_face_has_char;
+ face_class->get_faceid = pango2_user_face_get_faceid;
+ face_class->create_font = pango2_user_face_create_font;
+}
+
+/* }}} */
+ /* {{{ Public API */
+
+/**
+ * Pango2UserFaceGetFontInfoFunc:
+ * @face: the `Pango2UserFace`
+ * @size: the size of the font that is being created
+ * @extents: (out caller-allocates): return location for font extents
+ * user_data: user data that was passed to [ctor@Pango2.UserFace.new]
+ *
+ * Callback to obtain font extents for user fonts.
+ *
+ * Typically, the `ascender` and `descender` fields in the @extents
+ * struct should both the filled with non-negative values that add
+ * up to @size.
+ *
+ * Returns: `TRUE` on success
+ */
+
+/**
+ * Pango2UserFaceUnicodeToGlyphFunc:
+ * @face: the `Pango2UserFace`
+ * @unicode: the Unicode character
+ * @glyph: (out caller-allocates): return location for the glyph that
+ * @user_data: user data that was passed to [ctor@Pango2.UserFace.new]
+ *
+ * Callback to determine if a user font can render a character,
+ * and what glyph it will use.
+ *
+ * Returns: `TRUE` on success
+ */
+
+/**
+ * Pango2UserFaceGetGlyphInfoFunc:
+ * @face: the `Pango2UserFace`
+ * @size: the size of the font that is queried
+ * @glyph: the glyph that is being queried
+ * @extents: (out caller-allocates): return location for the glyphs ink rectangle
+ * @h_advance: (out caller-allocates): return location for the h advance
+ * @v_advance: (out caller-allocates): return location for the v advance
+ * @is_color_glyph: (out caller-allocates): return location for information about
+ * whether @glyph has color
+ * @user_data: user data that was passed to [ctor@Pango2.UserFace.new]
+ *
+ * Callback to obtain information about a glyph in a user font.
+ *
+ * The @extents, @h_advance and @v_advance arguments should be filled with
+ * values that are scaled according to @size. Note that @y_bearing will typically
+ * be positive, and @height negative.
+ *
+ * Returns: `TRUE` on success
+ */
+
+/**
+ * Pango2UserFaceTextToGlyphFunc:
+ * @face: the `Pango2UserFace`
+ * @size: the size of the font that is used
+ * @text: the text to shape
+ * @length: the length of @text
+ * @analysis: `Pango2Analysis` for @text
+ * @glyphs: (out caller-allocates): the `Pango2GlyphString` to populate
+ * @flags: `Pango2ShapeFlags` to use
+ * @user_data: user data that was pased to [ctor@Pango2.UserFace.new]
+ *
+ * Callback to shape a segment of text with a user font.
+ *
+ * This callback is optional when creating a user font. If it isn't
+ * provided, Pango2 will rely on the `Pango2UserFaceUnicodeToGlyphFunc`
+ * and the `Pango2UserFaceGetGlyphInfo` callback to translate Unicode
+ * characters to glyphs 1-1, and position the glyphs according to their
+ * advance widths.
+ *
+ * If this callback is provided, it replaces all of Pango2's own shaping.
+ * The function can implement ligatures, reordering, and other features
+ * that turn the text-to-glyph mapping into an m-n relationship. The
+ * function is responsible for filling not just the glyphs and their
+ * positions, but also cluster information and glyph attributes in
+ * [struct@Pango2.GlyphVisAttr].
+ *
+ * Returns: `TRUE` on success
+ */
+
+/**
+ * Pango2UserFaceRenderGlyphFunc:
+ * @face: the `Pango2UserFace`
+ * @size: the size of the font that is used
+ * @glyph: the glyph that is being queried
+ * @user_data: user data that was pased to [ctor@Pango2.UserFace.new]
+ * @backend_id: a string identifying the [class@Pango2.Renderer] in use
+ * @backend_data: backend-specific data
+ *
+ * Callback to render a glyph with a user font.
+ *
+ * This callback is optional when creating a user font. If it isn't
+ * provided, the font will not produce any visible output.
+ *
+ * The @backend_id identifies the [class@Pango2.Renderer] in use.
+ * Implementations should return `FALSE` for unsupported backends.
+ *
+ * The cairo backend uses the string "cairo" as @backend_id, and
+ * provides a `cairo_t` as @backend_data. The context is set up
+ * to render in `font space`, i.e. The transformation is set up
+ * to map the unit square to @size x @size. If supported, Pango2
+ * uses `cairo_user_font_face_set_render_color_glyph_func` to
+ * allow glyphs to be rendered with colors. For more information,
+ * see the cairo documentation about user fonts.
+ *
+ * Returns: `TRUE` on success
+ */
+
+/**
+ * pango2_user_face_new:
+ * @font_info_func: (scope notified): a `Pango2UserFaceGetFontInfoFunc`
+ * @glyph_func: (scope notified): a `Pango2UserFaceUnicodeToGlyphFunc`
+ * @glyph_info_func: (scope notified): a `Pango2UserFaceGetGlyphInfoFunc`
+ * @shape_func: (scope notified) (nullable): a `Pango2UserFaceTextToGlyphFunc`
+ * @render_func: (scope notified) (nullable): a `Pango2UserFaceRenderGlyphFunc`
+ * @user_data: user data that will be assed to the callbacks
+ * @destroy: destroy notify for @user_data
+ * @name: name for the face
+ * @description: `Pango2FontDescription` for the font
+ *
+ * Creates a new user font face.
+ *
+ * A user font face does not rely on font data from a font file,
+ * but instead uses callbacks to determine glyph extents, positions
+ * and rendering.
+ *
+ * If @shape_func is `NULL`, Pango2 will rely on @glyph_func and
+ * @glyph_info_func to find and position a glyph for each character.
+ *
+ * If @render_func is `NULL`, the font will not produce any visible
+ * glyphs.
+ *
+ * Returns: (transfer full): a newly created `Pango2UserFace`
+ */
+Pango2UserFace *
+pango2_user_face_new (Pango2UserFaceGetFontInfoFunc font_info_func,
+ Pango2UserFaceUnicodeToGlyphFunc glyph_func,
+ Pango2UserFaceGetGlyphInfoFunc glyph_info_func,
+ Pango2UserFaceTextToGlyphFunc shape_func,
+ Pango2UserFaceRenderGlyphFunc render_func,
+ gpointer user_data,
+ GDestroyNotify destroy,
+ const char *name,
+ const Pango2FontDescription *description)
+{
+ Pango2UserFace *self;
+ Pango2FontFace *face;
+
+ g_return_val_if_fail (font_info_func != NULL, NULL);
+ g_return_val_if_fail (glyph_func != NULL, NULL);
+ g_return_val_if_fail (glyph_info_func != NULL, NULL);
+ g_return_val_if_fail (name != NULL, NULL);
+ g_return_val_if_fail (description != NULL, NULL);
+
+ self = g_object_new (PANGO2_TYPE_USER_FACE, NULL);
+
+ self->font_info_func = font_info_func;
+ self->glyph_func = glyph_func;
+ self->glyph_info_func = glyph_info_func;
+ self->shape_func = shape_func ? shape_func : default_shape_func;
+ self->render_func = render_func ? render_func : default_render_func;
+ self->user_data = user_data;
+ self->destroy = destroy;
+
+ if (!name)
+ name = style_from_font_description (description);
+
+ face = PANGO2_FONT_FACE (self);
+
+ face->name = g_strdup (name);
+ face->description = pango2_font_description_copy (description);
+
+ return self;
+}
+
+/* }}} */
+
+/* vim:set foldmethod=marker expandtab: */