Appearance
Font Optimization Plan
Activate font subsetting, expand format support, add all Google Fonts with search, and make custom fonts usable in HTML blocks.
Why
Real-world case: 8.1 MB custom font, creative uses ~50 Norwegian characters. Browser must parse the full 8.1 MB in memory even though CDN compresses the transfer to 292 KB. Ad verifiers measure decompressed resource weight and memory usage -- this is a concrete reason creatives get blocked.
After subsetting: ~30 KB font, ~30 KB transfer, ~30 KB memory. Problem eliminated.
Beyond optimization: the font selector is limited to 11 hardcoded Google Fonts, custom fonts can't be used in HTML blocks, and only WOFF/WOFF2 uploads are accepted despite the backend supporting TTF/OTF.
Scope
Part A: Font subsetting (Creative-Composer)
- Activate existing font-optimizer pipeline
- Fix charset extraction to cover all text sources
- Add Latin safety net charset
- Handle multiple fonts per creative
- Fall back to full Latin subset for unpredictable content
Part B: Format support (Application-Frontend)
- Accept TTF/OTF uploads (backend already supports them)
Part C: Google Fonts expansion (Application-Frontend, CAV-29)
- Replace hardcoded 11-font dropdown with full Google Fonts catalog
- Add search bar for font selection
Part D: Custom fonts in HTML blocks (Creative-Engine)
- Use readable font-family names instead of hashed names
- Custom fonts automatically become available in HTML blocks via their real name
Part A: Font Subsetting
A1. Fix font URL extraction in profile.ts
profile.ts has commented-out code for extracting the custom font URL. Uncomment and verify it reads from the correct path in the remapped JSON.
Creative-Composer/src/bin/commands/job/profile.tsA2. Expand extractCharset coverage
Current extractCharset only reads payload.text. Expand to cover:
- Button labels
- Form input placeholders and labels
- Form submit button text
- Conversation choice text and response text
- Slider navigation labels
- Ad label text (already covered)
- Creative name (already covered)
- Close button label from creativeProperties
Creative-Composer/src/font-optimizer/index.ts -- extractCharset()A3. Add Latin safety net
Always include baseline characters regardless of what extractCharset finds:
U+0020-007E (ASCII: A-Z, a-z, 0-9, basic 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 sign)~200 extra glyphs, negligible size impact.
A4. Make optimizable() less conservative
Currently bails out entirely for Tag blocks and free-text inputs. Instead:
- If creative has unpredictable text: use full Latin safety net as the charset (still massive savings for CJK/Arabic fonts)
- Only skip optimization if we truly can't predict anything (edge case)
A5. Handle multiple fonts per creative
Current fontopt assumes one font. A creative can have different custom fonts per block. Iterate over all unique font URLs found in the remapped JSON.
A6. Wire fontopt into job.ts
Import fontopt and call it between remapping and building:
typescript
// After remapping, before build
const fontUrl = profile.customFontUrl // from step A1
if (fontUrl) {
const result = await fontopt(fontUrl, remappedJson)
if (result.ok) {
fontData = result.value // base64 WOFF2
}
}Pass fontData to injectTag instead of null.
A7. Verify Engine loads the optimized font
Check how CUSTOMFONTDATAPLACEHOLDER is used in the tag stub. The optimized font (base64 WOFF2) needs to either:
- Replace the CDN URL with a data URI in the creative JSON, or
- Be embedded in the tag stub and loaded by the Engine
Determine which path the original developer intended and verify it works end-to-end.
Part B: Format Support
B1. Accept TTF/OTF in frontend
One-liner in Application-Frontend:
TypefaceSection.vue: change accept from ['.woff', '.woff2'] to ['.woff', '.woff2', '.ttf', '.otf']Backend already accepts these formats. Composer converts to WOFF2 at build time regardless of input format.
Part C: Google Fonts Expansion (CAV-29)
C1. Fetch Google Fonts catalog
Use the Google Fonts API to get the full font list. Can be fetched at build time or cached/bundled as a static JSON.
https://www.googleapis.com/webfonts/v1/webfonts?key=API_KEYC2. Replace font dropdown with searchable selector
Replace the current hardcoded InputSelect dropdown in TypefaceSection.vue with a searchable font picker. Show font name + preview of the typeface.
Application-Frontend -- TypefaceSection.vueC3. Lazy-load font preview
Don't load all 1500 fonts at once. Load previews on-demand as user scrolls/searches, using the Google Fonts CSS API:
https://fonts.googleapis.com/css2?family=Roboto&text=AaC4. Engine already supports Google Fonts
FontHelper.ts already loads Google Fonts via <link> tag. No Engine changes needed -- just pass the correct font-family name from the frontend.
Part D: Custom Fonts in HTML Blocks
D1. Use readable font-family names in Engine
Change generateCustomFontFamily() in helpers.ts to produce a readable name instead of a hash:
Current: "eoq_Poppins_Regular_woff"
After: "Poppins Regular"Strip file extension, keep original name. The @font-face rule uses this as font-family, making it usable by any CSS in the iframe -- including HTML blocks.
Creative-Engine/src/styles/components/helpers.ts -- generateCustomFontFamily()D2. Custom fonts just work in HTML blocks
Once D1 is done, users write font-family: 'Poppins Regular' in their HTML block CSS and it works -- the @font-face is already injected into the iframe head by FontHelper.
No new upload mechanism needed. No changes to HTML block properties.
Optional: show a hint in the HTML editor listing available custom fonts.
Testing
- Build a creative with an 8+ MB custom font -- verify subsetting works
- Compare published bundle size before and after
- Verify all text renders correctly (no missing glyphs)
- Test with: plain text, form, conversation, Tag block creatives
- Test with multiple fonts on different blocks
- Test TTF and OTF uploads end-to-end
- Test Google Fonts search and selection
- Test custom font usage in HTML block via font-family name
Expected results
| Metric | Before | After |
|---|---|---|
| Font file size | 8.1 MB | ~30 KB |
| Font memory parsing | 8.1 MB | ~30 KB |
| Font transfer | 292 KB | ~20 KB |
| Total bundle | 4.6 MB | ~4.3 MB |
| Risk of ad blocking | High | Low |
| Available Google Fonts | 11 | 1500+ |
| Font formats accepted | WOFF/WOFF2 | WOFF/WOFF2/TTF/OTF |
| Custom font in HTML blocks | Not possible | Works via font-family name |