feat(fonts): add bundled font substitutes and readiness reporting#3608
Open
caio-pizzol wants to merge 12 commits into
Open
feat(fonts): add bundled font substitutes and readiness reporting#3608caio-pizzol wants to merge 12 commits into
caio-pizzol wants to merge 12 commits into
Conversation
Render common Word fonts the browser lacks (Calibri, Cambria, Arial, Times New Roman, Courier New) with metric-compatible open substitutes loaded before measurement, via a manifest-driven asset provider that emits font files as separate assets instead of inlining them into JS. Adds a read-only fonts diagnostics surface (superdoc.fonts.*, fonts-changed / onReport). Runtime asset base defaults to /fonts/; a configurable/script-relative base is a follow-up. Excludes Aptos, the write API, embedded fonts, and legal sign-off.
This comment was marked as outdated.
This comment was marked as outdated.
Make the bundled font assets deployable across CDN, npm/bundler, and SSR instead of assuming a root-served /fonts/. Adds a fonts.assetBaseUrl plus a sync fonts.resolveAssetUrl config; resolution order is resolveAssetUrl > assetBaseUrl > CDN script-relative > /fonts/. The CDN build captures its own script URL in cdn-entry and defaults the base to ./fonts/ next to superdoc.min.js. A substitute whose asset fails to load now surfaces in diagnostics (missing=true, loadStatus=failed) and warns once with the URL, so a misconfigured base is never silent. The cdn/laravel/nextjs examples serve the bundled fonts so their smoke tests have no 404s.
# Conflicts: # packages/superdoc/src/core/SuperDoc.ts
…antics Two review fixes for the asset-base work. The bundled pack installs once per document FontFaceSet (shared across SuperDoc instances on a page); a later install with a different assetBaseUrl/resolveAssetUrl was silently dropped - it now keeps the first config and warns on a genuine conflict instead. And the report's `missing` flag now means a SETTLED non-loaded state (failed / timed_out / fallback_used), not transient unloaded/loading, so an early getReport() pull before the gate settles no longer over-reports.
…placeholder font Two CI failures introduced by the font work on this branch: - cdn-entry.js imports @superdoc/font-system (for the script-relative asset base), which the superdoc Vitest env could not resolve - the alias is intentionally kept out of getAliases (it breaks the vite-plugin-dts build) and only added in the CDN config. Add a Vitest-scoped alias so cdn-entry.test.js resolves it; the dts build is unaffected. - The empty-block-SDT placeholder paints fontFamily through resolvePhysicalFamily like all painted text, so Arial renders as Liberation Sans. Update the stale assertion to expect the physical family (logical stays for export, not DOM).
…tion The load-before-measure font gate adds an await ahead of incrementalLayout, so dispatching the Ctrl+Alt+H shortcut on "incrementalLayout called" alone now races the layout-applied state and the header/footer session cannot yet see the missing region. Add the same render-settle step the sibling header/footer tests use, and await the blocked event. Test-only; matches the file's existing pattern.
…t pages Header/footer regions come from resolved layout pages (rebuildRegions), not the headers[] array, so headers=[] left a per-page region and the shortcut took the activation path. With the font gate making layout timing deterministic, the page-mount poll recursed under the synchronous rAF mock and overflowed. Use a layout with no pages (the real no-region condition) and drop the flaky setTimeout settle.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b3ede38546
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Adds the patch coverage codecov flagged on #3608, test-only (no product change): - cdn-entry: the document.currentScript -> ./fonts/ asset-base default, both the success path and the best-effort catch, via module re-eval with the heavy SuperDoc graph stubbed and @superdoc/font-system left real. - SuperDoc.fonts: getReport / getMissingFonts / getDocumentFonts (incl. the logical family map) and the empty-arrays-when-no-active-editor fallback.
There was a problem hiding this comment.
2 issues found across 81 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
After a document swap an old editor can still emit fonts-changed (a timed-out font
finishing later) with no document id to disambiguate; the relay forwarded it as the
active document's report and poisoned the onReport cache. Gate the relay callback to
the active editor so superdoc.on('fonts-changed') / fonts.onReport() reflect the
active document. Addresses the P2 review comment on SuperDoc.ts:1573.
There was a problem hiding this comment.
1 issue found across 3 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Public-contract accuracy fixes, no runtime behavior change: - classify the six new root font exports (SuperDocFontsApi, FontsConfig, FontsChangedPayload, FontResolutionRecord, FontAssetUrlContext, FontAssetUrlResolver) in the root snapshot (json + md). AGENTS.md mandate; the closure gate skips unclassified names so this was otherwise silent. - remove the never-emitted 'config-change' from FontsChangedPayload.source (only 'initial' | 'late-load' is emitted). Re-add when the write API emits it. - fix the stale FontReadinessGate.resolveFamilies "identity until T1" comment; this PR wires resolvePhysicalFamilies as the resolver. - fix the fonts.onReport doc that overpromised "regardless of timing"; it replays only if a report has resolved, nothing pre-settle after a swap. - make verify-bundled-metrics.py fail on skipped source .ttf unless ALLOW_SKIPS, so a CI run can never report success after validating nothing.
Classifying the six font types as type-only root exports requires matching AssertNotAny<T> coverage in the consumer fixture (SD-3213a gate); without it the public-types fixture check fails. Completes the classification change.
…rrors Two review follow-ups: - The active-editor guard on the fonts-changed relay covered the live event but not the cached replay-on-wire, so creating an inactive editor with a cached payload could still poison the onReport cache. Factor the rule into one #fontReportSurfaces predicate used by both paths; add a regression test for creating an inactive editor with a cached report. - The dev bundled-fonts middleware piped a read stream with no error handler, so a mid-read failure could crash the Vite dev server. Respond 500 (if nothing was sent) and close on stream error.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds core font-rendering support: documents using common Word fonts the browser lacks (Calibri, Cambria, Arial, Times New Roman, Courier New) render with metric-compatible open substitutes loaded before measurement, instead of silently paginating against a browser fallback that drifts page counts by machine. Also adds a read-only diagnostics surface for what each font resolved to and whether it loaded. Core font-rendering support, not full Word font coverage.
What changed
@superdoc/font-system: logical→physical resolver (Calibri→Carlito, …), a per-FontFaceSetregistry + load-state, and a resolver/registry-based report.url(<base>/<file>)faces (no binary imports in shared runtime), and a vite plugin that serves the pack at/fonts/*in dev and emits it as separatedist/fonts/*assets in build. Font bytes are never inlined into the JS bundle; the manifest scales to the full (~40-font) rollout by adding rows.fonts.assetBaseUrlplus a syncfonts.resolveAssetUrlconfig, resolvedresolveAssetUrl>assetBaseUrl> CDN script-relative >/fonts/. The CDN<script>build captures its own script URL and defaults the base to./fonts/. A substitute whose asset fails to load surfaces in diagnostics (missing: true,loadStatus: 'failed') and warns once with the URL, so a misconfigured base is never silent.superdoc.fonts.getReport()/getMissingFonts()/getDocumentFonts()(pull) andonReport()(snapshot-then-subscribe). A new authoritativefonts-changedevent /onFontsChangedconfig carries{documentFonts, resolutions, missingFonts, loadSummary, source, version}. Legacyfonts-resolvedearly probe unchanged.Verified
.woff2metric preservation checked by a committed script.dist/fonts/*assets (woff2 + OFL/Apache license texts); the substitutes load live from/fonts/*.woff2and the report shows themloaded.check:public:superdoc, 13 stages) green; unit tests for resolver, gate, report, asset-URL resolution, and the SuperDoc relay/onReport.Known limitation
FontFaceSet, so multiple SuperDoc instances on one page share one font config; the first wins and a later, conflictingfonts.assetBaseUrlis ignored (with a console warning, not silently).Not included (out of scope)
add/map/preload)Legal — release blocker
The bundled clones (Carlito, Caladea, Liberation Sans/Serif/Mono) ship with their OFL-1.1 / Apache-2.0 license texts (
dist/fonts/), but shipping them publicly needs legal sign-off. Treat as a blocker for any public release that includes the bundled assets.