Appearance
Font Glyph Pruning
Problem
Custom fonts uploaded to creatives can be very large (8+ MB for fonts with full Unicode coverage). Ad networks and publishers may block or remove creatives that exceed size thresholds. A font with 10,000+ glyphs where the creative only uses Norwegian text is massively wasteful.
Existing Infrastructure (unused)
Creative-Composer already has a complete font optimization pipeline that was built but never wired into the build job:
Files
Creative-Composer/src/font-optimizer/index.ts-- core functionsCreative-Composer/src/bin/commands/job/fontopt.ts-- job wrapper (never imported)Creative-Composer/src/bin/commands/job.ts-- build pipeline (passesfontData: null)
What's already built
optimizable(creativeJson)-- checks if creative text is predictable. Returnsfalseif creative has:- Any
Tagblock (custom HTML with unpredictable text) - Any
TextInputwith non-number validation (user can type anything)
- Any
extractCharset(creativeJson)-- walks all components, collects every character from:- Block text content
- Ad label text
- Creative name
- Adds number literals if any number-only input exists
createFontSubset(source, target, include)-- usessubset-font(npm, harfbuzz WASM) to create a WOFF2 containing only the needed glyphsconvertToWoff2(source, target)-- converts WOFF to WOFF2 usingfontverterfor non-optimizable creativesTag injection --
injectTag.tsalready has aCUSTOMFONTDATAPLACEHOLDERreplacement that accepts base64 font data. Currently always receivesnull.
Why it's not active
The fontopt function in fontopt.ts is exported but never imported in job.ts. The build pipeline skips font optimization entirely. Likely was built as a prototype and never completed/tested.
What's Needed to Activate
Minimum viable activation
- Import
fontoptinjob.ts - Call it between remapping and building, pass the font URL from creative JSON
- Pass the resulting base64 font data to
injectTaginstead ofnull - Verify the engine's
FontHelper.tscan load the inline font data (or the tag stub injects it correctly)
Gaps in current implementation
extractCharsetis incomplete: only readspayload.text, doesn't cover:- Button labels
- Form placeholder text, validation messages, submit button text
- Slider navigation labels
- Conversation response options
- Any text set via
creativeProperties(e.g. close button label) - Dynamic content from feeds/DCO (if implemented in the future)
optimizableis conservative: bails out entirely for Tag blocks and free-text inputs. Could instead fall back to a broader "safe" character set (full Latin + common punctuation) instead of skipping optimization.No safety net: the charset extraction doesn't include a baseline like
U+0020-007E(ASCII) +U+00C0-00FF(Latin-1 Supplement for ÆØÅ etc.). If any character is missed, the font renders with fallback -- visible to end users.Multiple fonts per creative: current code assumes one font. Creatives can have different fonts per block.
No flow awareness: charset extraction reads all components but doesn't account for flow branches where different text appears.
Recommended Safety Net
Always include regardless of extracted charset:
U+0020-007E -- ASCII (A-Z, a-z, 0-9, punctuation)
U+00C0-00FF -- Latin-1 Supplement (ÆØÅ, accented chars)
U+2013-2014 -- en-dash, em-dash
U+2018-201D -- smart quotes
U+2026 -- ellipsis
U+20AC -- euro signThis covers Norwegian, Swedish, Danish, German, French, Spanish, and most Western European text. Adds ~200 glyphs at most -- negligible size impact vs. the 10,000+ in a full CJK font.
Expected Impact
- Full CJK/Arabic font: 8+ MB to 30-80 KB (99%+ reduction)
- Large Latin font: 500 KB to 20-40 KB (90%+ reduction)
- Already-small Latin font: 30 KB to 15-20 KB (modest, but WOFF2 conversion alone helps)
Next Steps
- Audit
extractCharset-- verify all text sources in a real creative JSON are covered - Add safety net charset -- always include Latin base set
- Wire
fontoptintojob.ts-- the actual activation - Test with real creatives -- compare file sizes before/after, verify no missing glyphs
- Handle multiple fonts -- iterate per font, not per creative
- Consider broader fallback for Tag/TextInput -- use full Latin set instead of skipping entirely