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