Skip to content

fix: embed woff2 & inlined (data: URI) web fonts#2

Merged
mmoollee101-lab merged 1 commit into
mainfrom
fix/embed-woff2-datauri-fonts
Jul 16, 2026
Merged

fix: embed woff2 & inlined (data: URI) web fonts#2
mmoollee101-lab merged 1 commit into
mainfrom
fix/embed-woff2-datauri-fonts

Conversation

@mmoollee101-lab

Copy link
Copy Markdown
Owner

Problem

Re-converting a self-contained HTML deck (fonts shipped as url("data:font/woff2;base64,…"), resolved at runtime from a UUID placeholder) produced a .pptx where every font was broken: each run was tagged typeface="Pretendard" but ppt/fonts was empty, so PowerPoint substituted a fallback and bold text rendered as a mismatched faux-bold.

Two root causes:

  1. Font resolution parsed only the raw <style>/<link> text — where the src is still an unresolved placeholder (a bare UUID) — so it found nothing to embed.
  2. It skipped woff2 outright ("the embedder can't decode it").

Fix

  • Resolve fonts from the live CSSOM for faces inlined as data:/blob: (what self-contained decks actually end up with after their loader runs).
  • Decode woff2 → TrueType via fonteditor-core's built-in woff2 module (no new dependency) so PowerPoint / dom-to-pptx — both woff2-blind — can embed it.
  • Embed the real Regular and Bold faces.
  • Keep the existing Node-side parse of CDN <link>/<style> unchanged: a plain-UA fetch returns the full font, not the browser's per-script unicode-range subsets (a subset would embed as a tiny face missing most glyphs). CSSOM subset faces are skipped for the same reason.
  • Only families actually used by the slide text are embedded.

Verification (PowerPoint COM render, Pretendard not installed system-wide)

  • woff2 data:-URI deck (Pretendard) — before: fallback + faux-bold; after: full Regular+Bold embedded (2.5 MB, full Korean coverage), renders faithfully.
  • CDN-font sample (Noto Sans KR) — still embeds the full 5.9 MB face with Korean coverage (guards against a subset-collapse regression).

Docs (README, CLI help) and CHANGELOG updated.

Self-contained decks that ship their fonts as
`url("data:font/woff2;base64,…")` — resolved at runtime from a UUID
placeholder in the raw CSS — came out with the font *tagged* but not
embedded: font resolution parsed only the raw <style>/<link> text (still
the unresolved placeholder) and skipped woff2 outright. PowerPoint then
substituted a fallback and bold text rendered as a mismatched faux-bold
("all the fonts are broken").

Font resolution now also reads the LIVE CSSOM for faces inlined as
data:/blob:, decodes woff2 to TrueType (via fonteditor-core's woff2
module — no new dependency) so PowerPoint/dom-to-pptx can read it, and
embeds the real Regular and Bold faces. The existing Node-side parse of
CDN <link>/<style> stylesheets is kept as-is (a plain-UA fetch returns
the FULL font, not the browser's per-script unicode-range subsets), and
CSSOM subset/unicode-range faces are skipped for the same reason. Only
families actually used by the slide text are embedded.

Verified end-to-end with PowerPoint COM render: a woff2 data:-URI deck
(Pretendard) now embeds full Regular+Bold with Korean coverage and
renders faithfully; the CDN-font sample (Noto Sans KR) still embeds the
full 5.9 MB face.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mmoollee101-lab
mmoollee101-lab merged commit 9b022ff into main Jul 16, 2026
3 checks passed
@mmoollee101-lab
mmoollee101-lab deleted the fix/embed-woff2-datauri-fonts branch July 16, 2026 06:38
mmoollee101-lab added a commit that referenced this pull request Jul 16, 2026
Retrospective PDCA completion report for the fix landed in PR #2
(main 9b022ff): embedding woff2 and inlined data:-URI web fonts.
Records root cause, the two-source resolver design, acceptance
criteria (6/6), PowerPoint-COM visual verification, and the
CDN-subset regression caught during development.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
mmoollee101-lab added a commit that referenced this pull request Jul 16, 2026
Guards the two failure modes fixed in #2 so they can't silently return:
  R1  a woff2 font inlined as a data: URI must be embedded (offline)
  R2  a CDN font must embed FULL, not a unicode-range subset (network-gated)

node:test suite drives the public API (convertHtmlToPptx) end-to-end and
asserts on the output .pptx bytes: ppt/fonts non-empty, embeddedFontLst has
Regular AND Bold, every embedded EOT's cmap covers Korean, and a byte-size
floor rejects the 31 KB subset trap. Zero new dependencies (node:test +
jszip + fonteditor-core, all already present).

The R1 fixture is a self-contained 5.6 KB HTML deck inlining a ~1.7 KB
Pretendard subset (OFL-1.1, attributed in test/fixtures/NOTICE.md), produced
by scripts/gen-font-fixture.js. R2 reuses samples/sample.html (Noto Sans KR)
and skips loudly when the CDN is unreachable.

Runs via `npm run test:fonts`, wired into CI as a dedicated `font-embed`
job that launches real Chromium (the fast lint/load matrix stays browser-
free). Verified: both cases pass, and neutralizing the data:-URI embed makes
R1 fail with "no embedded fonts (ppt/fonts empty)".

Includes the full PDCA record (plan/design/analysis/report) for the feature.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant