Pango passes wrong glyph indices to custom renderer

Summary

We encountered a production rendering issue where a custom Pango renderer displayed incorrect glyphs. Instead of rendering the correct characters, the PangoGlyph indices passed to our draw_glyphs function were misaligned with the actual font mapping. For example, a lowercase ‘c’ (ASCII 99) resulted in a glyph index of 102 (ASCII ‘f’). This behavior was inconsistent across different font weights (Regular, Bold, Italic) and custom font files. The root cause was a confusion between Unicode codepoints and Pango Glyph IDs, likely exacerbated by custom font loading that bypassed Pango’s internal glyph generation.

Root Cause

The issue stems from a fundamental misunderstanding of the data types returned by Pango in the PangoGlyphInfo structure.

  • PangoGlyph is not a Unicode Codepoint: The PangoGlyphInfo.glyph field is a PangoGlyph (typedef’d to guint32). It is not a Unicode value or a raw FreeType glyph index. It is a Pango-internal identifier that encodes the font, the specific glyph, and potentially shaping attributes.
  • Direct Passing to FreeType: The custom renderer was likely attempting to cast this PangoGlyph value directly to a FreeType FT_UInt (glyph index) or mapping it directly to a char_code.
  • Invalid Font Map Usage: The custom font map implementation may not have correctly registered the glyphs with Pango’s internal engine, causing Pango to generate valid but semantically incorrect identifiers that did not map to the expected glyphs in the TrueType files.

Why This Happens in Real Systems

This scenario is common when developers bypass high-level abstraction APIs to implement “optimized” or custom rendering backends.

  • API Misinterpretation: Documentation for PangoGlyphString often highlights the glyphs array, leading developers to assume the glyph field is a direct handle for rendering (like an FT_Glyph_Index), ignoring the requirement for pango_font_get_glyph_extents.
  • Custom Font Map Complexity: When loading custom .ttf files outside the standard PangoFT2FontMap, developers often fail to implement the full PangoFontMap and PangoFont interface correctly. If the get_glyph or find_font methods are not strictly compliant, Pango falls back to internal logic that generates mathematically valid but contextually wrong glyph IDs.

Real-World Impact

  • Visual Artifacts: The rendered text is garbled (e.g., “code” appears as “dpoe” or random symbols), rendering the UI unusable for text-heavy applications.
  • Instability: Relying on undefined behavior (casting internal structs) leads to crashes if Pango internals change or if the font encoding varies (e.g., Unicode vs. Symbol maps).
  • Maintenance Nightmare: Debugging these issues requires deep knowledge of both Pango internals and FreeType. The “offset” behavior observed suggests that the internal mapping table is shifting based on font style, indicating a corruption in the font loader’s logic.

Example or Code

The user’s code likely resembles this incorrect implementation, which treats the Pango internal ID as a renderable handle:

void draw_glyphs(PangoFont* font, PangoGlyphString* glyphs, int x, int y) {
    for (int i = 0; i num_glyphs; i++) {
        PangoGlyphInfo *gi = &glyphs->glyphs[i];

        // INCORRECT: Casting PangoGlyph directly to a FreeType index or char code
        // This produces the wrong characters and offsets.
        unsigned int wrong_index = (unsigned int)gi->glyph;

        printf("glyph: %u\n", wrong_index); 

        // draw_glyph expects a Unicode char or FT_Index, but gets Pango internal ID
        draw_glyph(font, wrong_index, x, y); 

        x += gi->geometry.width;
    }
}

How Senior Engineers Fix It

A senior engineer addresses this by strictly adhering to the Pango rendering pipeline, ensuring that the abstraction layer (Pango) is fully respected.

  • Use pango_cairo_show_glyph_string: Unless absolutely necessary for performance, use the Cairo backend. It handles the mapping from PangoGlyph to cairo_glyph_t automatically.
  • Query pango_font_get_glyph_extents: Never assume the glyph ID. If you must render manually, you must query the font for the actual metrics and glyph handles required by your rendering engine.
  • Validate Custom Font Map: Ensure the custom PangoFontMap implementation returns valid PangoFont objects that implement the get_glyph_map interface correctly.
  • Standard Debugging Workflow:
    1. Check if PangoGlyph is PANGO_GLYPH_EMPTY or PANGO_GLYPH_UNKNOWN.
    2. Ensure the PangoGlyphString was generated by a layout using the specific custom font map.
    3. If rendering to FreeType directly, use pango_font_get_glyph_extents to get the extents and not for the index, then use pango_fc_font_get_raw_glyph_index (if using FontConfig) to get the actual FT index.

Why Juniors Miss It

  • “It’s Just a Number”: Junior developers often see the glyph field as a simple integer index, failing to understand that it is a context-aware internal token.
  • Looking at the Wrong API: They search for “Pango to FreeType” and find low-level examples that mix APIs, assuming that casting a struct member is standard practice.
  • Visual Confirmation Bias: Seeing that ‘c’ maps to ‘f’ (ASCII difference of 2) suggests an offset error rather than a fundamental mapping failure. They look for a +2 or -2 bug in their loop math, missing that the entire domain of the data is wrong.