From 30c0c152ca19567c8cba77dcb4f36275d6def0d3 Mon Sep 17 00:00:00 2001 From: Sudip Shil Date: Fri, 8 May 2026 11:13:39 +0530 Subject: [PATCH] Fix PDF rendering of unknown glyphs Resolves: RHEL-145621 --- pango-use-cairo-user-font-el10.patch | 1054 ++++++++++++++++++++++++++ pango.spec | 7 +- 2 files changed, 1060 insertions(+), 1 deletion(-) create mode 100644 pango-use-cairo-user-font-el10.patch diff --git a/pango-use-cairo-user-font-el10.patch b/pango-use-cairo-user-font-el10.patch new file mode 100644 index 0000000..2122adc --- /dev/null +++ b/pango-use-cairo-user-font-el10.patch @@ -0,0 +1,1054 @@ +From d2ac39235633e43ccbb109717a9d7411a0ac4efd Mon Sep 17 00:00:00 2001 +From: Behdad Esfahbod +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 +-#include +- + #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 +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 + diff --git a/pango.spec b/pango.spec index 8f67d11..22cf682 100644 --- a/pango.spec +++ b/pango.spec @@ -9,12 +9,13 @@ Name: pango Version: 1.54.0 -Release: 3%{?dist} +Release: 4%{?dist} Summary: System for layout and rendering of internationalized text License: LGPL-2.0-or-later URL: https://pango.gnome.org/ Source0: https://download.gnome.org/sources/%{name}/1.54/%{name}-%{version}.tar.xz +Patch: pango-use-cairo-user-font-el10.patch BuildRequires: pkgconfig(cairo) >= %{cairo_version} BuildRequires: pkgconfig(cairo-gobject) >= %{cairo_version} @@ -138,6 +139,10 @@ fi %changelog +* Fri May 8 2026 Sudip Shil - 1.54.0-4 +- Fix PDF rendering of unknown glyphs +- Resolves: RHEL-145621 + * Tue Oct 29 2024 Troy Dawson - 1.54.0-3 - Bump release for October 2024 mass rebuild: Resolves: RHEL-64018