Fonts: carry uploaded faces through export/import + embed in SVG export

This commit is contained in:
2026-06-16 23:52:16 +03:00
parent 2ed9db3792
commit d8a7bcb7c1
16 changed files with 810 additions and 90 deletions
+25 -4
View File
@@ -171,10 +171,17 @@ ships, it is an explicit per-font user action, never automatic.
one file drives the whole weight range — only weight/width survive Vega's text rendering
(no `font-variation-settings` hook). No `CustomTheme.fonts` field: a used face is derived
by scanning configs/specs (`collectFontFamilies`) — the config is the
source of truth, and snippets use fonts with no theme to carry a field. Not done here:
embedding font bytes into the §08 export + SVG export (shared base64 machinery, with item 7).
7. **Deferred** — Google Fonts opt-in tier; SVG export font embedding (+ §08 font-byte
round-trip, shared machinery); built-in expressive preset gallery ("Editorial",
source of truth, and snippets use fonts with no theme to carry a field.
7. **Font export round-trip** ✅ (2026-06-16) — uploaded faces travel base64-encoded in the
§08 envelope (additive `fonts` array, decoded on import, **skipped** on family clash so a
self-backup doesn't pile up copies, rolled back with the rest on a failed import, registered
live so they render without reload), and the per-chart **SVG export embeds** the referenced
uploaded faces as `@font-face` data-URIs so an exported vector renders the right type off-app.
Shared base64 + `fontDataUri`/`primaryFamilyName`/`serializeFontAsset` machinery in
`core/font-asset.ts`; SVG embed + family-matching in `core/chart-export.ts`. Roster and
system stacks are never embedded (decoration with their own fallbacks; their bytes aren't in
the font library).
8. **Deferred** — Google Fonts opt-in tier; built-in expressive preset gallery ("Editorial",
"Terminal", "Sketch") showcasing the roster.
**Rejected:** per-snippet theme field (2026-06-12 — `spec.config` + merge/extract covers
@@ -234,6 +241,20 @@ the app standardizes on v6.
## 6. Status log
- **2026-06-16 (slice 7)** — **font export round-trip + SVG embed.** Uploaded faces now
survive a workspace transfer and travel inside an exported SVG. Core: `serializeFontAsset`/
`deserializeFontAsset` (+ base64 helpers), `primaryFamilyName`, and `fontDataUri` in
`font-asset.ts`; the §08 envelope grew an additive `fonts` array (`export-envelope.ts`),
`normalizeImport` decodes it, and a new `dropClashingFonts` enforces the skip-on-clash merge
rule (`import-normalize.ts`). Service: `transfer.ts` exports all library fonts, and import
dedupes by family, commits fonts in the pre-snippet phase (rolled back with datasets/themes on
a failed snippet write), and registers the new faces on success. SVG: `referencedUploadedFonts`
- `embedFontsInSvg` in `chart-export.ts`, wired through the renderer's `toImageURL('svg')` with
the referenced faces resolved in LivePreview's `getImageUrl` (config captured per render).
Decision (font clash): **skip, not rename** — a font is identified by its family (the key in
config slots), so an existing same-named face satisfies the reference, and a self-backup
doesn't accrete "Font 2" copies; recorded in spec §08 → Name conflicts. Verified: typecheck,
lint, full tests. Remaining in §4: Google Fonts opt-in tier; preset gallery.
- **2026-06-16 (slice 6)** — **user font upload + variable-font weight support.** New
`FontAsset` entity (instance #4 of the entity-store kind, confirmed by an eng-council
pre-build consult): `fonts` store @ DB v3, adapter/migration, `FontStore`,