Skip to content

Walk GPOS on the buffer cursor, keep COLRv1 paint transforms, add cmap formats 6 and 10 - #565

Merged
JimBobSquarePants merged 4 commits into
release/v3.1.xfrom
js/gpos-context-advance
Sep 17, 2026
Merged

JimBobSquarePants merged 4 commits into
release/v3.1.xfrom
js/gpos-context-advance

Conversation

@JimBobSquarePants

Copy link
Copy Markdown
Member

Prerequisites

  • I have written a descriptive pull-request title
  • I have verified that there are no overlapping pull-requests open
  • I have verified that I am following matches the existing coding patterns and practice as demonstrated in the repository. These follow strict Stylecop rules 👮.
  • I have provided test coverage for my change (where applicable)

Description

Fixes the faults behind SixLabors/ImageSharp.Drawing#418 that only show with the installed Segoe UI Emoji 1.70, and the cmap faults HarfBuzz's aots corpus found once it was wired in. HarfBuzz is the reference for every change.

GPOS walks the buffer cursor like GSUB. Positioning stepped one glyph after every lookup, so a matched contextual lookup was re-attempted on the glyphs it had consumed and the family sequence's members overlapped. GPOS now uses the same cursor GSUB uses (HarfBuzz apply_forward and apply_lookup): a lookup leaves the cursor after the records it consumed, the loop steps one record only when the cursor did not move, the GPOS ApplyLookupList ends with MoveTo(end), the three chained context formats call it instead of inlining nested application, and pair positioning moves the cursor to the second glyph, or past it when valueFormat2 is set.

cmap format 14 default UVS ranges are inclusive (startUnicodeValue through startUnicodeValue + additionalCount), so U+FE0F resolves for the last code point of each range.

COLRv1 radial and sweep paints keep their geometry in paint space with the whole PaintTransform, as FreeType and HarfBuzz hand it to the renderer. Flattening to device points reduced the transform to a similarity, which turned the toned thumbs-up's squashed shadow ellipses into circles that painted the finger dark. Paint.Transform is public. The image comparison tests are disabled until ImageSharp.Drawing 3.1.3 consumes the transform.

cmap formats 6 and 10 are read (HarfBuzz CmapSubtableTrimmed). Format 4 adds idDelta in the idRangeOffset branch and treats glyph 0 as missing. A glyph id past the font's glyph count no longer throws in the CFF tables: they return an empty charstring, the bounds finder returns an empty box when nothing was drawn, and hmtx and vmtx return zero past the glyph count. vmtx applies the last record past numberOfVMetrics, not the first.

Tests. The corpus tests now shape HarfBuzz's aots suite as well (274 cases). New HarfBuzz differential cases for Segoe UI Emoji 1.70, presentation selector tests, the font chain and 192pt thumbs-up rows on the browser page, a radial gradient transform test, and unit tests for cmap formats 4, 6 and 10 and both metrics tables. Fonts: 11065 tests pass on net8.0.

Positioning stepped one glyph after every lookup, so a matched
contextual lookup was re-attempted on the glyphs it had consumed.
With Segoe UI Emoji 1.70 the family sequence's chained context
applied its nested lookups twice and the members overlapped.

GPOS now walks the same buffer cursor GSUB uses: a lookup leaves the
cursor after the records it consumed and the loop steps one record
only when the cursor did not move. The GPOS ApplyLookupList ends with
MoveTo(end), the three chained context formats call it instead of
inlining nested application, and pair positioning moves the cursor to
the second glyph, or past it when valueFormat2 is set. MoveTo takes
the no-pass fast path outside a substitution pass.

Adds the installed Segoe UI Emoji 1.70 and its HarfBuzz differential
cases.
A default UVS range covers startUnicodeValue through
startUnicodeValue + additionalCount, so the last code point of each
range was never matched and U+FE0F fell through to the text
presentation for it.

Adds the presentation tests against HarfBuzz, the Segoe UI Emoji 1.70
font chain row and the 192pt toned thumbs-up row to the browser page.
Radial and sweep paints were flattened to device points, which
reduced a PaintTransform to a similarity. The squashed shadow
ellipses of the toned thumbs-up became large circles whose pad region
painted the finger opaque dark.

Paint.Transform is public and radial and sweep paints keep their
geometry in the paint's y-down space with the whole transform, as
FreeType and HarfBuzz hand it to the renderer. Linear paints stay
flattened. The image comparison tests are disabled until Drawing
3.1.3 consumes the transform.
The corpus tests now also shape HarfBuzz's aots suite, which found:

- cmap formats 6 and 10 were not read.
- Format 4 dropped idDelta in the idRangeOffset branch and reported
  glyph 0 as found.
- A glyph id past the font's glyph count threw in the CFF tables.
  They now return an empty charstring, the bounds finder returns an
  empty box when nothing was drawn, and hmtx and vmtx return zero past
  the glyph count. vmtx also applies the last record, not the first,
  past numberOfVMetrics.
@codecov

codecov Bot commented Sep 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.28571% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 81%. Comparing base (0d06baa) to head (1e44cf0).

Files with missing lines Patch % Lines
...c/SixLabors.Fonts/Rendering/PaintedGlyphMetrics.cs 57% 5 Missing and 1 partial ⚠️
src/SixLabors.Fonts/Tables/Cff/Cff2Table.cs 33% 1 Missing and 1 partial ⚠️
...bors.Fonts/Tables/General/CMap/Format10SubTable.cs 96% 1 Missing ⚠️
...abors.Fonts/Tables/General/CMap/Format6SubTable.cs 96% 1 Missing ⚠️
Additional details and impacted files
@@               Coverage Diff               @@
##           release/v3.1.x    #565    +/-   ##
===============================================
- Coverage              81%     81%    -1%     
===============================================
  Files                 406     408     +2     
  Lines               35747   35802    +55     
  Branches             5583    5586     +3     
===============================================
- Hits                29241   29185    -56     
- Misses               5341    5441   +100     
- Partials             1165    1176    +11     
Flag Coverage Δ
unittests 81% <94%> (-1%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant