Skip to content

RR-55: fonts handle, deck-level renderSvg, core 0.14 names (lockstep) - #150

Merged
michaeljgrimm merged 6 commits into
mainfrom
codex/rr-55-short-verb-api
Oct 7, 2026
Merged

michaeljgrimm merged 6 commits into
mainfrom
codex/rr-55-short-verb-api

Conversation

@michaeljgrimm

@michaeljgrimm michaeljgrimm commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Depends-On: OpenPresentation/opf#423
Depends-On: OpenPresentation/opf-pptx#196
Depends-On: OpenPresentation/opf-editor#116

Summary

RR-55 lockstep for opf-render (core 0.14.0): one fonts handle, whole-deck and one-slide render functions, core's slide context, and core's new import paths. Old names are deleted, not aliased. Output is unchanged: every golden raster hash is identical.

  • renderSvg(deck, options) returns string[]; renderSlideSvg(deck, index, options) returns one SVG.
  • loadFonts(options) (/fonts-node, /fonts-browser) returns the one handle; every deck-level function takes { fonts }.
  • A slide's canvas, layout, theme, colour scheme, font scheme, family names and darkBackground come from core resolveSlideContext.
  • Engine names are imported from @openpresentation/opf/composition; /fonts re-exports core's font-policy and symbol helpers instead of snapshots.
  • New tests: core-features (63 required core exports, every imported core name resolves, no feature detection, /fonts re-exports are core's own objects), slide-context (a roboto deck measures with Roboto), fonts-handle.

Old to new

Before 0.14
renderSvg(deck, { slideIndex }) (one slide) renderSlideSvg(deck, index, options)
renderSvgDeck(deck, options) renderSvg(deck, options) (string[])
slideIndex option removed
prepareNodeFonts, loadOfficeFontRegistry, loadBundledFontRegistry, loadBrowserFontRegistry, createFontRegistry loadFonts(options) per subpath; createFontRegistry is internal (src/font-registry.js, not an entry point)
prepareNodeFonts().options, { textMeasurement, embeddedFonts, fontFiles, useBundledFonts, loadSystemFonts } spread { fonts } (handle: textMeasurement, embeddedFonts, fontFiles, useBundledFonts, loadSystemFonts, registry, manifest, substitutions, ensure, pending, browser also dispose)
top-level textMeasurement / embeddedFonts on renderSvg, resolvePresentation, renderOptions fonts
top-level fontFiles / useBundledFonts / loadSystemFonts on svgToPng, svgToPdf fonts (fontDirs stays)
/export-browser svgToPdf takes fonts (embeds the handle's faces); fontData kept for use without a handle
<opf-deck>.fontRegistry; fonts attribute only fonts property takes a URL or a handle; fontRegistry removed
renderDeckHtml({ fonts: string }) fonts string (root URL attribute) or object (draws the markup)
present({ fonts }) registry handle
createFontGate(registry) createFontGate(fontsHandle)
browser registry loaders as the entry point fonts.ensure(presentation) / fonts.pending(presentation) (registry keeps ensureLazyFonts, ensureScripts, pendingLazyFonts, pendingScripts, loadScripts)
OPFRenderError('invalid-opf').issues .findings (core validate(deck, { only: ['format'] }) error findings; details.report is the whole report)
layout-overflow details.issues details.diagnostics
validatePresentation validate(x, { only: ['format'] })
engine names from @openpresentation/opf @openpresentation/opf/composition
/fonts FONT_POLICY (array), FONT_POLICY_SOURCE, FONT_POLICY_DECISIONS, SYMBOL_ENCODINGS, SYMBOL_ENCODINGS_SOURCE, symbolEncodingFor core's FONT_POLICY (table: .families, .provisionalDecisions), fontPolicyFor, isSymbolEncodedFamily, symbolCodeOf, mapSymbolText, scriptFontRole re-exported (the same objects)
diagnostics language-preview-unavailable, paragraph-direction-unavailable removed
catalog-resolution-failed for an unknown layout, theme or colour scheme id never thrown; core falls back and the render reports unresolved-layout, unresolved-theme, unresolved-color-scheme (font scheme: unresolved-font-scheme, unchanged)
resolvePresentation(...).slides[i].layout (inferred) undefined for a slide with no layout or an unknown id

Vetoable calls

  1. Unknown top-level options are silently ignored, as in core; there is no guard.
  2. loadFonts({ pack: 'none' }) is new: only the faces you supply (throws empty-font-registry without any).
  3. createFontRegistry, pickFace and skipUndecodableLookups live in the internal src/font-registry.js (not an entry point); tests import it from dist/ by path. symbolPreviewFaces stays in symbol-fonts.js; mapSymbolAdvances there is private.
  4. inferLayoutId and resolveLayout are deleted. A slide with no layout, or an unknown id, composes with no layout record. Unknown layout/theme/colour-scheme ids no longer throw; core's diagnostics are forwarded. The inline-object layout fallback is gone.
  5. darkBackground is taken from core; the renderer's own computation is deleted (equivalence checked on the corpus and on synthetic backgrounds).
  6. contextCatalogs() feeds host catalogs and catalogSources into the context in the renderer's old order (doc source, host catalogs, engine default source, bundled). Theme, colour-scheme and font-scheme records keep sorted keys.
  7. OPFRenderError.issues becomes .findings (also details.findings, details.report); layout-overflow and variable errors carry details.diagnostics.
  8. /fonts re-exports core's font-policy, symbol-code and script-slot helpers. Parity checked: the renderer's rows equal core's minus sources/note; symbol codes, advances and scriptFontRole match (core also accepts lowercase 'arab'). Core's mapSymbolText has no advance, so the preview adds it privately. Cost: the browser bundle grows by roughly 250 KB minified (core's symbol-font table is about 170 KB; total about 2.0 MB).
  9. <opf-deck>.fonts takes a URL or a handle (a handle wins), fontRegistry is deleted, renderDeckHtml({ fonts }) takes a string or an object, present({ fonts }) a string or a handle. The deck store passes only the handle's textMeasurement to inline slides, as before.
  10. resolvePresentation keeps its name (it returns the deck with every slide's geometry; resolveDeck adds nothing).
  11. One options type, RenderSvgOptions, serves renderSvg and renderSlideSvg, so there is room for skipHidden.

Legacy removed

  • Guards for an old core without resolveScriptFonts, paragraphDirection, patternRuns, metricTrendMark, tokenizeCode, resolveChartOptions, resolveChartData, chartPaletteForFill, glyphFallbackFamilies, resolveVariables.
  • The two *-unavailable diagnostics and the hand-drawn pattern fallbacks.
  • The renderer's copy of scriptFontRole, and its font-policy and symbol-encoding snapshots, generators and the CI snapshot check.
  • Test conditionals (hasCore, hasFallback, "skipped, no resolveLogo").
  • build-browser-check.mjs now really asserts paragraphDirection is in a browser bundle that draws a slide (the old check was silently skipped once the name left the root).

Tests (against core 1d7c57fd)

  • Goldens: 805 slides / 126 decks unchanged on the default baseline and on core's lock fixture (scripts/fixtures/opf-examples-png.example-decks.sha256.json).
  • npm test: 72 tests passed. npm run typecheck: 187 files, 0 failed. npm run validate, check:changes, check:fonts pass.
  • 22 Playwright browser suites pass.
  • Id-less partial inline theme and colorScheme render byte-identically to the same record laid over minimal / cool-horizon (the old behaviour).

Cannot pass yet / follow-ups

  • test:packed (and any job that installs published core) cannot pass until core 0.14.0 is published: package.json still says @openpresentation/opf ^0.13.0. The core range and the version move in the release-prep PR, per AGENTS.md.
  • test:variable-font-gate needs ../archive, so it was not run locally.
  • FA-05's skipHidden lands on RenderSvgOptions when this branch is rebased onto it (RenderSvgDeckOptions is deleted with renderSvgDeck).

🤖 Generated with Claude Code


Note

High Risk
Breaking public API across render, fonts, player, and export paths with deep core 0.14 coupling; integrators must migrate every call site even though raster goldens are unchanged.

Overview
RR-55 (breaking, core 0.14.0) reshapes the public API around a single loadFonts() handle and deck-level { fonts } instead of scattered textMeasurement, embeddedFonts, and fontFiles options. renderSvg now returns one SVG per slide (formerly renderSvgDeck); renderSlideSvg(deck, index) replaces single-slide renderSvg + slideIndex. prepareNodeFonts, loadOfficeFontRegistry, loadBrowserFontRegistry, and createFontRegistry as exports are removed in favor of loadFonts (/fonts-node, /fonts-browser), with ensure / pending on the handle and createFontRegistry moved internal.

Slide theme/layout/colour/font context and darkBackground come from core resolveSlideContext; unknown catalog ids no longer throw—they fall back with onDiagnostic (unresolved-*). OPFRenderError invalid-opf uses findings (not issues). Composition, charts, and fonts import core by name (no runtime “old core” guards); /fonts re-exports core FONT_POLICY and symbol helpers and drops local policy/symbol snapshots and the CI font-policy snapshot step.

<opf-deck>, player, renderDeckHtml, and browser svgToPdf take a fonts URL or handle (fontRegistry removed). Golden output is claimed unchanged.

Reviewed by Cursor Bugbot for commit cbfc08c. Configure here.

@cursor

cursor Bot commented Oct 7, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: fc53bd4b-e775-4a4b-8399-2da0c33f40de)

michaeljgrimm and others added 6 commits October 7, 2026 00:49
… (lockstep with core 0.14)

- renderSvg(deck, { fonts }) returns string[]; renderSlideSvg(deck, index, { fonts }) returns one SVG; renderSvgDeck and slideIndex are removed.
- loadFonts() from /fonts-node and /fonts-browser replaces prepareNodeFonts, loadOfficeFontRegistry, loadBundledFontRegistry, loadBrowserFontRegistry and createFontRegistry (now internal). Every deck-level function takes { fonts }.
- Slide canvas, layout, theme, colour scheme, font scheme, families and darkBackground come from core resolveSlideContext; inferLayoutId is removed and unknown ids fall back and are reported instead of throwing.
- OPFRenderError invalid-opf carries findings (core validate, format only).
- Engine names are imported from @openpresentation/opf/composition; /fonts re-exports core's font-policy and symbol-code helpers instead of snapshots.
- Tests: core-features, slide-context and fonts-handle added; every golden hash is unchanged.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…d suites before core 0.14 is published

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the short-verb API)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…res lists the FA names

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@michaeljgrimm
michaeljgrimm force-pushed the codex/rr-55-short-verb-api branch from 4cf2529 to cbfc08c Compare October 7, 2026 07:49
@cursor

cursor Bot commented Oct 7, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: f8906af9-0855-48ca-b9b4-bbea1c93aa2b)

@michaeljgrimm
michaeljgrimm merged commit 38ad562 into main Oct 7, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

full-ci Also run the full CI matrix on this PR (macOS, Windows, Node 22 and 26 legs)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant