Skip to content

TTFGlyph: incorrect WE_HAVE_A_TWO_BY_TWO composite transform in _getContours #369

@autowayfinding

Description

@autowayfinding

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

Image 2: Fontkit

Image

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions