Hi,
Fontkit resolves TrueType composite (glyf) components by applying the 2×2 part of the affine transform in TTFGlyph._getContours. The coefficients used there do not match the normative formulas in the OpenType specification.
Normative definition (authoritative source)
According to OpenType glyf — Composite glyph description (section on WE_HAVE_A_TWO_BY_TWO), the four F2DOT14 values are read in order as xscale, scale01, scale10, and yscale, and the spec defines the transformed coordinates as:
x′ = xscale × x + scale10 × y
y′ = scale01 × x + yscale × y
(Offset (dx, dy) is applied after matrix multiplication in the usual way; see the same chapter for ordering with SCALED_COMPONENT_OFFSET / UNSCALED_COMPONENT_OFFSET.)
So in code that stores the four parsed values under names scaleX, scale01, scale10, scaleY, the correct mapping is:
x′ = scaleX × x + scale10 × y + dx
y′ = scale01 × x + scaleY × y + dy
Current Fontkit behaviour
In TTFGlyph._getContours (Fontkit 2.0.x), the implementation uses scale01 in the formula for x′ and scale10 in the formula for y′, i.e. the cross-terms are swapped relative to the specification above:
let x = point.x * scaleX + point.y * scale01 + dx;
let y = point.y * scaleY + point.x * scale10 + dy;
Those two lines contradict the specification’s pairing of (scale10 with y) in x′ and (scale01 with x) in y′.
Impact
Glyphs whose components use a genuine 2×2 (e.g. rotation plus translation) render incorrectly — outlines and bounding boxes diverge from other conforming implementations.
Concrete repro: MartianGrotesk-VFVF.ttf — glyph plus is a composite of two minus components with a
WE_HAVE_A_TWO_BY_TWO matrix plus offset. Expected outline matches flattened coordinates from FontTools / other spec-aligned stacks; Fontkit produces a visibly wrong second bar / wrong bbox.
Image 1: SVG / Illustrator / others
Image 2: Fontkit
Related fix elsewhere (cross-check)
The same class of mistake was corrected in opentype.js 1.3.5 (“correct 2x2 glyf transformation (fix #432) via PR #652”), aligning behaviour with the same glyf composite wording.
Suggested fix
Match the formulas from the glyf composite section cited above:
let x = point.x * scaleX + point.y * scale10 + component.dx;
let y = point.y * scaleY + point.x * scale01 + component.dy;
(Equivalently: swap how scale01 and scale10 are applied to match the MS spec.)
Thanks for maintaining Fontkit.
Hi,
Fontkit resolves TrueType composite (glyf) components by applying the 2×2 part of the affine transform in TTFGlyph._getContours. The coefficients used there do not match the normative formulas in the OpenType specification.
Normative definition (authoritative source)
According to OpenType glyf — Composite glyph description (section on WE_HAVE_A_TWO_BY_TWO), the four F2DOT14 values are read in order as xscale, scale01, scale10, and yscale, and the spec defines the transformed coordinates as:
x′ = xscale × x + scale10 × y
y′ = scale01 × x + yscale × y
(Offset (dx, dy) is applied after matrix multiplication in the usual way; see the same chapter for ordering with SCALED_COMPONENT_OFFSET / UNSCALED_COMPONENT_OFFSET.)
So in code that stores the four parsed values under names scaleX, scale01, scale10, scaleY, the correct mapping is:
x′ = scaleX × x + scale10 × y + dx
y′ = scale01 × x + scaleY × y + dy
Current Fontkit behaviour
In TTFGlyph._getContours (Fontkit 2.0.x), the implementation uses scale01 in the formula for x′ and scale10 in the formula for y′, i.e. the cross-terms are swapped relative to the specification above:
let x = point.x * scaleX + point.y * scale01 + dx;
let y = point.y * scaleY + point.x * scale10 + dy;
Those two lines contradict the specification’s pairing of (scale10 with y) in x′ and (scale01 with x) in y′.
Impact
Glyphs whose components use a genuine 2×2 (e.g. rotation plus translation) render incorrectly — outlines and bounding boxes diverge from other conforming implementations.
Concrete repro: MartianGrotesk-VFVF.ttf — glyph plus is a composite of two minus components with a
WE_HAVE_A_TWO_BY_TWO matrix plus offset. Expected outline matches flattened coordinates from FontTools / other spec-aligned stacks; Fontkit produces a visibly wrong second bar / wrong bbox.
Image 1: SVG / Illustrator / others
Image 2: Fontkit
Related fix elsewhere (cross-check)
The same class of mistake was corrected in opentype.js 1.3.5 (“correct 2x2 glyf transformation (fix #432) via PR #652”), aligning behaviour with the same glyf composite wording.
Suggested fix
Match the formulas from the glyf composite section cited above:
let x = point.x * scaleX + point.y * scale10 + component.dx;
let y = point.y * scaleY + point.x * scale01 + component.dy;
(Equivalently: swap how scale01 and scale10 are applied to match the MS spec.)
Thanks for maintaining Fontkit.