Repository navigation
Conversation
src/render/colr.ts renders the paint graph of COLR version 1 base glyphs (BaseGlyphList, LayerList, ClipList) into premultiplied float RGBA: - all 32 paint formats, variable ones with their default values - linear (with the p2 rotation), two point conical radial and sweep gradients (biased angles), with pad, repeat and reflect - PaintGlyph masks through the text rasterizer, after the transform - all 28 composite modes (Porter-Duff, separable and HSL blends) - the text color for palette index 0xffff Layers carry the box they drew in, so compositing stays within it, and source over has its own loop. Glyphs render at 2x and are averaged down: layers sharing an edge would otherwise each be antialiased there and the ones below would show through (dark seams on Noto's emoji). COLR version 1 glyphs win over bitmaps and version 0 layers. The CPAL palette reader is shared with version 0. Tests: a Noto-COLRv1 subset (OFL) in the emoji tests, and a font built with fontTools (tests/fonts/colrv1-paints.py) with one glyph per paint feature. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJgRK5qpwJEbHPuFv6kgAQ
At 2x, the seams where layers share an edge (Noto's flags) still showed; at 4x they look like the bitmap version. To pay for it: - layers hold only their box instead of the whole canvas (at 4x a 64px emoji's canvas is about 1 MB per layer) - a glyph over a solid color, the most common paint, is the mask in that color without rendering and masking a filled layer - gradients write from the color table directly, without a closure and a subarray per pixel A new emoji glyph at 40-78px takes about 5.5 ms (10 ms before these, at 4x); output differs only by rounding (3 values of one pixel at alpha 8). COLRv1 targets regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJgRK5qpwJEbHPuFv6kgAQ
- damaged glyf data: non-increasing contour end points leave the glyph empty, composites can't contain themselves and have a command budget, the hinter likewise (cycles, at most 65536 points) - COLR v1 masks rasterize within the canvas only (the rasterizer splits segments at the bitmap edges instead of clamping their end points), the canvas stays within two ems of the em box and lowers its supersampling for very large glyphs - RangeErrors from reading damaged font data become "Font: damaged or truncated font data"; text over 2^28 pixels throws; text without ink (empty, spaces) renders a transparent pixel wide instead of throwing - graphemes are composed (NFC) or decomposed (NFD) where that makes a font have them, like HarfBuzz - sbix color bitmaps (PNG, dupe) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJgRK5qpwJEbHPuFv6kgAQ
- tests/fonts/sbix.py builds sbix.ttf (a PNG with an origin offset, and a 'dupe' of it); the bitmap and its placement are checked - empty text renders a pixel wide, combining sequences like the composed characters - damaged fonts (truncated, bytes changed, and the damaged Noto-COLRv1 subset that ran out of memory before) render or throw a font error within 3 seconds - changelog entries for sbix, normalization and the robustness fixes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJgRK5qpwJEbHPuFv6kgAQ
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.
This is step 3 of color emoji: COLR version 1 support, plus sbix. Testing with many fonts and every Unicode emoji turned up robustness fixes for damaged fonts, which are included here too.
COLR version 1 (
src/render/colr.ts)Also in this PR
duperecords. Placement follows the origin offset, and the image size is read from the PNG header.renderText(''), spaces only, or only invisible characters used to throw (already in 3.2.1). It now returns a transparent image 1 px wide.Robustness against damaged fonts
1,800 fuzzed fonts (truncated, or with random bytes changed): previously one ran out of memory; now every case either renders or throws an error starting with "Font:".
cause. The line height is at least 1 px, and text larger than 2²⁸ pixels throws.Testing
emoji-test.txt(Unicode 18, every qualification level) plus text samples.tests/fonts/sbix.pybuildssbix.ttf, a PNG with an origin offset plus a dupe.tests/regressions.jscovers the damaged-font cases. The damaged Noto subset from the fuzzing is checked in as a fixture; with the old parser it runs out of memory.Known limits
Hebrew and Arabic (right-to-left, Arabic shaping) and the positioning of combining marks (GPOS mark) are still not supported, as before.
🤖 Generated with Claude Code
https://claude.ai/code/session_01YJgRK5qpwJEbHPuFv6kgAQ