pango/pango-use-cairo-user-font-el10.patch
Sudip Shil 30c0c152ca Fix PDF rendering of unknown glyphs
Resolves: RHEL-145621
2026-05-08 12:41:23 +05:30

1055 lines
35 KiB
Diff

From d2ac39235633e43ccbb109717a9d7411a0ac4efd Mon Sep 17 00:00:00 2001
From: Behdad Esfahbod <behdad@behdad.org>
Date: Thu, 19 Mar 2026 14:29:46 -0600
Subject: [PATCH 1/2] pangocairo: use a user font for unknown glyph hexboxes
Unknown glyph hexboxes were drawn procedurally, so cairo PDF output
ended up exposing the ASCII hex digits to text extractors instead of
treating each fallback glyph as a single glyph.
Fix this by rendering hexboxes through a cairo user font and by
splitting cairo_show_text_glyphs() emission at cluster boundaries when
switching between normal glyphs and unknown glyphs. This keeps normal
text on the native scaled font while letting the PDF backend see each
hexbox as one glyph.
---
pango/pangocairo-font.c | 479 ++++++++++++++++++++++++++++++++++++-
pango/pangocairo-private.h | 7 +
pango/pangocairo-render.c | 323 ++++++++++++++++++++-----
3 files changed, 752 insertions(+), 57 deletions(-)
diff --git a/pango/pangocairo-font.c b/pango/pangocairo-font.c
index 30665d1b..9bef0f94 100644
--- a/pango/pangocairo-font.c
+++ b/pango/pangocairo-font.c
@@ -22,8 +22,6 @@
#include "config.h"
#include <math.h>
-#include <string.h>
-
#include "pangocairo.h"
#include "pangocairo-private.h"
#include "pango-font-private.h"
@@ -38,6 +36,14 @@
typedef PangoCairoFontIface PangoCairoFontInterface;
G_DEFINE_INTERFACE (PangoCairoFont, pango_cairo_font, PANGO_TYPE_FONT)
+static PangoCairoFontHexBoxInfo *
+_pango_cairo_font_private_get_hex_box_info (PangoCairoFontPrivate *cf_priv);
+static void
+_pango_cairo_font_private_get_glyph_extents_missing (PangoCairoFontPrivate *cf_priv,
+ PangoGlyph glyph,
+ PangoRectangle *ink_rect,
+ PangoRectangle *logical_rect);
+
static void
pango_cairo_font_default_init (PangoCairoFontIface *iface)
{
@@ -60,6 +66,379 @@ _pango_cairo_font_private_scaled_font_data_destroy (PangoCairoFontPrivateScaledF
}
}
+typedef struct
+{
+ PangoCairoFont *cfont;
+} PangoCairoHexBoxFontUserData;
+
+static const cairo_user_data_key_t pango_cairo_hex_box_font_private_key = {0};
+
+static void
+pango_cairo_hex_box_font_user_data_destroy (void *data)
+{
+ PangoCairoHexBoxFontUserData *user_data = data;
+
+ g_clear_object (&user_data->cfont);
+ g_free (user_data);
+}
+
+static PangoCairoFontPrivate *
+pango_cairo_hex_box_get_font_private (cairo_scaled_font_t *scaled_font)
+{
+ cairo_font_face_t *font_face = cairo_scaled_font_get_font_face (scaled_font);
+ PangoCairoHexBoxFontUserData *user_data;
+
+ user_data = cairo_font_face_get_user_data (font_face,
+ &pango_cairo_hex_box_font_private_key);
+ if (!user_data)
+ return NULL;
+
+ return PANGO_CAIRO_FONT_PRIVATE (user_data->cfont);
+}
+
+static unsigned long
+pango_cairo_hex_box_encode_glyph (PangoCairoFontPrivate *cf_priv,
+ PangoGlyph glyph)
+{
+ gpointer value;
+
+ if (!(glyph & PANGO_GLYPH_UNKNOWN_FLAG))
+ return glyph;
+
+ if (!cf_priv->hex_box_glyphs)
+ {
+ cf_priv->hex_box_glyph_base = 1;
+ cf_priv->hex_box_glyphs = g_hash_table_new (g_direct_hash, g_direct_equal);
+ cf_priv->hex_box_pango_glyphs = g_array_new (FALSE, FALSE, sizeof (PangoGlyph));
+ }
+
+ value = g_hash_table_lookup (cf_priv->hex_box_glyphs, GUINT_TO_POINTER (glyph));
+ if (value)
+ return GPOINTER_TO_UINT (value);
+
+ if (cf_priv->hex_box_glyph_base + cf_priv->hex_box_pango_glyphs->len > 0xFFFF)
+ return glyph;
+
+ value = GUINT_TO_POINTER (cf_priv->hex_box_glyph_base + cf_priv->hex_box_pango_glyphs->len);
+ g_hash_table_insert (cf_priv->hex_box_glyphs,
+ GUINT_TO_POINTER (glyph),
+ value);
+ g_array_append_val (cf_priv->hex_box_pango_glyphs, glyph);
+
+ return GPOINTER_TO_UINT (value);
+}
+
+static PangoGlyph
+pango_cairo_hex_box_decode_glyph (PangoCairoFontPrivate *cf_priv,
+ unsigned long glyph)
+{
+ if (cf_priv->hex_box_pango_glyphs &&
+ glyph >= cf_priv->hex_box_glyph_base &&
+ glyph < cf_priv->hex_box_glyph_base + cf_priv->hex_box_pango_glyphs->len)
+ return g_array_index (cf_priv->hex_box_pango_glyphs,
+ PangoGlyph,
+ glyph - cf_priv->hex_box_glyph_base);
+
+ return glyph;
+}
+
+static void
+pango_cairo_hex_box_draw_frame (cairo_t *cr,
+ double x,
+ double y,
+ double width,
+ double height,
+ double line_width,
+ gboolean invalid)
+{
+ cairo_rectangle (cr, x, y, width, height);
+
+ if (invalid)
+ {
+ cairo_new_sub_path (cr);
+ cairo_move_to (cr, x, y);
+ cairo_rel_line_to (cr, width, height);
+
+ cairo_new_sub_path (cr);
+ cairo_move_to (cr, x + width, y);
+ cairo_rel_line_to (cr, -width, height);
+
+ cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT);
+ }
+
+ cairo_set_line_width (cr, line_width);
+ cairo_set_line_join (cr, CAIRO_LINE_JOIN_MITER);
+ cairo_set_miter_limit (cr, 2.);
+ cairo_stroke (cr);
+}
+
+static void
+pango_cairo_hex_box_draw_box_glyph (cairo_t *cr,
+ double width,
+ gboolean invalid)
+{
+ pango_cairo_hex_box_draw_frame (cr,
+ 1.5,
+ 1.5 - PANGO_UNKNOWN_GLYPH_HEIGHT,
+ width - 3.0,
+ PANGO_UNKNOWN_GLYPH_HEIGHT - 3.0,
+ 1.0,
+ invalid);
+}
+
+static void
+pango_cairo_hex_box_render_missing_glyph (PangoCairoFontPrivate *cf_priv,
+ PangoGlyph glyph,
+ cairo_t *cr)
+{
+ char buf[7];
+ char hexbox_string[2] = { 0, 0 };
+ PangoCairoFontHexBoxInfo *hbi;
+ PangoRectangle logical_rect;
+ const char *name;
+ const char *p;
+ gunichar ch;
+ gboolean invalid_input;
+ double width, lsb, x0, y0;
+ int row, col;
+ int rows, cols;
+
+ ch = glyph & ~PANGO_GLYPH_UNKNOWN_FLAG;
+ invalid_input = G_UNLIKELY (glyph == PANGO_GLYPH_INVALID_INPUT || ch > 0x10FFFF);
+
+ _pango_cairo_font_private_get_glyph_extents_missing (cf_priv, glyph, NULL, &logical_rect);
+ width = (double) logical_rect.width / PANGO_SCALE;
+
+ hbi = _pango_cairo_font_private_get_hex_box_info (cf_priv);
+ if (!hbi || !_pango_cairo_font_install ((PangoFont *) hbi->font, cr))
+ {
+ pango_cairo_hex_box_draw_box_glyph (cr, width, invalid_input);
+ return;
+ }
+
+ if (G_UNLIKELY (invalid_input))
+ {
+ rows = hbi->rows;
+ cols = 1;
+ }
+ else if (ch == 0x2423 || g_unichar_type (ch) == G_UNICODE_SPACE_SEPARATOR)
+ {
+ double x = 0.5 * width;
+ double y = hbi->box_descent - 0.5 * hbi->box_height;
+
+ cairo_new_sub_path (cr);
+ cairo_arc (cr, x, y, 1.5 * hbi->line_width, 0, 2 * G_PI);
+ cairo_close_path (cr);
+ cairo_fill (cr);
+ return;
+ }
+ else if (ch == '\t')
+ {
+ double y = hbi->box_descent - 0.5 * hbi->box_height;
+ double offset = 0.2 * width;
+ double x = offset;
+ double al = width - 2 * offset;
+ double tl = MIN (hbi->digit_width, 0.75 * al);
+ double tw2 = 2.5 * hbi->line_width;
+ double lw2 = 0.5 * hbi->line_width;
+
+ cairo_move_to (cr, x - lw2, y - tw2);
+ cairo_line_to (cr, x + lw2, y - tw2);
+ cairo_line_to (cr, x + lw2, y - lw2);
+ cairo_line_to (cr, x + al - tl, y - lw2);
+ cairo_line_to (cr, x + al - tl, y - tw2);
+ cairo_line_to (cr, x + al, y);
+ cairo_line_to (cr, x + al - tl, y + tw2);
+ cairo_line_to (cr, x + al - tl, y + lw2);
+ cairo_line_to (cr, x + lw2, y + lw2);
+ cairo_line_to (cr, x + lw2, y + tw2);
+ cairo_line_to (cr, x - lw2, y + tw2);
+ cairo_close_path (cr);
+ cairo_fill (cr);
+ return;
+ }
+ else if (ch == '\n' || ch == 0x2028 || ch == 0x2029)
+ {
+ double offset = 0.2 * width;
+ double al = width - 2 * offset;
+ double tl = MIN (hbi->digit_width, 0.75 * al);
+ double ah = al - 0.5 * tl;
+ double tw2 = 2.5 * hbi->line_width;
+ double x = offset;
+ double y = - (hbi->box_height - al) / 2;
+ double lw2 = 0.5 * hbi->line_width;
+
+ cairo_move_to (cr, x, y);
+ cairo_line_to (cr, x + tl, y - tw2);
+ cairo_line_to (cr, x + tl, y - lw2);
+ cairo_line_to (cr, x + al - lw2, y - lw2);
+ cairo_line_to (cr, x + al - lw2, y - ah);
+ cairo_line_to (cr, x + al + lw2, y - ah);
+ cairo_line_to (cr, x + al + lw2, y + lw2);
+ cairo_line_to (cr, x + tl, y + lw2);
+ cairo_line_to (cr, x + tl, y + tw2);
+ cairo_close_path (cr);
+ cairo_fill (cr);
+ return;
+ }
+ else if ((name = pango_get_ignorable_size (ch, &rows, &cols)))
+ {
+ /* Nothing else to do, we render default-ignorables with their nick. */
+ }
+ else
+ {
+ rows = hbi->rows;
+ cols = (ch > 0xffff ? 6 : 4) / rows;
+ g_snprintf (buf, sizeof (buf), (ch > 0xffff) ? "%06X" : "%04X", ch);
+ name = buf;
+ }
+
+ width = 3 * hbi->pad_x + cols * (hbi->digit_width + hbi->pad_x);
+ lsb = ((double) logical_rect.width / PANGO_SCALE - width) * .5;
+ lsb = floor (lsb / hbi->pad_x) * hbi->pad_x;
+
+ pango_cairo_hex_box_draw_frame (cr,
+ lsb + 1.5 * hbi->pad_x,
+ hbi->box_descent - hbi->box_height + hbi->pad_y * .5,
+ width - hbi->pad_x,
+ hbi->box_height - hbi->pad_y,
+ hbi->line_width,
+ invalid_input);
+
+ if (invalid_input)
+ return;
+
+ x0 = lsb + hbi->pad_x * 3;
+ y0 = hbi->box_descent - hbi->pad_y * 2 - ((hbi->rows - rows) * hbi->digit_height / 2);
+
+ for (row = 0, p = name; row < rows; row++)
+ {
+ double y = y0 - (rows - 1 - row) * (hbi->digit_height + hbi->pad_y);
+
+ for (col = 0; col < cols; col++, p++)
+ {
+ double x = x0 + col * (hbi->digit_width + hbi->pad_x);
+
+ cairo_move_to (cr, x, y);
+ hexbox_string[0] = p[0];
+ cairo_text_path (cr, hexbox_string);
+ cairo_fill (cr);
+ }
+ }
+}
+
+static void
+pango_cairo_hex_box_font_space_advance (cairo_t *cr,
+ double *x_advance,
+ double *y_advance)
+{
+ cairo_matrix_t matrix;
+
+ cairo_get_matrix (cr, &matrix);
+ if (cairo_matrix_invert (&matrix) == CAIRO_STATUS_SUCCESS)
+ cairo_matrix_transform_distance (&matrix, x_advance, y_advance);
+}
+
+static cairo_status_t
+pango_cairo_hex_box_render_glyph (cairo_scaled_font_t *scaled_font,
+ unsigned long glyph,
+ cairo_t *cr,
+ cairo_text_extents_t *extents)
+{
+ PangoCairoFontPrivate *cf_priv = pango_cairo_hex_box_get_font_private (scaled_font);
+ cairo_scaled_font_t *native_scaled_font;
+ PangoGlyph pango_glyph;
+ PangoRectangle logical_rect;
+ double x_advance = 0;
+ double y_advance = 0;
+
+ if (!cf_priv)
+ return CAIRO_STATUS_USER_FONT_ERROR;
+
+ pango_glyph = pango_cairo_hex_box_decode_glyph (cf_priv, glyph);
+
+ if (pango_glyph & PANGO_GLYPH_UNKNOWN_FLAG)
+ {
+ _pango_cairo_font_private_get_glyph_extents_missing (cf_priv,
+ pango_glyph,
+ NULL,
+ &logical_rect);
+ x_advance = (double) logical_rect.width / PANGO_SCALE;
+ }
+ else
+ {
+ cairo_glyph_t cairo_glyph = { pango_glyph, 0, 0 };
+
+ native_scaled_font = _pango_cairo_font_private_get_scaled_font (cf_priv);
+ if (!native_scaled_font || cairo_scaled_font_status (native_scaled_font) != CAIRO_STATUS_SUCCESS)
+ return CAIRO_STATUS_USER_FONT_ERROR;
+
+ cairo_scaled_font_glyph_extents (native_scaled_font, &cairo_glyph, 1, extents);
+ x_advance = extents->x_advance;
+ y_advance = extents->y_advance;
+ }
+
+ pango_cairo_hex_box_font_space_advance (cr, &x_advance, &y_advance);
+ extents->x_bearing = 0;
+ extents->y_bearing = 0;
+ extents->width = 0;
+ extents->height = 0;
+ extents->x_advance = x_advance;
+ extents->y_advance = y_advance;
+
+ cairo_save (cr);
+ cairo_identity_matrix (cr);
+ {
+ if (pango_glyph & PANGO_GLYPH_UNKNOWN_FLAG)
+ {
+ pango_cairo_hex_box_render_missing_glyph (cf_priv, pango_glyph, cr);
+ }
+ else
+ {
+ cairo_glyph_t cairo_glyph = { pango_glyph, 0, 0 };
+
+ cairo_set_scaled_font (cr, native_scaled_font);
+ cairo_glyph_path (cr, &cairo_glyph, 1);
+ cairo_fill (cr);
+ }
+ }
+ cairo_restore (cr);
+
+ return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+pango_cairo_hex_box_unicode_to_glyph (cairo_scaled_font_t *scaled_font,
+ unsigned long unicode,
+ unsigned long *glyph_index)
+{
+ PangoCairoFontPrivate *cf_priv = pango_cairo_hex_box_get_font_private (scaled_font);
+ hb_font_t *hb_font;
+ hb_codepoint_t glyph;
+
+ if (!cf_priv)
+ return CAIRO_STATUS_USER_FONT_ERROR;
+
+ hb_font = pango_font_get_hb_font (PANGO_FONT (cf_priv->cfont));
+ if (hb_font_get_nominal_glyph (hb_font, unicode, &glyph))
+ {
+ *glyph_index = glyph;
+ return CAIRO_STATUS_SUCCESS;
+ }
+
+ if (unicode == 0x20)
+ {
+ *glyph_index = pango_cairo_hex_box_encode_glyph (cf_priv,
+ PANGO_GET_UNKNOWN_GLYPH (0x20));
+ return CAIRO_STATUS_SUCCESS;
+ }
+
+ *glyph_index = pango_cairo_hex_box_encode_glyph (cf_priv,
+ unicode > 0x10FFFF ? PANGO_GLYPH_INVALID_INPUT
+ : PANGO_GET_UNKNOWN_GLYPH (unicode));
+ return CAIRO_STATUS_SUCCESS;
+}
+
cairo_scaled_font_t *
_pango_cairo_font_private_get_scaled_font (PangoCairoFontPrivate *cf_priv)
{
@@ -135,6 +514,85 @@ done:
return cf_priv->scaled_font;
}
+cairo_scaled_font_t *
+_pango_cairo_font_get_hex_box_scaled_font (PangoCairoFont *cfont)
+{
+ PangoCairoFontPrivate *cf_priv = PANGO_CAIRO_FONT_PRIVATE (cfont);
+ cairo_scaled_font_t *native_scaled_font;
+ cairo_font_face_t *font_face;
+ cairo_font_options_t *font_options;
+ cairo_matrix_t ctm, font_matrix;
+ PangoCairoHexBoxFontUserData *user_data;
+
+ if (G_UNLIKELY (!cf_priv))
+ return NULL;
+
+ if (cf_priv->hex_box_scaled_font)
+ return cf_priv->hex_box_scaled_font;
+
+ native_scaled_font = _pango_cairo_font_private_get_scaled_font (cf_priv);
+ if (G_UNLIKELY (!native_scaled_font || cairo_scaled_font_status (native_scaled_font) != CAIRO_STATUS_SUCCESS))
+ return NULL;
+
+ font_face = cairo_user_font_face_create ();
+ if (G_UNLIKELY (cairo_font_face_status (font_face) != CAIRO_STATUS_SUCCESS))
+ {
+ cairo_font_face_destroy (font_face);
+ return NULL;
+ }
+
+ cairo_user_font_face_set_render_glyph_func (font_face, pango_cairo_hex_box_render_glyph);
+ cairo_user_font_face_set_unicode_to_glyph_func (font_face, pango_cairo_hex_box_unicode_to_glyph);
+ user_data = g_new0 (PangoCairoHexBoxFontUserData, 1);
+ user_data->cfont = g_object_ref (cfont);
+ if (G_UNLIKELY (cairo_font_face_set_user_data (font_face,
+ &pango_cairo_hex_box_font_private_key,
+ user_data,
+ pango_cairo_hex_box_font_user_data_destroy) != CAIRO_STATUS_SUCCESS))
+ {
+ pango_cairo_hex_box_font_user_data_destroy (user_data);
+ cairo_font_face_destroy (font_face);
+ return NULL;
+ }
+
+ cairo_scaled_font_get_ctm (native_scaled_font, &ctm);
+ cairo_scaled_font_get_font_matrix (native_scaled_font, &font_matrix);
+ font_options = cairo_font_options_create ();
+ cairo_scaled_font_get_font_options (native_scaled_font, font_options);
+
+ cf_priv->hex_box_scaled_font = cairo_scaled_font_create (font_face,
+ &font_matrix,
+ &ctm,
+ font_options);
+
+ cairo_font_options_destroy (font_options);
+ cairo_font_face_destroy (font_face);
+
+ if (G_UNLIKELY (!cf_priv->hex_box_scaled_font ||
+ cairo_scaled_font_status (cf_priv->hex_box_scaled_font) != CAIRO_STATUS_SUCCESS))
+ {
+ cairo_scaled_font_t *scaled_font = cf_priv->hex_box_scaled_font;
+
+ cf_priv->hex_box_scaled_font = NULL;
+ if (scaled_font)
+ cairo_scaled_font_destroy (scaled_font);
+ }
+
+ return cf_priv->hex_box_scaled_font;
+}
+
+unsigned long
+_pango_cairo_font_encode_hex_box_glyph (PangoCairoFont *cfont,
+ PangoGlyph glyph)
+{
+ PangoCairoFontPrivate *cf_priv = PANGO_CAIRO_FONT_PRIVATE (cfont);
+
+ if (G_UNLIKELY (!cf_priv))
+ return glyph;
+
+ return pango_cairo_hex_box_encode_glyph (cf_priv, glyph);
+}
+
/**
* pango_cairo_font_get_scaled_font:
* @font: (nullable): a `PangoFont` from a `PangoCairoFontMap`
@@ -596,7 +1054,11 @@ _pango_cairo_font_private_initialize (PangoCairoFontPrivate *cf_priv,
cf_priv->is_hinted = cairo_font_options_get_hint_metrics (font_options) != CAIRO_HINT_METRICS_OFF;
cf_priv->scaled_font = NULL;
+ cf_priv->hex_box_scaled_font = NULL;
cf_priv->hbi = NULL;
+ cf_priv->hex_box_glyphs = NULL;
+ cf_priv->hex_box_pango_glyphs = NULL;
+ cf_priv->hex_box_glyph_base = 0;
cf_priv->glyph_extents_cache = NULL;
cf_priv->metrics_by_lang = NULL;
}
@@ -617,9 +1079,22 @@ _pango_cairo_font_private_finalize (PangoCairoFontPrivate *cf_priv)
cairo_scaled_font_destroy (cf_priv->scaled_font);
cf_priv->scaled_font = NULL;
+ if (cf_priv->hex_box_scaled_font)
+ cairo_scaled_font_destroy (cf_priv->hex_box_scaled_font);
+ cf_priv->hex_box_scaled_font = NULL;
+
_pango_cairo_font_hex_box_info_destroy (cf_priv->hbi);
cf_priv->hbi = NULL;
+ if (cf_priv->hex_box_glyphs)
+ g_hash_table_destroy (cf_priv->hex_box_glyphs);
+ cf_priv->hex_box_glyphs = NULL;
+
+ if (cf_priv->hex_box_pango_glyphs)
+ g_array_free (cf_priv->hex_box_pango_glyphs, TRUE);
+ cf_priv->hex_box_pango_glyphs = NULL;
+ cf_priv->hex_box_glyph_base = 0;
+
if (cf_priv->glyph_extents_cache)
g_free (cf_priv->glyph_extents_cache);
cf_priv->glyph_extents_cache = NULL;
diff --git a/pango/pangocairo-private.h b/pango/pangocairo-private.h
index 704ae497..272fab12 100644
--- a/pango/pangocairo-private.h
+++ b/pango/pangocairo-private.h
@@ -79,7 +79,11 @@ struct _PangoCairoFontPrivate
PangoCairoFontPrivateScaledFontData *data;
cairo_scaled_font_t *scaled_font;
+ cairo_scaled_font_t *hex_box_scaled_font;
PangoCairoFontHexBoxInfo *hbi;
+ GHashTable *hex_box_glyphs;
+ GArray *hex_box_pango_glyphs;
+ unsigned int hex_box_glyph_base;
gboolean is_hinted;
PangoGravity gravity;
@@ -103,6 +107,9 @@ struct _PangoCairoFontIface
gboolean _pango_cairo_font_install (PangoFont *font,
cairo_t *cr);
+cairo_scaled_font_t *_pango_cairo_font_get_hex_box_scaled_font (PangoCairoFont *cfont);
+unsigned long _pango_cairo_font_encode_hex_box_glyph (PangoCairoFont *cfont,
+ PangoGlyph glyph);
PangoFontMetrics * _pango_cairo_font_get_metrics (PangoFont *font,
PangoLanguage *language);
PangoCairoFontHexBoxInfo *_pango_cairo_font_get_hex_box_info (PangoCairoFont *cfont);
diff --git a/pango/pangocairo-render.c b/pango/pangocairo-render.c
index 902f5257..c3990209 100644
--- a/pango/pangocairo-render.c
+++ b/pango/pangocairo-render.c
@@ -395,6 +395,92 @@ done:
cairo_restore (crenderer->cr);
}
+static gboolean
+pango_cairo_glyph_string_has_unknown_glyphs (PangoGlyphString *glyphs)
+{
+ int i;
+
+ for (i = 0; i < glyphs->num_glyphs; i++)
+ {
+ if (glyphs->glyphs[i].glyph & PANGO_GLYPH_UNKNOWN_FLAG)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+pango_cairo_glyph_range_has_unknown_glyphs (PangoGlyphString *glyphs,
+ int glyph_start,
+ int glyph_end)
+{
+ int i;
+ int dir = glyph_start < glyph_end ? 1 : -1;
+
+ for (i = glyph_start; i != glyph_end; i += dir)
+ {
+ if (glyphs->glyphs[i].glyph & PANGO_GLYPH_UNKNOWN_FLAG)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static int
+pango_cairo_renderer_count_glyphs (PangoGlyphString *glyphs,
+ int glyph_start,
+ int glyph_end,
+ gboolean use_hex_box_scaled_font)
+{
+ int count = 0;
+ int i;
+ int dir = glyph_start < glyph_end ? 1 : -1;
+
+ for (i = glyph_start; i != glyph_end; i += dir)
+ {
+ PangoGlyph glyph = glyphs->glyphs[i].glyph;
+
+ if (glyph == PANGO_GLYPH_EMPTY)
+ continue;
+
+ if (!use_hex_box_scaled_font && (glyph & PANGO_GLYPH_UNKNOWN_FLAG))
+ continue;
+
+ count++;
+ }
+
+ return count;
+}
+
+static void
+pango_cairo_renderer_get_glyph_range (int glyph_start,
+ int glyph_end,
+ int *range_start,
+ int *range_end)
+{
+ if (glyph_start < glyph_end)
+ {
+ *range_start = glyph_start;
+ *range_end = glyph_end;
+ }
+ else
+ {
+ *range_start = glyph_end + 1;
+ *range_end = glyph_start + 1;
+ }
+}
+
+static gboolean
+pango_cairo_renderer_has_hex_box_scaled_font (PangoCairoRenderer *crenderer,
+ PangoFont *font,
+ PangoGlyphString *glyphs)
+{
+ return !crenderer->do_path &&
+ crenderer->has_show_text_glyphs &&
+ pango_cairo_glyph_string_has_unknown_glyphs (glyphs) &&
+ _pango_cairo_font_get_hex_box_scaled_font ((PangoCairoFont *) font) != NULL;
+}
+
#ifndef STACK_BUFFER_SIZE
#define STACK_BUFFER_SIZE (512 * sizeof (int))
#endif
@@ -402,16 +488,19 @@ done:
#define STACK_ARRAY_LENGTH(T) (STACK_BUFFER_SIZE / sizeof(T))
static void
-pango_cairo_renderer_show_text_glyphs (PangoRenderer *renderer,
- const char *text,
- int text_len,
- PangoGlyphString *glyphs,
- cairo_text_cluster_t *clusters,
- int num_clusters,
- gboolean backward,
- PangoFont *font,
- int x,
- int y)
+pango_cairo_renderer_show_text_glyphs_range (PangoRenderer *renderer,
+ const char *text,
+ int text_len,
+ PangoGlyphString *glyphs,
+ int glyph_start,
+ int glyph_end,
+ cairo_text_cluster_t *clusters,
+ int num_clusters,
+ gboolean backward,
+ PangoFont *font,
+ int x,
+ int y,
+ gboolean use_hex_box_scaled_font)
{
PangoCairoRenderer *crenderer = (PangoCairoRenderer *) (renderer);
@@ -426,9 +515,15 @@ pango_cairo_renderer_show_text_glyphs (PangoRenderer *renderer,
if (!crenderer->do_path)
set_color (crenderer, PANGO_RENDER_PART_FOREGROUND);
- if (!_pango_cairo_font_install (font, crenderer->cr))
+ for (i = 0; i < glyph_start; i++)
+ x_position += glyphs->glyphs[i].geometry.width;
+
+ if (use_hex_box_scaled_font)
+ cairo_set_scaled_font (crenderer->cr,
+ _pango_cairo_font_get_hex_box_scaled_font ((PangoCairoFont *) font));
+ else if (!_pango_cairo_font_install (font, crenderer->cr))
{
- for (i = 0; i < glyphs->num_glyphs; i++)
+ for (i = glyph_start; i < glyph_end; i++)
{
PangoGlyphInfo *gi = &glyphs->glyphs[i];
@@ -447,13 +542,13 @@ pango_cairo_renderer_show_text_glyphs (PangoRenderer *renderer,
goto done;
}
- if (glyphs->num_glyphs > (int) G_N_ELEMENTS (stack_glyphs))
- cairo_glyphs = g_new (cairo_glyph_t, glyphs->num_glyphs);
+ if (glyph_end - glyph_start > (int) G_N_ELEMENTS (stack_glyphs))
+ cairo_glyphs = g_new (cairo_glyph_t, glyph_end - glyph_start);
else
cairo_glyphs = stack_glyphs;
count = 0;
- for (i = 0; i < glyphs->num_glyphs; i++)
+ for (i = glyph_start; i < glyph_end; i++)
{
PangoGlyphInfo *gi = &glyphs->glyphs[i];
@@ -466,10 +561,18 @@ pango_cairo_renderer_show_text_glyphs (PangoRenderer *renderer,
if (gi->glyph & PANGO_GLYPH_UNKNOWN_FLAG)
{
- if (gi->glyph == (0x20 | PANGO_GLYPH_UNKNOWN_FLAG))
+ if (use_hex_box_scaled_font)
+ {
+ cairo_glyphs[count].index = _pango_cairo_font_encode_hex_box_glyph ((PangoCairoFont *) font,
+ gi->glyph);
+ cairo_glyphs[count].x = cx;
+ cairo_glyphs[count].y = cy;
+ count++;
+ }
+ else if (gi->glyph == (0x20 | PANGO_GLYPH_UNKNOWN_FLAG))
; /* no hex boxes for space, please */
else
- _pango_cairo_renderer_draw_unknown_glyph (crenderer, font, gi, cx, cy);
+ _pango_cairo_renderer_draw_unknown_glyph (crenderer, font, gi, cx, cy);
}
else
{
@@ -501,6 +604,69 @@ done:
cairo_restore (crenderer->cr);
}
+static void
+pango_cairo_renderer_show_text_glyphs (PangoRenderer *renderer,
+ const char *text,
+ int text_len,
+ PangoGlyphString *glyphs,
+ cairo_text_cluster_t *clusters,
+ int num_clusters,
+ gboolean backward,
+ PangoFont *font,
+ int x,
+ int y)
+{
+ pango_cairo_renderer_show_text_glyphs_range (renderer,
+ text,
+ text_len,
+ glyphs,
+ 0,
+ glyphs->num_glyphs,
+ clusters,
+ num_clusters,
+ backward,
+ font,
+ x,
+ y,
+ FALSE);
+}
+
+static void
+pango_cairo_renderer_show_text_glyphs_segment (PangoRenderer *renderer,
+ const char *text,
+ PangoGlyphString *glyphs,
+ int text_start,
+ int text_end,
+ int glyph_start,
+ int glyph_end,
+ cairo_text_cluster_t *clusters,
+ int num_clusters,
+ gboolean backward,
+ PangoFont *font,
+ int x,
+ int y,
+ gboolean use_hex_box_scaled_font)
+{
+ int range_start, range_end;
+
+ pango_cairo_renderer_get_glyph_range (glyph_start, glyph_end,
+ &range_start, &range_end);
+
+ pango_cairo_renderer_show_text_glyphs_range (renderer,
+ text + text_start,
+ text_end - text_start,
+ glyphs,
+ range_start,
+ range_end,
+ clusters,
+ num_clusters,
+ backward,
+ font,
+ x,
+ y,
+ use_hex_box_scaled_font);
+}
+
static void
pango_cairo_renderer_draw_glyphs (PangoRenderer *renderer,
PangoFont *font,
@@ -533,6 +699,7 @@ pango_cairo_renderer_draw_glyph_item (PangoRenderer *renderer,
PangoGlyphItemIter iter;
cairo_text_cluster_t *cairo_clusters;
cairo_text_cluster_t stack_clusters[STACK_ARRAY_LENGTH (cairo_text_cluster_t)];
+ gboolean use_hex_box_scaled_font;
int num_clusters;
if (!crenderer->has_show_text_glyphs || crenderer->do_path)
@@ -547,50 +714,96 @@ pango_cairo_renderer_draw_glyph_item (PangoRenderer *renderer,
return;
}
+ use_hex_box_scaled_font = pango_cairo_renderer_has_hex_box_scaled_font (crenderer,
+ font,
+ glyphs);
+
if (glyphs->num_glyphs > (int) G_N_ELEMENTS (stack_clusters))
cairo_clusters = g_new (cairo_text_cluster_t, glyphs->num_glyphs);
else
cairo_clusters = stack_clusters;
- num_clusters = 0;
- if (pango_glyph_item_iter_init_start (&iter, glyph_item, text))
- {
- do {
- int num_bytes, num_glyphs, i;
-
- num_bytes = iter.end_index - iter.start_index;
- num_glyphs = backward ? iter.start_glyph - iter.end_glyph : iter.end_glyph - iter.start_glyph;
-
- if (num_bytes < 1)
- g_warning ("pango_cairo_renderer_draw_glyph_item: bad cluster has num_bytes %d", num_bytes);
- if (num_glyphs < 1)
- g_warning ("pango_cairo_renderer_draw_glyph_item: bad cluster has num_glyphs %d", num_glyphs);
-
- /* Discount empty and unknown glyphs */
- for (i = MIN (iter.start_glyph, iter.end_glyph+1);
- i < MAX (iter.start_glyph+1, iter.end_glyph);
- i++)
- {
- PangoGlyphInfo *gi = &glyphs->glyphs[i];
-
- if (gi->glyph == PANGO_GLYPH_EMPTY ||
- gi->glyph & PANGO_GLYPH_UNKNOWN_FLAG)
- num_glyphs--;
- }
-
- cairo_clusters[num_clusters].num_bytes = num_bytes;
- cairo_clusters[num_clusters].num_glyphs = num_glyphs;
- num_clusters++;
- } while (pango_glyph_item_iter_next_cluster (&iter));
- }
+ {
+ gboolean have_segment = FALSE;
+ gboolean segment_use_hex_box = FALSE;
+ int segment_start_index = 0;
+ int segment_end_index = 0;
+ int segment_start_glyph = 0;
+ int segment_end_glyph = 0;
+
+ num_clusters = 0;
+ if (pango_glyph_item_iter_init_start (&iter, glyph_item, text))
+ {
+ do {
+ gboolean cluster_use_hex_box;
+ int num_bytes, num_glyphs;
+
+ num_bytes = iter.end_index - iter.start_index;
+ cluster_use_hex_box = use_hex_box_scaled_font &&
+ pango_cairo_glyph_range_has_unknown_glyphs (glyphs,
+ iter.start_glyph,
+ iter.end_glyph);
+ num_glyphs = pango_cairo_renderer_count_glyphs (glyphs,
+ iter.start_glyph,
+ iter.end_glyph,
+ cluster_use_hex_box);
+
+ if (num_bytes < 1)
+ g_warning ("pango_cairo_renderer_draw_glyph_item: bad cluster has num_bytes %d", num_bytes);
+ if (backward ? iter.start_glyph - iter.end_glyph < 1 : iter.end_glyph - iter.start_glyph < 1)
+ g_warning ("pango_cairo_renderer_draw_glyph_item: bad cluster has num_glyphs %d",
+ backward ? iter.start_glyph - iter.end_glyph : iter.end_glyph - iter.start_glyph);
+
+ if (have_segment && cluster_use_hex_box != segment_use_hex_box)
+ {
+ pango_cairo_renderer_show_text_glyphs_segment (renderer,
+ text,
+ glyphs,
+ segment_start_index,
+ segment_end_index,
+ segment_start_glyph,
+ segment_end_glyph,
+ cairo_clusters,
+ num_clusters,
+ backward,
+ font,
+ x, y,
+ segment_use_hex_box);
+ have_segment = FALSE;
+ num_clusters = 0;
+ }
- pango_cairo_renderer_show_text_glyphs (renderer,
- text + item->offset, item->length,
- glyphs,
- cairo_clusters, num_clusters,
- backward,
- font,
- x, y);
+ if (!have_segment)
+ {
+ segment_use_hex_box = cluster_use_hex_box;
+ segment_start_index = iter.start_index;
+ segment_start_glyph = iter.start_glyph;
+ have_segment = TRUE;
+ }
+
+ segment_end_index = iter.end_index;
+ segment_end_glyph = iter.end_glyph;
+ cairo_clusters[num_clusters].num_bytes = num_bytes;
+ cairo_clusters[num_clusters].num_glyphs = num_glyphs;
+ num_clusters++;
+ } while (pango_glyph_item_iter_next_cluster (&iter));
+ }
+
+ if (have_segment)
+ pango_cairo_renderer_show_text_glyphs_segment (renderer,
+ text,
+ glyphs,
+ segment_start_index,
+ segment_end_index,
+ segment_start_glyph,
+ segment_end_glyph,
+ cairo_clusters,
+ num_clusters,
+ backward,
+ font,
+ x, y,
+ segment_use_hex_box);
+ }
if (cairo_clusters != stack_clusters)
g_free (cairo_clusters);
--
2.53.0
From 56e5ae1a64206d121dd4e942fd01b9a5a1468eb4 Mon Sep 17 00:00:00 2001
From: Behdad Esfahbod <behdad@behdad.org>
Date: Fri, 20 Mar 2026 10:17:01 -0600
Subject: [PATCH 2/2] pangocairo: draw hex box frames with fills
This works around a bug in stroke width in cairo type3 font output.
https://gitlab.freedesktop.org/cairo/cairo/-/issues/934
---
pango/pangocairo-font.c | 47 +++++++++++++++++++++++++++++------------
1 file changed, 34 insertions(+), 13 deletions(-)
diff --git a/pango/pangocairo-font.c b/pango/pangocairo-font.c
index 9bef0f94..790da32a 100644
--- a/pango/pangocairo-font.c
+++ b/pango/pangocairo-font.c
@@ -151,25 +151,46 @@ pango_cairo_hex_box_draw_frame (cairo_t *cr,
double line_width,
gboolean invalid)
{
- cairo_rectangle (cr, x, y, width, height);
+ double half_line_width = 0.5 * line_width;
+
+ cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
+ cairo_rectangle (cr,
+ x - half_line_width,
+ y - half_line_width,
+ width + line_width,
+ height + line_width);
+ cairo_rectangle (cr,
+ x + half_line_width,
+ y + half_line_width,
+ MAX (width - line_width, 0),
+ MAX (height - line_width, 0));
if (invalid)
{
- cairo_new_sub_path (cr);
- cairo_move_to (cr, x, y);
- cairo_rel_line_to (cr, width, height);
-
- cairo_new_sub_path (cr);
- cairo_move_to (cr, x + width, y);
- cairo_rel_line_to (cr, -width, height);
+ double diagonal_length = hypot (width, height);
- cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT);
+ if (diagonal_length > 0)
+ {
+ double dx = half_line_width * height / diagonal_length;
+ double dy = half_line_width * width / diagonal_length;
+
+ cairo_new_sub_path (cr);
+ cairo_move_to (cr, x - dx, y + dy);
+ cairo_line_to (cr, x + dx, y - dy);
+ cairo_line_to (cr, x + width + dx, y + height - dy);
+ cairo_line_to (cr, x + width - dx, y + height + dy);
+ cairo_close_path (cr);
+
+ cairo_new_sub_path (cr);
+ cairo_move_to (cr, x + width + dx, y + dy);
+ cairo_line_to (cr, x + width - dx, y - dy);
+ cairo_line_to (cr, x - dx, y + height - dy);
+ cairo_line_to (cr, x + dx, y + height + dy);
+ cairo_close_path (cr);
+ }
}
- cairo_set_line_width (cr, line_width);
- cairo_set_line_join (cr, CAIRO_LINE_JOIN_MITER);
- cairo_set_miter_limit (cr, 2.);
- cairo_stroke (cr);
+ cairo_fill (cr);
}
static void
--
2.53.0