Skip to content

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.ts

A2. 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_KEY

C2. 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.vue

C3. 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=Aa

C4. 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

  1. Build a creative with an 8+ MB custom font -- verify subsetting works
  2. Compare published bundle size before and after
  3. Verify all text renders correctly (no missing glyphs)
  4. Test with: plain text, form, conversation, Tag block creatives
  5. Test with multiple fonts on different blocks
  6. Test TTF and OTF uploads end-to-end
  7. Test Google Fonts search and selection
  8. Test custom font usage in HTML block via font-family name

Expected results

MetricBeforeAfter
Font file size8.1 MB~30 KB
Font memory parsing8.1 MB~30 KB
Font transfer292 KB~20 KB
Total bundle4.6 MB~4.3 MB
Risk of ad blockingHighLow
Available Google Fonts111500+
Font formats acceptedWOFF/WOFF2WOFF/WOFF2/TTF/OTF
Custom font in HTML blocksNot possibleWorks via font-family name

Internal documentation