Choose the font an emoji is drawn in, and lay out a font without GDEF (#76) - #290
Merged
Merged
Conversation
This was referenced Sep 21, 2026
jakejackson1
force-pushed
the
feature/76-emoji-prerequisites
branch
from
September 21, 2026 04:38
10c878f to
41e25b6
Compare
jakejackson1
changed the base branch from
gravitypdf
to
feature/76-unicode-namespace
September 21, 2026 04:38
jakejackson1
changed the base branch from
feature/76-unicode-namespace
to
gravitypdf
September 21, 2026 05:08
FontCache::jsonWrite() now forgets its in-memory copy of the entry. Before, AddFont() read back the stale metrics it had just replaced whenever it regenerated a font in the same process. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The emoji tables are generated the same way as Ucdn's, so reading a file of the database and wrapping a table's entries move into the GeneratedTable trait the generators share. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Mpdf\Unicode\Emoji finds ZWJ sequences, flags, keycaps, skin tones and tag sequences, and what presentation each asks for. Its property tables are generated from Unicode 18's emoji-data.txt by the new composer emoji:update. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Most emoji fonts carry GSUB and no GDEF, and TTFontFile refused them under useOTL. Such a font is now read with every glyph in class 0, and a mark filtering set in a font with no marks filters nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ps (#76) The font cache now holds colorFormats (COLRv1, COLRv0, SVG, CBDT, sbix) and tagChars, the Private Use code each plane-14 tag's glyph was read at, and CACHE_FORMAT goes to 5. tests/data/ttf/color holds a tiny fixture font per format, built by the build.py beside them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Presentation selectors are removed before shaping, as HarfBuzz hides them, so a font's ligature can form without one in the way. Tag characters are given the Private Use code their glyph was read at. Noto Emoji now forms its ZWJ, flag, keycap and subdivision-flag ligatures. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
jakejackson1
force-pushed
the
feature/76-emoji-prerequisites
branch
2 times, most recently
from
September 21, 2026 06:48
06e530d to
f2b6b99
Compare
…font where it asks for colour (#76) SubstituteCharsMB() moves a ZWJ sequence, flag, keycap, skin tone or tag sequence to a backup font whole, rather than leaving its joiners in the document font. An emoji that asks for colour is offered to a colour backup font first, and U+FE0E to the others first. The rules for choosing the font live in the new Mpdf\Fonts\FontSubstitution service. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
jakejackson1
force-pushed
the
feature/76-emoji-prerequisites
branch
from
September 21, 2026 06:53
f2b6b99 to
7b49a07
Compare
jakejackson1
added this pull request to stack #296
September 21, 2026 06:57
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 0 of the colour emoji plan for #76. It builds on #294 (
UcdnandBidimoved intoMpdf\Unicode) and is the base of a stack: Phase 1 (#291, Type3 + CBDT), Phase 2 (#292, sbix) and Phase 3 (#293, COLRv0) build on it.This PR chooses the font an emoji is drawn in and lets the shaper form it. Drawing the glyphs in colour comes in #291–#293.
Commits
Each commit passes
composer csand the test suite on its own.Read a font's metrics back as they were just rewritten.
FontCache::jsonWrite()now forgets its in-memory copy. Before this,AddFont()read back stale metrics whenever it regenerated a font in the same process.Share the Unicode table generators' file reading and line wrapping. These move into the
GeneratedTabletrait, which the emoji generator uses too.Find each emoji in a run of text by the grammar of UTS Keep a greyscale JPEG greyscale when its Exif orientation is corrected #51.
Mpdf\Unicode\Emojifinds ZWJ sequences, flags, keycaps, skin tones and tag sequences, and the presentation each asks for. Its tables come from Unicode 18'semoji-data.txtthrough the newcomposer emoji:update.Lay out a font without GDEF as HarfBuzz does. Most emoji fonts have GSUB and no GDEF, and
TTFontFilerefused them underuseOTL. Such a font is now read with every glyph in class 0, and a mark filtering set in a font with no marks filters nothing.ParserGoldenMasterTestjoins the sharedGoldenMasterTestCase, since no font is refused any more. The fixtures forangerthasandBlank-WideCmap-Syntheticare regenerated.Record the colour formats a font carries and the tag characters it maps. The font cache gains:
colorFormats: any ofCOLRv1,COLRv0,SVG,CBDTandsbix.tagChars: the Private Use code each plane-14 tag's glyph was read at.CACHE_FORMATgoes to 5.tests/data/ttf/color/holds one tiny fixture font per format, all built from one set of designs and GSUB by thebuild.pybeside them, which uses fontTools and Pillow. That also exercises all five CBLC index formats.Hand an emoji to the shaper as HarfBuzz would. Presentation selectors are removed before shaping, as HarfBuzz hides them, so Noto's keycap
1 FE0F 20E3forms its ligature. Tag characters are given their Private Use code. Noto Emoji now forms its ZWJ, flag, keycap and subdivision-flag ligatures.Keep an emoji whole on its way to a backup font, and prefer a colour font where it asks for colour.
SubstituteCharsMB()moves a whole emoji sequence to a backup font. Before, its joiners were left behind in the document font. The rules for choosing the font live in the newMpdf\Fonts\FontSubstitutionservice.Which font an emoji goes to
Checking whether a backup font is in colour loads its metrics the first time a run asks for a presentation: about 2 ms for five fonts with a warm font cache. A backup font that is only checked and never used isn't embedded.
Deviation from the plan
The PUA-budget item moves to Phase 1. It only matters for colour fonts, and in Phase 1 those fonts skip TrueType subsetting. The budget can be enforced there without keeping the subsetter's three copies of the PUA loop in step.
Known limits
WriteHTMLtext path, which already removes ZWJ/ZWNJ.SubstituteCharsNonCore()doesn't use the cluster scan.Tests
EmojiTest: the UTS Keep a greyscale JPEG greyscale when its Exif orientation is corrected #51 cases.EmojiTablesTest: the generator.ColorFormatsTest: format detection and tag aliases, including SVG and a COLRv1 table with no v0 records, from patched copies of the fixtures.OtlWithoutGdefTest: Noto Emoji's ligatures.EmojiSubstitutionTest: whole sequences, presentation order, a colour document font, and runs that meet SIP text or an emoji no font has.FontSubstitutionTest: backup font order, coverage and cached widths.FontCacheTest(the stale read),LookupFlagTest,OtlDumpTestandSharedFirstLookupFeatureTest(Aboriginal Sans, which has no GDEF).I also planted 38 small bugs across the new code. Every one that changes behaviour makes a test fail.
composer testpasses (2742 tests, including snapshots), and so doescomposer cs. PHPStan has no findings beyond the base's.#291–#293 haven't been rebased onto this yet. Until they are, GitHub shows #290's commits in #291's diff, and #293's
Otlwill needuse Mpdf\Unicode\Emoji;.🤖 Generated with Claude Code