Skip to content

Fix composite glyph transform double-scaling - #2014

Open
soufianekhiat wants to merge 1 commit into
nothings:masterfrom
soufianekhiat:fix/truetype-composite-scale
Open

soufianekhiat wants to merge 1 commit into
nothings:masterfrom
soufianekhiat:fix/truetype-composite-scale

Conversation

@soufianekhiat

@soufianekhiat soufianekhiat commented Oct 14, 2026 •

Copy link
Copy Markdown
stb_truetype_composite_openmoji_colr0 stb_truetype_composite_emoji

Problem

In stbtt__GetGlyphShapeTT(), composite glyph components are transformed with:

v->x = m * (mtx[0]*x + mtx[2]*y + mtx[4]);
v->y = n * (mtx[1]*x + mtx[3]*y + mtx[5]);

where m = sqrt(mtx[0]^2 + mtx[1]^2) and n = sqrt(mtx[2]^2 + mtx[3]^2).

The 2x2 matrix already contains the component scale, so multiplying by m/n again squares it. Any component using WE_HAVE_A_SCALE, WE_HAVE_AN_X_AND_Y_SCALE or WE_HAVE_A_TWO_BY_TWO ends up at the wrong size and position. Unscaled components (m = n = 1) are unaffected, which is why this mostly goes unnoticed with regular text fonts.

It is very visible with emoji/COLR fonts that reuse scaled sub-glyphs (e.g. OpenMoji, Noto Color Emoji, Microsoft Fluent Emoji): parts of the glyph render too big or too small, or are shifted.

Fix

Per the TrueType glyf spec, m/n should only be used to rescale the offset (mtx[4], mtx[5]), and only when SCALED_COMPONENT_OFFSET (bit 11, 0x0800) is set and UNSCALED_COMPONENT_OFFSET (bit 12, 0x1000) is not. The default (neither flag set) is unscaled offsets, which matches Microsoft's convention and modern font tools.

The transform becomes v' = M * v + t:

if ((flags & (1<<11)) && !(flags & (1<<12))) { mtx[4] *= m; mtx[5] *= n; }
v->x = mtx[0]*x + mtx[2]*y + mtx[4];
v->y = mtx[1]*x + mtx[3]*y + mtx[5];

Component scale factors (m, n) were applied on top of the full 2x2 matrix,
squaring the scale of scaled/transformed composite components. Per the
TrueType spec they are only meant to rescale the offsets when
SCALED_COMPONENT_OFFSET (bit 11) is set and UNSCALED_COMPONENT_OFFSET
(bit 12) is not. Fixes wrongly sized sub-components in e.g. COLR emoji
fonts (OpenMoji, Noto Color Emoji, Fluent Emoji).
@soufianekhiat soufianekhiat changed the title stb_truetype: fix composite glyph transform double-scaling Fix composite glyph transform double-scaling Oct 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant