Draw a COLR version 1 font in colour (#76) - #299
Merged
Merged
Conversation
jakejackson1
force-pushed
the
feature/76-colrv1
branch
from
September 21, 2026 21:39
af8d8d3 to
d7daa5b
Compare
jakejackson1
added this pull request to stack #301
September 22, 2026 00:18
jakejackson1
force-pushed
the
feature/76-colrv1
branch
from
September 22, 2026 00:22
d7daa5b to
f410c68
Compare
jakejackson1
removed this pull request from stack #301
September 22, 2026 00:27
jakejackson1
added this pull request to stack #302
September 22, 2026 00:27
jakejackson1
force-pushed
the
feature/76-colrv1
branch
from
September 22, 2026 00:31
f410c68 to
ec5996f
Compare
jakejackson1
force-pushed
the
feature/76-colrv1
branch
from
September 22, 2026 00:40
ec5996f to
3d59154
Compare
Walk each colour glyph's paint graph as it is drawn: layers, glyph clips, solid fills, linear and radial gradients as axial and radial shadings, the transforms as cm, and composites through blend modes and soft masks. A sweep gradient is drawn in the colour of its middle stop, with a warning. Noto Color Emoji's vector build now draws in colour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
jakejackson1
force-pushed
the
feature/76-colrv1
branch
from
September 22, 2026 01:53
3d59154 to
d87670b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 4 of the colour emoji plan for #76. The substitution fix the paints showed up, spaces between emoji staying in the document font, was split out as #303, now merged; the snapshots here include it. Noto Color Emoji's vector build, the one Google Fonts serves, now draws in colour.
What changes
ColrV1Sourcewalks each colour glyph's paint graph as the glyph is drawn. How each paint maps:W n; with a solid child, the outline filled, as a v0 layer iscm/BM, the source as a group blended into an isolated group with the backdrop/TRfor the OUT modes; ATOP and XOR are sums of IN and OUT (table inCOMPOSITES)What isn't drawn as the font asks, logged as a warning:
Every read is checked to lie within COLR.
ColorLine: a gradient's stops sorted, taken onto 0 to 1, and spread for REPEAT and REFLECT.GlyphResourcesgainsblend(),shading(),group()andsoftMask().Type3FontWriterregisters them per subset and writes them with the subset's resource dictionary. That dictionary now carries/Shadingand more/XObjectand/ExtGStateentries:ColorFormats::sources()lists every format a font carries, inSOURCESorder. A COLRv1 glyph with no paint is therefore drawn from its v0 layers, and a font carrying two bitmap formats falls back from one to the other per glyph.COLRv1comes first inSOURCES.The palette, the palette-index-to-colour rule and the fill a layer is drawn with are shared by both COLR sources. They live in
ColorFontFile::palette()/colour()and aFillsInColourtrait. The v0 snapshot's content is unchanged.CACHE_FORMATgoes from 7 to 8. A COLRv1-only font cached before this was cached as not drawable, without its glyph map. TheSOURCESdocblock now says to raise it when a format is added.The fixture
TestEmoji-COLRv1.ttfis rebuilt so each emoji shows another kind of paint. It covers radial, linear, reflected-with-varying-alpha and repeated gradients, an affine transform, scale about a centre, rotate plus skew, translate plus scale, a clip box, PaintColrGlyph, MULTIPLY, SRC_IN, DEST_OUT, a sweep and a Var paint. The recipe is inbuild.py; only that font changes.Fixed after a review against the spec
A read of the COLR and CPAL specs, with checks against Noto-COLRv1, found five places where drawing strayed from the spec. They're fixed here:
nonNegative(),ColorLine::between()). Noto has none of these.ColorLine::premultiplied()adds stops to the colour shading wherever both colour and alpha change between two stops, and gives an alpha-0 stop its neighbour's colour on each side.The ClipList's format byte is now checked too. The
color-emoji-colrv1snapshot is unchanged, since the fixture has no gradient where both colour and alpha vary.Checked against a real font
color-emoji-colrv1snapshot.Kept as it is
GlyphResourcesside into a class of its own is worth doing before SVG (Phase 5) adds more; doing it here would move Draw a CBDT colour font in colour, as Type3 fonts (#76) #291's image code in a PR about paints.AddExtGState(). Each names a group that is only numbered once its subset's groups are written.writeResources(). They could be written straight after their subset, which would save memory, not time.Tests
ColrV1SourceTest: each paint kind through the fixture. Synthetic COLR tables cover radial gradients cut at radius 0 (padded and repeated), hard edges, unknown composite modes and PLUS, loops, unknown formats, reads past COLR, degenerate gradients, a scale to nothing, a transparent colour, and a VarColorLine.ColorLineTest, including premultiplied stops, their error bound and cap, stops at alpha 0, and hard edges.ColorFontTest: the shadings, groups and masks as written to the PDF.color-emoji-colrv1snapshot.🤖 Generated with Claude Code