@json-to-office/shared 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1284,7 +1284,7 @@ function deepMerge(target, source) {
1284
1284
  if (isObject(target) && isObject(source)) {
1285
1285
  Object.keys(source).forEach((key) => {
1286
1286
  if (isObject(source[key])) {
1287
- if (!(key in target)) {
1287
+ if (!(key in target) || !isObject(target[key])) {
1288
1288
  output[key] = source[key];
1289
1289
  } else {
1290
1290
  output[key] = deepMerge(target[key], source[key]);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types/services.ts","../src/fonts/collect.ts","../src/fonts/document-registry.ts","../src/fonts/validator.ts","../src/fonts/synthesize.ts","../src/fonts/sources/data-loader.ts","../src/fonts/sources/google-fetcher.ts","../src/fonts/sources/url-fetcher.ts","../src/fonts/sources/ttf-validate.ts","../src/fonts/cache/memory-cache.ts","../src/fonts/registry.ts","../src/fonts/catalog/popular-google.ts","../src/fonts/catalog/upstream-overrides.ts","../src/fonts/substitute.ts","../src/theme/chart-palette.ts","../src/utils/deepMerge.ts"],"sourcesContent":["/**\n * Service configuration types for external integrations (e.g. Highcharts export server)\n */\n\n// ============================================================================\n// Visual rasterization policy — single source of truth for DPI bounds shared\n// by the visual schema, the in-process rasterizer, the flatten transform, and\n// both HTTP /rasterize surfaces. Keep these in sync in one place.\n// ============================================================================\n\n/** Default raster resolution when a `visual` does not specify one. */\nexport const DEFAULT_VISUAL_DPI = 200;\n/** Minimum accepted raster resolution. */\nexport const MIN_VISUAL_DPI = 36;\n/** Maximum accepted raster resolution (bounds bitmap size / DoS surface). */\nexport const MAX_VISUAL_DPI = 600;\n\n/** Clamp an arbitrary dpi to [MIN_VISUAL_DPI, MAX_VISUAL_DPI]; non-finite → default. */\nexport function clampVisualDpi(dpi: unknown): number {\n if (typeof dpi !== 'number' || !Number.isFinite(dpi))\n return DEFAULT_VISUAL_DPI;\n return Math.min(MAX_VISUAL_DPI, Math.max(MIN_VISUAL_DPI, Math.round(dpi)));\n}\n\nexport type HighchartsHeaders = Record<string, string>;\n\nexport type HighchartsHeadersResolver = (\n body: unknown\n) => HighchartsHeaders | Promise<HighchartsHeaders>;\n\nexport interface HighchartsServiceConfig {\n serverUrl?: string;\n headers?: HighchartsHeaders | HighchartsHeadersResolver;\n}\n\n// ============================================================================\n// PPTX rasterization service (used by the docx `visual` component)\n// ============================================================================\n\nexport type PptxServiceHeaders = Record<string, string>;\n\nexport type PptxServiceHeadersResolver = (\n body: unknown\n) => PptxServiceHeaders | Promise<PptxServiceHeaders>;\n\n/** Max font faces accepted in one rasterize request. */\nexport const MAX_RASTERIZE_FONTS = 32;\n/** Max total DECODED font bytes accepted in one rasterize request. */\nexport const MAX_RASTERIZE_FONT_BYTES = 8 * 1024 * 1024;\n\n/**\n * One font face shipped alongside a rasterize request so the rasterizer's\n * out-of-process LibreOffice can render the slide with the document's real\n * fonts instead of a system fallback.\n *\n * `data` is base64 of the raw font file — NO `data:` URI prefix — so the\n * request stays plain serializable JSON.\n *\n * `family` is the CATALOG family (`ResolvedFont.family`, e.g. \"Inter\"), not\n * the synthesized sub-family the presentation references. The receiving\n * stager applies `synthesizeFamilyName` + `rewriteFontFamilyName` itself,\n * exactly as it does for the PDF-preview path; pre-synthesizing here would\n * double-apply the suffix (\"Inter Light Light\").\n */\nexport interface RasterizeFontFace {\n family: string;\n weight: number;\n italic: boolean;\n /** Base64-encoded font file bytes (no `data:` prefix). */\n data: string;\n format?: 'ttf' | 'otf' | 'woff' | 'woff2';\n}\n\n/**\n * Request handed to a pptx rasterizer: a single-slide pptx presentation\n * component definition plus the target resolution.\n */\nexport interface PptxRasterizeRequest {\n /** A pptx presentation component definition ({ name: 'pptx', ... }) with one slide */\n presentation: unknown;\n /** Target raster resolution in dots-per-inch */\n dpi: number;\n /**\n * Directory that relative asset paths inside the presentation resolve\n * against — the originating document's own directory. Absent → the\n * rasterizer's cwd, the legacy behavior (#142).\n */\n baseDir?: string;\n /**\n * Font faces to stage for the rasterizer's LibreOffice launch. Absent →\n * system fonts only, which is what every non-font-aware caller (and every\n * pre-Area-6 client) sends.\n */\n fonts?: RasterizeFontFace[];\n}\n\n/**\n * Result returned by a pptx rasterizer.\n */\nexport interface PptxRasterizeResult {\n /** Rendered PNG as a base64 data URI (data:image/png;base64,...) */\n base64DataUri: string;\n /** Natural pixel width of the rendered image */\n width: number;\n /** Natural pixel height of the rendered image */\n height: number;\n}\n\n/**\n * In-process rasterizer callback. Implementations build the .pptx from the\n * presentation JSON and rasterize it to a PNG (e.g. via LibreOffice + poppler).\n */\nexport type PptxRasterizer = (\n request: PptxRasterizeRequest\n) => Promise<PptxRasterizeResult>;\n\n// ============================================================================\n// Batch rasterization (#153) — one request rasterizes many independent slides.\n//\n// Each slide is a complete single-slide presentation (the exact shape a\n// single {@link PptxRasterizeRequest} carries), NOT a slide fragment of one\n// merged deck. This keeps slides independent — each may use its own canvas\n// size, theme, and dpi, so callers never need to group visuals — and lets\n// implementations key per-slide caches identically to the single-slide path.\n// ============================================================================\n\n/**\n * Maximum slides accepted in one batch request. Shared by the HTTP surface\n * (request validation) and clients (chunk size) so the two cannot drift.\n * Bounds per-request work and response size the same way rate limits bound\n * request counts.\n */\nexport const MAX_RASTERIZE_BATCH_SLIDES = 32;\n\n/** One slide in a batch: a single-slide presentation plus its resolution. */\nexport interface PptxRasterizeBatchSlide {\n /** A pptx presentation component definition ({ name: 'pptx', ... }) with one slide */\n presentation: unknown;\n /** Target raster resolution in dots-per-inch (absent → service default) */\n dpi?: number;\n}\n\n/** Request handed to a batch pptx rasterizer. */\nexport interface PptxRasterizeBatchRequest {\n /** Slides to rasterize; results come back index-aligned with this array. */\n slides: PptxRasterizeBatchSlide[];\n /** Base directory for relative asset paths, shared by every slide (#142). */\n baseDir?: string;\n /**\n * Font faces staged for the batch's LibreOffice launch, shared by every\n * slide exactly like `baseDir`. Deliberately REQUEST-level and never\n * per-slide: {@link PptxRasterizeBatchSlide} stays `{presentation, dpi}` so\n * batch-internal dedupe and the per-slide disk-cache key stay uniform with\n * the single-slide path.\n */\n fonts?: RasterizeFontFace[];\n}\n\n/**\n * Pipeline stage a slide failed in. `build` failures are caused by the\n * slide's own JSON (safe to surface verbatim to callers); `convert` and\n * `rasterize` failures are environment/tooling errors whose raw messages may\n * carry host paths — HTTP surfaces sanitize those.\n */\nexport type PptxRasterizeFailureStage = 'build' | 'convert' | 'rasterize';\n\n/**\n * Per-slide outcome. A batch response is 200-with-item-errors rather than\n * all-or-nothing: one bad visual must not discard its siblings' pixels.\n */\nexport type PptxRasterizeBatchSlideResult =\n | ({ ok: true } & PptxRasterizeResult)\n | { ok: false; error: string; stage?: PptxRasterizeFailureStage };\n\n/** Result returned by a batch pptx rasterizer. */\nexport interface PptxRasterizeBatchResult {\n /** Index-aligned with the request's `slides` (same length, same order). */\n results: PptxRasterizeBatchSlideResult[];\n}\n\n/**\n * In-process batch rasterizer callback. Batch-level failures (missing\n * binaries, bad request) throw; per-slide failures land in `results`.\n */\nexport type PptxBatchRasterizer = (\n request: PptxRasterizeBatchRequest\n) => Promise<PptxRasterizeBatchResult>;\n\n/**\n * Configuration for the pptx rasterization service backing `visual` components.\n *\n * Mirrors {@link HighchartsServiceConfig}: the published packages depend on this\n * interface, never on a binary. A host injects either an in-process `render`\n * callback or an HTTP `serverUrl`.\n */\nexport interface PptxServiceConfig {\n /**\n * In-process rasterizer. Takes precedence over `serverUrl` when provided.\n * Ideal for tests (no binaries) and single-process hosts.\n */\n render?: PptxRasterizer;\n /**\n * In-process batch rasterizer. When provided, the docx renderer coalesces a\n * document's visuals into batch calls (#153) instead of one `render` call\n * per visual. Like `render`, takes precedence over `serverUrl`.\n */\n renderBatch?: PptxBatchRasterizer;\n /**\n * HTTP rasterization service URL. The service receives\n * `{ presentation, dpi }` and returns a {@link PptxRasterizeResult}.\n */\n serverUrl?: string;\n /** Optional headers (or async resolver) for the HTTP service. */\n headers?: PptxServiceHeaders | PptxServiceHeadersResolver;\n /** Default DPI applied when a `visual` does not specify one. */\n dpi?: number;\n}\n\nexport interface ServicesConfig {\n highcharts?: HighchartsServiceConfig;\n pptx?: PptxServiceConfig;\n}\n","/**\n * Walk a document tree and collect every font family name referenced.\n *\n * Matches both DOCX conventions (`family` nested under `font`, theme.fonts.*)\n * and PPTX conventions (`fontFace`, chart `titleFontFace` etc., theme.fonts.*).\n * Key-name matching is intentionally permissive so future component schemas\n * that reuse the same conventions pick up automatically.\n */\n\nexport const FONT_NAME_KEYS = new Set([\n 'family',\n 'fontFace',\n 'titleFontFace',\n 'legendFontFace',\n 'dataLabelFontFace',\n 'catAxisLabelFontFace',\n 'valAxisLabelFontFace',\n]);\n\nexport const THEME_FONT_KEYS = new Set(['heading', 'body', 'mono', 'light']);\n\n/**\n * Subtrees that declare fonts rather than reference them. `fontRegistry`\n * entries carry `family` (and `sources[].family` for kind:'safe'/'google'),\n * which the depth-agnostic `family` match would otherwise scoop up as\n * references — a registry would self-satisfy its own validation, and a\n * `kind:'google'` source family would appear as a phantom reference.\n * substitute.ts's rewriter skips the same key; the two MUST stay in sync.\n */\nexport const FONT_DECLARATION_KEYS = new Set(['fontRegistry']);\n\nfunction collect(node: unknown, out: Set<string>, parentKey?: string): void {\n if (node == null) return;\n\n if (typeof node === 'string') {\n // String values under a font-name key or under theme.fonts.{heading,body,...}\n if (\n parentKey &&\n (FONT_NAME_KEYS.has(parentKey) || THEME_FONT_KEYS.has(parentKey))\n ) {\n const trimmed = node.trim();\n if (trimmed.length > 0) out.add(trimmed);\n }\n return;\n }\n\n if (Array.isArray(node)) {\n // Forward parentKey so font-name arrays (e.g. theme.fonts.heading: [...])\n // are walked with the same context as the non-array case. Mirrors\n // substitute.ts's rewrite walker — the two must stay in sync or\n // substitution silently misses array-shaped references.\n for (const item of node) collect(item, out, parentKey);\n return;\n }\n\n if (typeof node === 'object') {\n // Special-cased: theme.fonts is an object whose values (or values.family) are font names.\n const maybeFonts = (node as Record<string, unknown>).fonts;\n if (parentKey === 'theme' && maybeFonts && typeof maybeFonts === 'object') {\n for (const [k, v] of Object.entries(\n maybeFonts as Record<string, unknown>\n )) {\n if (typeof v === 'string') {\n const trimmed = v.trim();\n if (trimmed.length > 0) out.add(trimmed);\n } else if (v && typeof v === 'object') {\n const fam = (v as Record<string, unknown>).family;\n if (typeof fam === 'string' && fam.trim().length > 0) {\n out.add(fam.trim());\n }\n }\n void k;\n }\n }\n\n for (const [k, v] of Object.entries(node as Record<string, unknown>)) {\n // Declarations, not references — see FONT_DECLARATION_KEYS.\n if (FONT_DECLARATION_KEYS.has(k)) continue;\n collect(v, out, k);\n }\n }\n}\n\n/** Scan an arbitrary doc tree (DOCX or PPTX) for every font family referenced. */\nexport function collectFontNames(doc: unknown): Set<string> {\n const out = new Set<string>();\n collect(doc, out);\n return out;\n}\n\n/** Scan a DOCX document tree for every font family name referenced. */\nexport const collectFontNamesFromDocx = collectFontNames;\n\n/** Scan a PPTX presentation tree for every font family name referenced. */\nexport const collectFontNamesFromPptx = collectFontNames;\n","/**\n * Read the document-scoped and theme-scoped font registries and merge them\n * with runtime `fonts.extraEntries`.\n *\n * Precedence (last wins, matching registry.ts's documented resolution rules\n * and FontRuntimeOpts.extraEntries's \"merged over the document's\n * fontRegistry\"):\n *\n * theme.fontRegistry < document.props.fontRegistry < fonts.extraEntries\n *\n * Merging happens here rather than inside FontRegistry because\n * `validateFontReferences` needs the same merged list, and two merge sites\n * would eventually disagree — which would show up as a font that validates\n * but never renders, or vice versa.\n */\n\nimport type { FontRegistryEntry } from '../schemas/font-catalog';\n\nfunction isEntry(v: unknown): v is FontRegistryEntry {\n if (!v || typeof v !== 'object') return false;\n const e = v as Record<string, unknown>;\n return (\n typeof e.id === 'string' &&\n typeof e.family === 'string' &&\n Array.isArray(e.sources)\n );\n}\n\nfunction readAt(node: unknown, key: string): FontRegistryEntry[] {\n if (!node || typeof node !== 'object') return [];\n const raw = (node as Record<string, unknown>)[key];\n return Array.isArray(raw) ? raw.filter(isEntry) : [];\n}\n\n/** `document.props.fontRegistry`, defensively (props may be absent/null). */\nexport function documentFontRegistry(document: unknown): FontRegistryEntry[] {\n if (!document || typeof document !== 'object') return [];\n return readAt((document as Record<string, unknown>).props, 'fontRegistry');\n}\n\n/** `theme.fontRegistry`, defensively. */\nexport function themeFontRegistry(theme: unknown): FontRegistryEntry[] {\n return readAt(theme, 'fontRegistry');\n}\n\n/**\n * Merge entry groups in precedence order — later groups win on a collision of\n * `family` OR `id`, case-insensitively, which are the same two keys\n * `FontRegistry.addEntry` indexes on. Returns a flat list safe to hand to both\n * `validateFontReferences` and `new FontRegistry({ opts: { extraEntries } })`.\n */\nexport function mergeFontRegistries(\n ...groups: (FontRegistryEntry[] | undefined)[]\n): FontRegistryEntry[] {\n // Order IS the contract: `FontRegistry` replays this array through\n // `addEntry`, indexing on family and id with last-write-wins, so an entry\n // only outranks another by sitting later. Concatenating the groups in\n // precedence order is therefore the whole mechanism.\n //\n // Deliberately NOT de-duped through a Map keyed by family/id: `Map.set` on\n // an existing key keeps the original insertion *position*, so a\n // higher-precedence entry colliding with an earlier one would be emitted\n // early and then lose the replay to the entry it was supposed to beat.\n const out: FontRegistryEntry[] = [];\n for (const group of groups) {\n for (const entry of group ?? []) {\n const family = entry.family.toLowerCase();\n const id = entry.id.toLowerCase();\n // Drop only a true replacement — same family AND same id. An entry that\n // shares just one of the two still owns the other key in FontRegistry's\n // index, so removing it here would make that name unresolvable.\n for (let i = out.length - 1; i >= 0; i--) {\n if (\n out[i].family.toLowerCase() === family &&\n out[i].id.toLowerCase() === id\n ) {\n out.splice(i, 1);\n }\n }\n out.push(entry);\n }\n }\n return out;\n}\n","/**\n * Validate that every font name referenced in a document is either\n * in SAFE_FONTS or present in the document's fontRegistry / runtime overrides.\n *\n * Used at generate-start; emits warnings for unresolved names so pipelines\n * can surface them via their existing warning channels.\n */\n\nimport { SAFE_FONTS, isSafeFont } from '../schemas/font-catalog';\nimport type { FontRegistryEntry } from '../schemas/font-catalog';\n\n/**\n * Warning codes for font resolution + rendering.\n *\n * - `FONT_UNRESOLVED` — family not in SAFE_FONTS and not registered.\n * - `FONT_MODE_SUBSTITUTED` — non-safe families rewritten to safe equivalents.\n * - `FONT_MODE_CUSTOM` — export mode \"custom\" — refs kept as-is.\n */\nexport type FontIssueCode =\n | 'FONT_UNRESOLVED'\n | 'FONT_MODE_SUBSTITUTED'\n | 'FONT_MODE_CUSTOM';\n\nexport interface FontResolutionIssue {\n code: FontIssueCode;\n family: string;\n message: string;\n}\n\nexport interface FontValidationResult {\n /** Names that resolved via SAFE_FONTS or the registry. */\n resolved: string[];\n /** Names with no resolution path. */\n unresolved: string[];\n /** One warning per unresolved name. */\n warnings: FontResolutionIssue[];\n}\n\nexport interface FontValidationInput {\n /** Font names referenced in the document (from collectFontNamesFromDocx / FromPptx). */\n referencedNames: Iterable<string>;\n /** Runtime-registered entries (e.g. from FontRuntimeOpts.extraEntries). */\n registeredEntries?: FontRegistryEntry[];\n}\n\nfunction buildRegistryIndex(\n registeredEntries?: FontRegistryEntry[]\n): Set<string> {\n const idx = new Set<string>();\n for (const e of registeredEntries ?? []) {\n idx.add(e.family.toLowerCase());\n idx.add(e.id.toLowerCase());\n }\n return idx;\n}\n\n/**\n * Validate referenced font names against SAFE_FONTS + runtime-registered entries.\n * Does not perform network fetches — this runs purely off schema + opts content.\n */\nexport function validateFontReferences(\n input: FontValidationInput\n): FontValidationResult {\n const registryIdx = buildRegistryIndex(input.registeredEntries);\n const resolved: string[] = [];\n const unresolved: string[] = [];\n const warnings: FontResolutionIssue[] = [];\n\n for (const name of input.referencedNames) {\n if (isSafeFont(name) || registryIdx.has(name.toLowerCase())) {\n resolved.push(name);\n continue;\n }\n unresolved.push(name);\n warnings.push({\n code: 'FONT_UNRESOLVED',\n family: name,\n message:\n `Font \"${name}\" is not a SAFE_FONTS entry and is not registered via fonts.extraEntries. ` +\n `It will render with a host fallback on machines lacking the font. ` +\n `Safe fonts: ${SAFE_FONTS.join(', ')}.`,\n });\n }\n\n return { resolved, unresolved, warnings };\n}\n","/**\n * Map a (family, weight, italic) tuple to the pair of\n * `(familyName, { bold, italic })` the renderer should actually use.\n *\n * OOXML runs can only carry a bold/italic toggle, not a numeric weight.\n * For weights outside the RIBBI quad (400/700 × roman/italic), Word\n * resolves intermediate weights via **separate sub-family faces** whose\n * internal family name is the canonical Google-Fonts-style subfamily,\n * e.g. `Inter Light`, `Inter ExtraBold Italic`. Rewriting the run's\n * `family` to that synthetic name lets Word pick the right face when the\n * recipient has the full family installed, and lets the LibreOffice\n * preview resolve the matching staged TTF by its internal name.\n *\n * No embedding involved — this is purely a name transform applied at\n * render time. Safe fonts and unrecognised weights fall back to the\n * bold-only heuristic (`weight >= 600 → bold`).\n */\n\n/** Human-readable labels for the canonical font-weight numbers. */\nexport const WEIGHT_LABELS: Record<number, string> = {\n 100: 'Thin',\n 200: 'ExtraLight',\n 300: 'Light',\n 400: 'Regular',\n 500: 'Medium',\n 600: 'SemiBold',\n 700: 'Bold',\n 800: 'ExtraBold',\n 900: 'Black',\n};\n\nexport interface SynthesizedFamily {\n /** The family name to emit in `rFonts`/`fontFace`. */\n family: string;\n /** Whether to also set the run's bold toggle. */\n bold: boolean;\n /** Whether to also set the run's italic toggle. */\n italic: boolean;\n /**\n * `true` when the input `weight` was not one of the canonical\n * 100/200/.../900 labels. The canonical family name is returned with\n * a `weight >= 600 → bold` fallback, but the run will not match a\n * dedicated sub-family face — callers should surface this so authors\n * know the weight was effectively rounded to Regular or Bold.\n */\n nonCanonicalWeight: boolean;\n}\n\n/**\n * Translate `(family, weight, italic)` into the rendering-time family name\n * plus the bold/italic toggles to emit on the run.\n *\n * - RIBBI (weights 400 + 700, roman + italic) stays on the canonical family\n * name and uses native bold/italic toggles.\n * - Other canonical weights become `\"<Family> <Weight>\"` (e.g.\n * `\"Inter Light\"`) with bold/italic toggles cleared; any italic flag is\n * folded into the name (`\"Inter Light Italic\"`).\n * - Non-canonical weights (floating or out-of-range) fall back to\n * `bold = weight >= 600` and leave the family name untouched.\n */\nexport function synthesizeFamilyName(\n family: string,\n weight: number | undefined,\n italic: boolean\n): SynthesizedFamily {\n if (weight == null) {\n return { family, bold: false, italic, nonCanonicalWeight: false };\n }\n // RIBBI — no rewrite needed, let native bold/italic do the work.\n if (weight === 400) {\n return { family, bold: false, italic, nonCanonicalWeight: false };\n }\n if (weight === 700) {\n return { family, bold: true, italic, nonCanonicalWeight: false };\n }\n const label = WEIGHT_LABELS[weight];\n if (!label) {\n // Non-canonical weight — best-effort bold fallback. Flag the result so\n // callers can warn: the run will render as Regular or Bold, not the\n // intermediate weight the author asked for.\n return {\n family,\n bold: weight >= 600,\n italic,\n nonCanonicalWeight: true,\n };\n }\n const suffix = italic ? ` ${label} Italic` : ` ${label}`;\n return {\n family: `${family}${suffix}`,\n bold: false,\n italic: false,\n nonCanonicalWeight: false,\n };\n}\n","/**\n * Decode a base64 / data-URL font payload to a Buffer.\n */\n\nimport type { ResolvedFontSource } from '../types';\nimport { detectFontFormat } from './format';\n\nexport interface DataSourceInput {\n data: string;\n weight?: number;\n italic?: boolean;\n}\n\n/**\n * Hard upper bound on the decoded font buffer. Real TTF/OTF faces are well\n * under 5 MB; even variable-axis fonts with CJK coverage rarely exceed 4 MB.\n * Rejecting oversized payloads before decoding prevents a malicious\n * `kind: 'data'` registry entry from allocating arbitrary server memory\n * when the generator runs behind an HTTP endpoint.\n */\nconst MAX_DATA_FONT_BYTES = 5 * 1024 * 1024;\n\n/**\n * Accepts either a bare base64 string or a data: URL.\n * Throws on invalid input; renderer catches and emits a warning.\n */\nexport function loadDataFontSource(input: DataSourceInput): ResolvedFontSource {\n const raw = input.data.trim();\n let b64: string;\n if (raw.startsWith('data:')) {\n const comma = raw.indexOf(',');\n if (comma < 0) throw new Error('Invalid data URL: no payload separator');\n // Only base64-encoded payloads are supported.\n const header = raw.slice(5, comma);\n if (!header.includes(';base64')) {\n throw new Error('Data URL must be base64-encoded');\n }\n b64 = raw.slice(comma + 1);\n } else {\n b64 = raw;\n }\n // Upper-bound the decoded size via base64 length before allocating. A\n // base64 string decodes to ~3/4 its character count, so a conservative\n // check on the encoded size avoids decoding a 50 MB payload just to\n // reject it afterward.\n const approxDecodedBytes = Math.floor((b64.length * 3) / 4);\n if (approxDecodedBytes > MAX_DATA_FONT_BYTES) {\n throw new Error(\n `Font data payload exceeds ${MAX_DATA_FONT_BYTES} byte limit`\n );\n }\n const data = Buffer.from(b64, 'base64');\n if (data.length === 0) throw new Error('Decoded font buffer is empty');\n if (data.length > MAX_DATA_FONT_BYTES) {\n throw new Error(\n `Font data payload exceeds ${MAX_DATA_FONT_BYTES} byte limit`\n );\n }\n // Base64 decoding silently discards invalid characters, so garbage input\n // yields a non-empty buffer that isn't a real font. Magic-byte check\n // rejects the garbage before it reaches the LibreOffice preview stager.\n const format = detectFontFormat(data);\n if (format === 'unknown') {\n throw new Error(\n 'Decoded font buffer is not a recognized font (expected TTF/OTF/WOFF/WOFF2)'\n );\n }\n // WOFF/WOFF2 flow through: Office output never embeds bytes, and the\n // LibreOffice preview stager handles them via fontconfig.\n return {\n data,\n weight: input.weight ?? 400,\n italic: input.italic ?? false,\n format,\n };\n}\n","/**\n * Google Fonts fetcher.\n *\n * Hits the CSS API v2 with an older User-Agent that returns TTF (default UA\n * gets WOFF2, which Office cannot embed as-is). Parses the `src: url(...)` line\n * and downloads the binary.\n *\n * Uses memory + optional disk cache keyed by `${family}|${weight}|${italic}`.\n */\n\nimport type { ResolvedFontSource } from '../types';\nimport { FontMemoryCache } from '../cache/memory-cache';\nimport { detectFontFormat } from './format';\n\ninterface FontDiskCacheLike {\n get(key: string): Promise<Buffer | undefined>;\n set(key: string, value: Buffer): Promise<void>;\n}\n\nexport interface GoogleFetchOptions {\n family: string;\n weights: number[];\n italics?: boolean;\n memoryCache?: FontMemoryCache;\n diskCache?: FontDiskCacheLike;\n fetchTimeoutMs?: number;\n /** Override for tests. */\n fetcher?: typeof fetch;\n}\n\nexport interface GoogleFetchResult {\n sources: ResolvedFontSource[];\n warnings: string[];\n}\n\nconst TTF_UA = 'Mozilla/4.0';\n\nasync function fetchWithTimeout(\n url: string,\n opts: {\n headers?: Record<string, string>;\n timeoutMs?: number;\n fetcher?: typeof fetch;\n }\n): Promise<Response> {\n const ctrl = new AbortController();\n const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 5000);\n try {\n const f = opts.fetcher ?? fetch;\n return await f(url, { headers: opts.headers, signal: ctrl.signal });\n } finally {\n clearTimeout(timer);\n }\n}\n\nfunction buildCssUrl(\n family: string,\n weights: number[],\n italics: boolean\n): string {\n // URL-encode then restore spaces as `+` (Google's CSS2 API convention).\n const famPart = encodeURIComponent(family).replace(/%20/g, '+');\n const sortedWeights = [...weights].sort((a, b) => a - b);\n if (italics) {\n const axis = sortedWeights.flatMap((w) => [`0,${w}`, `1,${w}`]).join(';');\n return `https://fonts.googleapis.com/css2?family=${famPart}:ital,wght@${axis}&display=swap`;\n }\n const wghtPart = sortedWeights.join(';');\n return `https://fonts.googleapis.com/css2?family=${famPart}:wght@${wghtPart}&display=swap`;\n}\n\n/**\n * Parse Google Fonts CSS response into { weight, italic, ttfUrl } tuples.\n * Each @font-face block contains the src + font-weight + font-style we need.\n */\nfunction parseCssFaces(\n css: string\n): { weight: number; italic: boolean; ttfUrl: string }[] {\n const out: { weight: number; italic: boolean; ttfUrl: string }[] = [];\n const faceRe = /@font-face\\s*\\{([^}]*)\\}/g;\n let m: RegExpExecArray | null;\n while ((m = faceRe.exec(css)) !== null) {\n const block = m[1];\n // Pin the CDN: only accept URLs whose hostname is fonts.gstatic.com so a\n // hijacked/mitm'd CSS response can't redirect downloads to arbitrary hosts.\n const urlM = block.match(\n /src:\\s*url\\((https:\\/\\/fonts\\.gstatic\\.com\\/[^)]+\\.ttf)\\)/\n );\n if (!urlM) continue;\n const weightM = block.match(/font-weight:\\s*(\\d+)/);\n const italicM = block.match(/font-style:\\s*italic/);\n out.push({\n weight: weightM ? parseInt(weightM[1], 10) : 400,\n italic: Boolean(italicM),\n ttfUrl: urlM[1],\n });\n }\n return out;\n}\n\nfunction cacheKey(family: string, weight: number, italic: boolean): string {\n return `google|${family}|${weight}|${italic ? 'i' : 'r'}`;\n}\n\nexport async function fetchGoogleFontSources(\n opts: GoogleFetchOptions\n): Promise<GoogleFetchResult> {\n const weights = opts.weights?.length ? opts.weights : [400, 700];\n const italics = opts.italics ?? false;\n const warnings: string[] = [];\n const sources: ResolvedFontSource[] = [];\n\n // Try every (weight, italic) combo — first against caches, then via fetch.\n const wanted: { weight: number; italic: boolean }[] = [];\n for (const w of weights) {\n wanted.push({ weight: w, italic: false });\n if (italics) wanted.push({ weight: w, italic: true });\n }\n\n // Resolve any cache hits first.\n const pending: { weight: number; italic: boolean }[] = [];\n for (const w of wanted) {\n const key = cacheKey(opts.family, w.weight, w.italic);\n const mem = opts.memoryCache?.get(key);\n if (mem) {\n sources.push({\n data: mem,\n weight: w.weight,\n italic: w.italic,\n format: detectFontFormat(mem),\n });\n continue;\n }\n const disk = await opts.diskCache?.get(key);\n if (disk) {\n opts.memoryCache?.set(key, disk);\n sources.push({\n data: disk,\n weight: w.weight,\n italic: w.italic,\n format: detectFontFormat(disk),\n });\n continue;\n }\n pending.push(w);\n }\n\n if (pending.length === 0) {\n return { sources, warnings };\n }\n\n // Single CSS request covers all pending variants.\n const needItalics = pending.some((p) => p.italic);\n const cssUrl = buildCssUrl(\n opts.family,\n Array.from(new Set(pending.map((p) => p.weight))),\n needItalics\n );\n let faces: { weight: number; italic: boolean; ttfUrl: string }[];\n try {\n const cssRes = await fetchWithTimeout(cssUrl, {\n headers: { 'User-Agent': TTF_UA },\n timeoutMs: opts.fetchTimeoutMs,\n fetcher: opts.fetcher,\n });\n if (!cssRes.ok) {\n warnings.push(\n `Google Fonts CSS fetch for \"${opts.family}\" returned ${cssRes.status}`\n );\n return { sources, warnings };\n }\n const css = await cssRes.text();\n faces = parseCssFaces(css);\n } catch (err) {\n warnings.push(\n `Google Fonts CSS fetch for \"${opts.family}\" failed: ${\n (err as Error).message\n }`\n );\n return { sources, warnings };\n }\n\n for (const need of pending) {\n const match = faces.find(\n (f) => f.weight === need.weight && f.italic === need.italic\n );\n if (!match) {\n warnings.push(\n `Google Fonts \"${opts.family}\" missing weight ${need.weight}${\n need.italic ? ' italic' : ''\n }`\n );\n continue;\n }\n try {\n const res = await fetchWithTimeout(match.ttfUrl, {\n timeoutMs: opts.fetchTimeoutMs,\n fetcher: opts.fetcher,\n });\n if (!res.ok) {\n warnings.push(\n `Google Fonts TTF fetch for \"${opts.family}\" ${need.weight} returned ${res.status}`\n );\n continue;\n }\n const ab = await res.arrayBuffer();\n // Metadata validation (weight class, name-table defects, fsType) runs\n // centrally in FontRegistry.materializeEntry so file/data/url/google\n // sources are all checked under one code path.\n const buf = Buffer.from(ab);\n const key = cacheKey(opts.family, need.weight, need.italic);\n opts.memoryCache?.set(key, buf);\n await opts.diskCache?.set(key, buf);\n sources.push({\n data: buf,\n weight: need.weight,\n italic: need.italic,\n format: detectFontFormat(buf),\n });\n } catch (err) {\n warnings.push(\n `Google Fonts TTF fetch for \"${opts.family}\" ${need.weight} failed: ${\n (err as Error).message\n }`\n );\n }\n }\n\n return { sources, warnings };\n}\n","/**\n * Direct-URL font fetcher. Downloads a single TTF/OTF from an HTTPS URL and\n * returns it as a `ResolvedFontSource`. Cache-keyed the same way as the\n * Google Fonts fetcher so fetches are deduplicated across generations.\n *\n * Used as an escape hatch for families whose Google Fonts redistribution has\n * known defects (e.g. Inter's static Thin/ExtraLight shipping with a broken\n * `OS/2.usWeightClass`). The `UPSTREAM_OVERRIDES` catalog points affected\n * families at clean upstream sources like rsms/inter via jsDelivr.\n */\n\nimport type { ResolvedFontSource } from '../types';\nimport { detectFontFormat } from './format';\nimport { isAllowedFontUrl } from './url-allowlist';\n\nexport interface UrlFetchOptions {\n url: string;\n weight: number;\n italic: boolean;\n fetchTimeoutMs?: number;\n fetcher?: typeof fetch;\n memoryCache?: {\n get(key: string): Buffer | undefined;\n set(key: string, value: Buffer): void;\n };\n diskCache?: {\n get(key: string): Promise<Buffer | undefined>;\n set(key: string, value: Buffer): Promise<void>;\n };\n}\n\nfunction cacheKey(url: string, weight: number, italic: boolean): string {\n return `url|${url}|${weight}|${italic ? 'i' : 'r'}`;\n}\n\nexport async function fetchUrlFontSource(\n opts: UrlFetchOptions\n): Promise<{ source?: ResolvedFontSource; warnings?: string[] }> {\n if (!isAllowedFontUrl(opts.url)) {\n return {\n warnings: [\n `URL font fetch rejected (host not in allowlist or non-HTTPS): ${opts.url}`,\n ],\n };\n }\n const key = cacheKey(opts.url, opts.weight, opts.italic);\n const mem = opts.memoryCache?.get(key);\n if (mem) {\n return {\n source: {\n data: mem,\n weight: opts.weight,\n italic: opts.italic,\n format: detectFontFormat(mem),\n },\n warnings: [],\n };\n }\n const disk = await opts.diskCache?.get(key);\n if (disk) {\n opts.memoryCache?.set(key, disk);\n return {\n source: {\n data: disk,\n weight: opts.weight,\n italic: opts.italic,\n format: detectFontFormat(disk),\n },\n warnings: [],\n };\n }\n\n const ctrl = new AbortController();\n const timer = setTimeout(() => ctrl.abort(), opts.fetchTimeoutMs ?? 10000);\n try {\n const f = opts.fetcher ?? fetch;\n // redirect: 'manual' prevents the allowlist from being bypassed via\n // a 3xx Location pointing at an off-list host. We re-validate the\n // Location header against the allowlist before following.\n let res = await f(opts.url, { signal: ctrl.signal, redirect: 'manual' });\n let hops = 0;\n while (res.status >= 300 && res.status < 400 && res.status !== 304) {\n const next = res.headers.get('location');\n if (!next) {\n return {\n warnings: [\n `URL font fetch \"${opts.url}\" ${res.status} with no Location header`,\n ],\n };\n }\n const resolved = new URL(next, opts.url).toString();\n if (!isAllowedFontUrl(resolved)) {\n return {\n warnings: [\n `URL font fetch \"${opts.url}\" redirected to disallowed host: ${resolved}`,\n ],\n };\n }\n if (++hops > 3) {\n return {\n warnings: [`URL font fetch \"${opts.url}\" too many redirects`],\n };\n }\n res = await f(resolved, { signal: ctrl.signal, redirect: 'manual' });\n }\n if (!res.ok) {\n return {\n warnings: [`URL font fetch \"${opts.url}\" returned ${res.status}`],\n };\n }\n const ab = await res.arrayBuffer();\n const raw = Buffer.from(ab);\n // Reject obvious non-font responses — e.g. jsDelivr 404 HTML, 200 OK\n // redirect pages, or aliased directory listings. Without this check the\n // bytes would sail through to the embed step and corrupt the output.\n const format = detectFontFormat(raw);\n if (format === 'unknown' || raw.length < 512) {\n return {\n warnings: [\n `URL font fetch \"${opts.url}\" returned ${raw.length} bytes of ${format} — not a TTF/OTF. Skipping.`,\n ],\n };\n }\n // WOFF/WOFF2 flow through: Office output never embeds bytes; the\n // LibreOffice preview stager handles them via fontconfig.\n // Metadata validation runs centrally in FontRegistry.materializeEntry.\n const buf = raw;\n opts.memoryCache?.set(key, buf);\n await opts.diskCache?.set(key, buf);\n return {\n source: {\n data: buf,\n weight: opts.weight,\n italic: opts.italic,\n format,\n },\n warnings: [],\n };\n } catch (err) {\n return {\n warnings: [\n `URL font fetch \"${opts.url}\" failed: ${(err as Error).message}`,\n ],\n };\n } finally {\n clearTimeout(timer);\n }\n}\n","/**\n * Post-fetch metadata validation for TTF/OTF bytes. Catches the three known\n * classes of defects that surface in Google Fonts' redistribution pipeline:\n *\n * 1. Wrong `OS/2.usWeightClass` (Chivo Light, Mada Regular, Petrona)\n * 2. Duplicate usWeightClass across weights (Exo Thin/ExtraLight)\n * 3. Non-unique `name` subfamily records (Inter, Manrope, Recursive)\n *\n * We don't throw on mismatch — the pipeline has already tried to fix the\n * bytes where it can (`rewriteFontSubfamilyNames` after variable-font\n * instancing; `rewriteFontFamilyName` at preview staging). The validator\n * returns human-readable diagnostics so the caller can emit warnings tagged\n * `FONT_METADATA_DEFECT`, pointing users at the upstream override escape\n * hatch before they ship a broken document.\n */\n\nimport { standardSubfamilyNames } from './ttf-name';\n\nconst HEADER_SIZE = 12;\nconst TABLE_RECORD_SIZE = 16;\n\ninterface NameProbe {\n platformID: number;\n nameID: number;\n value: string;\n}\n\nfunction readTable(\n ttf: Buffer,\n tag: string\n): { off: number; len: number } | null {\n if (ttf.length < HEADER_SIZE) return null;\n const version = ttf.readUInt32BE(0);\n if (version !== 0x00010000 && version !== 0x4f54544f) return null;\n const numTables = ttf.readUInt16BE(4);\n for (let i = 0; i < numTables; i++) {\n const r = HEADER_SIZE + i * TABLE_RECORD_SIZE;\n if (r + TABLE_RECORD_SIZE > ttf.length) return null;\n if (ttf.toString('ascii', r, r + 4) === tag) {\n return { off: ttf.readUInt32BE(r + 8), len: ttf.readUInt32BE(r + 12) };\n }\n }\n return null;\n}\n\nfunction readUsWeightClass(ttf: Buffer): number | null {\n const os2 = readTable(ttf, 'OS/2');\n if (!os2) return null;\n if (os2.off + 6 > ttf.length) return null;\n return ttf.readUInt16BE(os2.off + 4);\n}\n\nfunction readNames(ttf: Buffer, wanted: Set<number>): NameProbe[] {\n const nt = readTable(ttf, 'name');\n if (!nt) return [];\n const tableOff = nt.off;\n // Name-table header is 6 bytes (format, count, stringOffset) before the\n // first name record. Reject obviously-truncated tables up front so we\n // don't read count/storageRel past the buffer's end on malformed fonts.\n if (tableOff + 6 > ttf.length) return [];\n const count = ttf.readUInt16BE(tableOff + 2);\n const storageRel = ttf.readUInt16BE(tableOff + 4);\n const storage = tableOff + storageRel;\n const out: NameProbe[] = [];\n for (let j = 0; j < count; j++) {\n const r = tableOff + 6 + j * 12;\n // Each name record is 12 bytes. Stop as soon as the claimed count\n // would walk past the buffer — a malformed font claiming count=999999\n // would otherwise read garbage on each iteration.\n if (r + 12 > ttf.length) break;\n const platformID = ttf.readUInt16BE(r);\n const nameID = ttf.readUInt16BE(r + 6);\n if (!wanted.has(nameID)) continue;\n const length = ttf.readUInt16BE(r + 8);\n const offset = ttf.readUInt16BE(r + 10);\n const raw = ttf.slice(storage + offset, storage + offset + length);\n let value: string;\n if (platformID === 1) {\n value = raw.toString('ascii');\n } else {\n // Decode UTF-16BE. Buffer has no direct utf16be support; swap bytes.\n const swapped = Buffer.from(raw);\n if (swapped.length % 2 === 0) swapped.swap16();\n value = swapped.toString('utf16le');\n }\n out.push({ platformID, nameID, value });\n }\n return out;\n}\n\nexport interface FontMetadataDiagnostic {\n code:\n | 'WEIGHT_CLASS_MISMATCH'\n | 'SUBFAMILY_MISMATCH'\n | 'LEGACY_SUBFAMILY_MISMATCH';\n message: string;\n}\n\n/**\n * Inspect a font's metadata against the weight + italic we asked it to\n * represent. Returns one diagnostic per detected defect.\n */\nexport function validateFontMetadata(\n ttf: Buffer,\n weight: number,\n italic: boolean,\n familyLabel: string\n): FontMetadataDiagnostic[] {\n const diags: FontMetadataDiagnostic[] = [];\n const usWeight = readUsWeightClass(ttf);\n if (usWeight != null && usWeight !== weight) {\n diags.push({\n code: 'WEIGHT_CLASS_MISMATCH',\n message: `Font \"${familyLabel}\" weight ${weight}: OS/2.usWeightClass reports ${usWeight}. Likely a defective redistribution — consider adding an upstream override.`,\n });\n }\n\n // OS/2.fsType (embedding-permission bits) deliberately NOT checked. Office\n // output never embeds font bytes anymore — substitute mode rewrites to\n // SAFE_FONTS, custom mode ships references as-is, and the LibreOffice\n // preview stager only registers bytes transiently with the converter's\n // child process. Permission warnings would be pure noise for every\n // Google Fonts resolution.\n\n const std = standardSubfamilyNames(weight, italic);\n if (!std) return diags;\n const expected17 = std.typographic;\n const expected2 = std.legacy;\n\n const names = readNames(ttf, new Set([2, 17]));\n for (const n of names) {\n if (n.nameID === 17 && n.value !== expected17) {\n diags.push({\n code: 'SUBFAMILY_MISMATCH',\n message: `Font \"${familyLabel}\" weight ${weight}${italic ? ' italic' : ''}: name record (platform ${n.platformID}) nameID 17 = \"${n.value}\", expected \"${expected17}\".`,\n });\n }\n if (n.nameID === 2 && n.value !== expected2) {\n diags.push({\n code: 'LEGACY_SUBFAMILY_MISMATCH',\n message: `Font \"${familyLabel}\" weight ${weight}${italic ? ' italic' : ''}: name record (platform ${n.platformID}) nameID 2 = \"${n.value}\", expected \"${expected2}\".`,\n });\n }\n }\n return diags;\n}\n","/**\n * In-process LRU cache for resolved font buffers.\n * Scoped to a single process — do not share across requests on a server.\n */\n\nexport interface MemoryCacheOptions {\n /** Approximate soft cap in bytes. LRU-evict when exceeded. */\n maxBytes?: number;\n}\n\nexport class FontMemoryCache {\n private readonly store = new Map<string, Buffer>();\n private bytes = 0;\n private readonly maxBytes: number;\n\n constructor(opts: MemoryCacheOptions = {}) {\n this.maxBytes = opts.maxBytes ?? 20 * 1024 * 1024; // 20 MB default\n }\n\n get(key: string): Buffer | undefined {\n const v = this.store.get(key);\n if (!v) return undefined;\n // Refresh LRU position.\n this.store.delete(key);\n this.store.set(key, v);\n return v;\n }\n\n set(key: string, value: Buffer): void {\n const existing = this.store.get(key);\n if (existing) this.bytes -= existing.byteLength;\n this.store.set(key, value);\n this.bytes += value.byteLength;\n while (this.bytes > this.maxBytes && this.store.size > 0) {\n const oldest = this.store.keys().next().value as string | undefined;\n if (!oldest) break;\n const removed = this.store.get(oldest);\n this.store.delete(oldest);\n if (removed) this.bytes -= removed.byteLength;\n }\n }\n\n size(): number {\n return this.store.size;\n }\n}\n","/**\n * FontRegistry — merges catalog + document registry + runtime entries\n * and materializes referenced fonts into ResolvedFont records.\n *\n * Resolution rules, per referenced name:\n * 1. Registry match (by family or id, case-insensitive). Runtime entries win\n * on collision with document entries. Materialize each source.\n * 2. SAFE_FONTS membership → empty sources.\n * 3. Otherwise → empty sources with FONT_UNRESOLVED warning.\n */\n\nimport { isSafeFont } from '../schemas/font-catalog';\nimport type { FontRegistryEntry, FontSource } from '../schemas/font-catalog';\nimport type {\n FontRuntimeOpts,\n ResolvedFont,\n ResolvedFontSource,\n} from './types';\nimport { loadDataFontSource } from './sources/data-loader';\nimport { fetchGoogleFontSources } from './sources/google-fetcher';\nimport { fetchUrlFontSource } from './sources/url-fetcher';\nimport { validateFontMetadata } from './sources/ttf-validate';\nimport { FontMemoryCache } from './cache/memory-cache';\n\n/**\n * Minimal interface the registry needs from a disk cache. The concrete\n * implementation ships in `./cache/disk-cache` but is Node-only (uses fs/crypto).\n * Callers on Node inject an instance; browser callers pass nothing.\n */\nexport interface FontDiskCacheLike {\n get(key: string): Promise<Buffer | undefined>;\n set(key: string, value: Buffer): Promise<void>;\n}\n\n/**\n * Minimal interface for a file-loader. Same reasoning as FontDiskCacheLike:\n * concrete impl is Node-only, callers inject when on Node.\n */\nexport type FontFileLoader = (input: {\n path: string;\n weight?: number;\n italic?: boolean;\n baseDir?: string;\n}) => Promise<ResolvedFontSource>;\n\n/**\n * Minimal interface for the variable-font fetcher. `subset-font` (the\n * harfbuzz-wasm wrapper we use for axis pinning) reaches for `fs` at\n * init time, which crashes in the browser. Injection keeps that import\n * behind the Node-only subpath; browser bundles never pull it in, and\n * browser callers simply won't see `kind: 'variable'` fonts resolved\n * (the registry warns and skips instead).\n */\nexport type FontVariableLoader = (input: {\n url: string;\n weight: number;\n italic: boolean;\n axes?: Record<string, number>;\n fetchTimeoutMs?: number;\n memoryCache?: {\n get(key: string): Buffer | undefined;\n set(key: string, value: Buffer): void;\n };\n diskCache?: {\n get(key: string): Promise<Buffer | undefined>;\n set(key: string, value: Buffer): Promise<void>;\n };\n}) => Promise<{ source?: ResolvedFontSource; warnings?: string[] }>;\n\nexport interface FontRegistryInput {\n /** Runtime options — entries come from opts.extraEntries. */\n opts?: FontRuntimeOpts;\n /** Optional disk cache (Node only). Pass an instance of FontDiskCache. */\n diskCache?: FontDiskCacheLike;\n /**\n * Optional `kind: \"file\"` loader (Node only). Inject `loadFileFontSource`\n * from `@json-to-office/shared/fonts/sources/file-loader` on Node. Browser\n * callers pass nothing; `kind: \"file\"` sources then warn and skip.\n */\n fileLoader?: FontFileLoader;\n /**\n * Optional `kind: \"variable\"` loader (Node only). Inject\n * `fetchVariableFontSource` from `@json-to-office/shared/fonts/node` on\n * Node. Browser callers pass nothing; `kind: \"variable\"` sources then\n * warn and skip. Keeping this injected avoids dragging subset-font (and\n * its `fs.promises.readFile` bootstrap) into client bundles.\n */\n variableLoader?: FontVariableLoader;\n}\n\nexport class FontRegistry {\n private readonly index: Map<string, FontRegistryEntry>;\n private readonly cache: Map<string, ResolvedFont>;\n private readonly opts: FontRuntimeOpts;\n private readonly memoryCache: FontMemoryCache;\n private readonly diskCache: FontDiskCacheLike | undefined;\n private readonly fileLoader: FontFileLoader | undefined;\n private readonly variableLoader: FontVariableLoader | undefined;\n\n constructor(input: FontRegistryInput = {}) {\n this.opts = input.opts ?? {};\n this.index = new Map();\n this.cache = new Map();\n this.memoryCache = new FontMemoryCache();\n this.diskCache = input.diskCache;\n this.fileLoader = input.fileLoader;\n this.variableLoader = input.variableLoader;\n\n for (const e of this.opts.extraEntries ?? []) this.addEntry(e);\n }\n\n private addEntry(entry: FontRegistryEntry): void {\n this.index.set(entry.family.toLowerCase(), entry);\n this.index.set(entry.id.toLowerCase(), entry);\n }\n\n /** Resolve every referenced name in one pass. Order preserved. */\n async resolveMany(names: Iterable<string>): Promise<ResolvedFont[]> {\n const out: ResolvedFont[] = [];\n for (const n of names) out.push(await this.resolve(n));\n return out;\n }\n\n async resolve(name: string): Promise<ResolvedFont> {\n const key = name.toLowerCase();\n const cached = this.cache.get(key);\n if (cached) return cached;\n\n const entry = this.index.get(key);\n let result: ResolvedFont;\n\n if (entry) {\n result = await this.materializeEntry(entry);\n } else if (isSafeFont(name)) {\n result = { family: name, sources: [], warnings: [] };\n } else {\n result = {\n family: name,\n sources: [],\n warnings: [\n `Font \"${name}\" is not registered and not in SAFE_FONTS; will rely on host fallback.`,\n ],\n };\n }\n this.cache.set(key, result);\n return result;\n }\n\n private async materializeEntry(\n entry: FontRegistryEntry\n ): Promise<ResolvedFont> {\n const sources: ResolvedFontSource[] = [];\n const warnings: string[] = [];\n\n for (const source of entry.sources) {\n try {\n const materialized = await this.materializeSource(source, warnings);\n for (const s of materialized) {\n if (s.format === 'ttf' || s.format === 'otf') {\n for (const d of validateFontMetadata(\n s.data,\n s.weight,\n s.italic,\n entry.family\n )) {\n warnings.push(`[FONT_METADATA_DEFECT:${d.code}] ${d.message}`);\n }\n }\n sources.push(s);\n }\n } catch (err) {\n warnings.push(\n `Font \"${entry.family}\" source (${source.kind}) failed: ${\n (err as Error).message\n }`\n );\n }\n }\n\n return {\n family: entry.family,\n sources,\n warnings,\n };\n }\n\n private async materializeSource(\n source: FontSource,\n warnings: string[]\n ): Promise<ResolvedFontSource[]> {\n switch (source.kind) {\n case 'safe':\n // System-installed; no embedding data.\n return [];\n case 'file': {\n if (!this.fileLoader) {\n warnings.push(\n `kind:\"file\" source for \"${source.path}\" requires a fileLoader (Node-only); skipping.`\n );\n return [];\n }\n return [\n await this.fileLoader({\n path: source.path,\n weight: source.weight,\n italic: source.italic,\n baseDir: this.opts.baseDir,\n }),\n ];\n }\n case 'data':\n return [\n loadDataFontSource({\n data: source.data,\n weight: source.weight,\n italic: source.italic,\n }),\n ];\n case 'google': {\n const gf = this.opts.googleFonts;\n if (gf?.enabled === false) {\n warnings.push(\n `Google Fonts fetch disabled — skipping \"${source.family}\".`\n );\n return [];\n }\n const { sources: fetched, warnings: fetchWarnings } =\n await fetchGoogleFontSources({\n family: source.family,\n weights: source.weights ?? [400, 700],\n italics: source.italics ?? false,\n memoryCache: this.memoryCache,\n diskCache: this.diskCache,\n fetchTimeoutMs: gf?.fetchTimeoutMs,\n });\n warnings.push(...fetchWarnings);\n return fetched;\n }\n case 'url': {\n const gf = this.opts.googleFonts;\n const { source: fetched, warnings: fetchWarnings } =\n await fetchUrlFontSource({\n url: source.url,\n weight: source.weight ?? 400,\n italic: source.italic ?? false,\n memoryCache: this.memoryCache,\n diskCache: this.diskCache,\n fetchTimeoutMs: gf?.fetchTimeoutMs,\n });\n if (fetchWarnings) warnings.push(...fetchWarnings);\n return fetched ? [fetched] : [];\n }\n case 'variable': {\n // Variable-font instancing: fetch the variable TTF once (disk-\n // cached), then harfbuzz-pin the `wght` axis to produce a clean\n // static for this weight. Requires a Node-injected loader because\n // `subset-font` pulls in `fs` at init; without it, browser\n // bundles would break. Callers on Node pass `fetchVariableFontSource`\n // from `@json-to-office/shared/fonts/node`.\n if (!this.variableLoader) {\n warnings.push(\n `kind:\"variable\" source for \"${source.url}\" requires a variableLoader (Node-only); skipping.`\n );\n return [];\n }\n const gf = this.opts.googleFonts;\n const { source: fetched, warnings: fetchWarnings } =\n await this.variableLoader({\n url: source.url,\n weight: source.weight,\n italic: source.italic ?? false,\n axes: source.axes,\n memoryCache: this.memoryCache,\n diskCache: this.diskCache,\n fetchTimeoutMs: gf?.fetchTimeoutMs,\n });\n if (fetchWarnings) warnings.push(...fetchWarnings);\n return fetched ? [fetched] : [];\n }\n default:\n // Exhaustiveness guard — new kind added to schema without handler\n throw new Error(\n `Unknown font source kind: ${(source as { kind: string }).kind}`\n );\n }\n }\n}\n","/**\n * Curated list of popular Google Fonts for picker autocomplete.\n *\n * Not exhaustive — the full Google Fonts library has ~1500 families.\n * This is ~37 names known to cover most real-world use cases.\n */\n\nexport interface PopularGoogleFont {\n family: string;\n category: 'sans' | 'serif' | 'mono' | 'display' | 'handwriting';\n /** Weights available on Google Fonts for this family. */\n weights: number[];\n /** Whether italic variants exist. */\n hasItalic: boolean;\n}\n\nexport const POPULAR_GOOGLE_FONTS: readonly PopularGoogleFont[] = [\n // Sans-serif\n {\n family: 'Inter',\n category: 'sans',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n hasItalic: false,\n },\n {\n family: 'Roboto',\n category: 'sans',\n weights: [100, 300, 400, 500, 700, 900],\n hasItalic: true,\n },\n {\n family: 'Open Sans',\n category: 'sans',\n weights: [300, 400, 500, 600, 700, 800],\n hasItalic: true,\n },\n {\n family: 'Lato',\n category: 'sans',\n weights: [100, 300, 400, 700, 900],\n hasItalic: true,\n },\n {\n family: 'Montserrat',\n category: 'sans',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n hasItalic: true,\n },\n {\n family: 'Poppins',\n category: 'sans',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n hasItalic: true,\n },\n {\n family: 'Work Sans',\n category: 'sans',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n hasItalic: true,\n },\n {\n family: 'Nunito',\n category: 'sans',\n weights: [200, 300, 400, 500, 600, 700, 800, 900],\n hasItalic: true,\n },\n {\n family: 'DM Sans',\n category: 'sans',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n hasItalic: true,\n },\n {\n family: 'Rubik',\n category: 'sans',\n weights: [300, 400, 500, 600, 700, 800, 900],\n hasItalic: true,\n },\n {\n family: 'Source Sans 3',\n category: 'sans',\n weights: [200, 300, 400, 500, 600, 700, 800, 900],\n hasItalic: true,\n },\n {\n family: 'Manrope',\n category: 'sans',\n weights: [200, 300, 400, 500, 600, 700, 800],\n hasItalic: false,\n },\n {\n family: 'Plus Jakarta Sans',\n category: 'sans',\n weights: [200, 300, 400, 500, 600, 700, 800],\n hasItalic: true,\n },\n {\n family: 'IBM Plex Sans',\n category: 'sans',\n weights: [100, 200, 300, 400, 500, 600, 700],\n hasItalic: true,\n },\n {\n family: 'Archivo',\n category: 'sans',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n hasItalic: true,\n },\n {\n family: 'Geist',\n category: 'sans',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n hasItalic: true,\n },\n {\n family: 'Space Grotesk',\n category: 'sans',\n weights: [300, 400, 500, 600, 700],\n hasItalic: false,\n },\n\n // Serif\n {\n family: 'Playfair Display',\n category: 'serif',\n weights: [400, 500, 600, 700, 800, 900],\n hasItalic: true,\n },\n {\n family: 'Merriweather',\n category: 'serif',\n weights: [300, 400, 700, 900],\n hasItalic: true,\n },\n {\n family: 'Lora',\n category: 'serif',\n weights: [400, 500, 600, 700],\n hasItalic: true,\n },\n {\n family: 'Source Serif 4',\n category: 'serif',\n weights: [200, 300, 400, 500, 600, 700, 800, 900],\n hasItalic: true,\n },\n {\n family: 'DM Serif Display',\n category: 'serif',\n weights: [400],\n hasItalic: true,\n },\n {\n family: 'Crimson Pro',\n category: 'serif',\n weights: [200, 300, 400, 500, 600, 700, 800, 900],\n hasItalic: true,\n },\n {\n family: 'Cormorant Garamond',\n category: 'serif',\n weights: [300, 400, 500, 600, 700],\n hasItalic: true,\n },\n\n // Monospace\n {\n family: 'JetBrains Mono',\n category: 'mono',\n weights: [100, 200, 300, 400, 500, 600, 700, 800],\n hasItalic: true,\n },\n {\n family: 'Fira Code',\n category: 'mono',\n weights: [300, 400, 500, 600, 700],\n hasItalic: false,\n },\n {\n family: 'IBM Plex Mono',\n category: 'mono',\n weights: [100, 200, 300, 400, 500, 600, 700],\n hasItalic: true,\n },\n {\n family: 'Source Code Pro',\n category: 'mono',\n weights: [200, 300, 400, 500, 600, 700, 800, 900],\n hasItalic: true,\n },\n {\n family: 'Space Mono',\n category: 'mono',\n weights: [400, 700],\n hasItalic: true,\n },\n {\n family: 'Geist Mono',\n category: 'mono',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n hasItalic: true,\n },\n\n // Display\n {\n family: 'Bebas Neue',\n category: 'display',\n weights: [400],\n hasItalic: false,\n },\n {\n family: 'Abril Fatface',\n category: 'display',\n weights: [400],\n hasItalic: false,\n },\n {\n family: 'Archivo Black',\n category: 'display',\n weights: [400],\n hasItalic: false,\n },\n {\n family: 'Oswald',\n category: 'display',\n weights: [200, 300, 400, 500, 600, 700],\n hasItalic: false,\n },\n\n // Handwriting\n {\n family: 'Caveat',\n category: 'handwriting',\n weights: [400, 500, 600, 700],\n hasItalic: false,\n },\n {\n family: 'Pacifico',\n category: 'handwriting',\n weights: [400],\n hasItalic: false,\n },\n];\n","/**\n * Per-family upstream overrides for popular Google Fonts whose\n * redistribution on fonts.google.com has known defects we can't fix via\n * metadata patching alone.\n *\n * When `autoGoogleFontEntries` hits a family present in this table, it\n * builds override sources instead of issuing `kind: \"google\"` CSS requests.\n * Each entry is either:\n *\n * - `{ kind: \"url\", url, weight, italic? }` — a direct HTTPS TTF/OTF.\n * Use when a clean per-weight static exists on a stable CDN.\n *\n * - `{ kind: \"variable\", url, weight, italic? }` — points at a variable\n * TTF with an `fvar` table. The registry downloads the variable font\n * once and harfbuzz-pins the `wght` axis to the specified weight,\n * producing a clean static TTF. Use when the upstream ships a variable\n * font but no per-weight statics on a CDN (rsms/inter is the\n * canonical example — variable font on jsDelivr, per-weight statics\n * only in GitHub release zips).\n *\n * Pick `variable` over `url` when both are available: the instancer\n * produces per-weight glyph outlines that diverge correctly at every\n * axis value. Google's static redistributions collapse adjacent weights\n * onto the same instance — Inter Thin and ExtraLight both source at\n * ~wght=250 in Google's pipeline, so their static TTFs have 98% identical\n * glyph outlines. Instancing the upstream variable font at exactly wght=100\n * vs wght=200 gives properly distinct geometry.\n *\n * Validate new entries with a HEAD request before adding — the fetchers\n * reject non-TTF responses, but a failed override silently falls back to\n * the Google path, defeating the purpose.\n */\n\n/** One upstream variant source. Type matches the FontSource schema so we\n * can pass the entry directly into `FontRegistry`'s materialize pipeline. */\nexport type UpstreamVariant =\n | {\n kind: 'url';\n url: string;\n weight: number;\n italic?: boolean;\n }\n | {\n kind: 'variable';\n url: string;\n weight: number;\n italic?: boolean;\n /** Extra axis pins merged on top of the derived `wght` pin. */\n axes?: Record<string, number>;\n };\n\nexport interface UpstreamOverride {\n /** Human-readable for logs/diagnostics only. */\n reason: string;\n variants: UpstreamVariant[];\n}\n\n/**\n * rsms/inter publishes the upright variable master as `InterVariable.ttf`\n * on jsDelivr, but the italic master ONLY as `InterVariable-Italic.woff2`\n * — no italic `.ttf` exists under `docs/font-files/` at any tag, so a\n * `.ttf` italic URL 404s and every italic Inter run silently falls back\n * to host defaults. The variable fetcher accepts woff2 sources (fontverter\n * converts to sfnt before instancing), so point at the woff2 directly.\n *\n * We instance each master at every advertised weight so Inter Thin (100),\n * ExtraLight (200), Light (300), Medium (500), SemiBold (600), ExtraBold\n * (800), and Black (900) come out with distinct glyph outlines instead of\n * the near-duplicates Google's static redistribution ships. Regular (400)\n * and Bold (700) from Google were already clean, but instancing them from\n * the same variable font keeps the full family visually consistent.\n *\n * Version pin: `@v4.1` — the last stable rsms/inter release at the time\n * of writing. jsDelivr caches the file aggressively; a version bump here\n * invalidates that cache for users on a subsequent generate.\n */\nconst INTER_VARIABLE_URL =\n 'https://cdn.jsdelivr.net/gh/rsms/inter@v4.1/docs/font-files/InterVariable.ttf';\nconst INTER_VARIABLE_ITALIC_URL =\n 'https://cdn.jsdelivr.net/gh/rsms/inter@v4.1/docs/font-files/InterVariable-Italic.woff2';\n\nfunction interVariants(): UpstreamVariant[] {\n const weights = [100, 200, 300, 400, 500, 600, 700, 800, 900];\n const upright = weights.map((weight) => ({\n kind: 'variable' as const,\n url: INTER_VARIABLE_URL,\n weight,\n italic: false,\n }));\n const italic = weights.map((weight) => ({\n kind: 'variable' as const,\n url: INTER_VARIABLE_ITALIC_URL,\n weight,\n italic: true,\n }));\n return [...upright, ...italic];\n}\n\nexport const UPSTREAM_OVERRIDES: Record<string, UpstreamOverride> = {\n inter: {\n reason:\n \"Google's static Inter Thin/ExtraLight both carry usWeightClass=250 and near-identical glyph outlines (xAvgCharWidth differs by 1.8%); instancing the upstream variable font per weight produces properly distinct statics.\",\n variants: interVariants(),\n },\n};\n\n/** Case-insensitive lookup. Returns undefined when the family has no override. */\nexport function getUpstreamOverride(\n family: string\n): UpstreamOverride | undefined {\n return UPSTREAM_OVERRIDES[family.toLowerCase()];\n}\n","/**\n * Font family substitution: rewrite every non-safe family reference in\n * the doc tree + theme to a SAFE_FONTS equivalent. Used by the\n * `'substitute'` export mode (`FontRuntimeOpts.mode`) so that non-safe\n * fonts (Playfair Display, Inter, …) ship as Georgia/Calibri and the\n * document renders identically on every recipient machine — no embed\n * bytes, no Word-for-Mac intermediate-weight surprises.\n *\n * The walker mirrors the shape used by `collectFontNamesFromDocx/Pptx`\n * so the two stay in sync: whatever `collect` scans, `rewrite` will\n * rewrite. Future component-schema additions that introduce new font\n * keys go in `FONT_NAME_KEYS` / `THEME_FONT_KEYS` once, both sides pick\n * them up.\n */\n\nimport { SAFE_FONTS, isSafeFont } from '../schemas/font-catalog';\nimport { POPULAR_GOOGLE_FONTS } from './catalog/popular-google';\nimport {\n FONT_NAME_KEYS,\n THEME_FONT_KEYS,\n FONT_DECLARATION_KEYS,\n} from './collect';\n\n/** One swap recorded during a rewrite. */\nexport interface FontSubstitution {\n from: string;\n to: string;\n}\n\nexport interface ApplyFontSubstitutionResult<T> {\n doc: T;\n substitutions: FontSubstitution[];\n}\n\n/**\n * Walk a doc tree + swap every non-safe family reference per `mapping`.\n * Returns a new tree (structural clone) plus the list of `(from, to)`\n * swaps made, deduped by source name.\n *\n * One deliberate exception to the clone: `fontRegistry` subtrees are carried\n * through by reference, since they declare fonts rather than reference them\n * and nothing downstream mutates them.\n *\n * Families already in SAFE_FONTS are never rewritten (even if a mapping\n * entry targets them as a key — safe fonts don't need substitution).\n * Families with no mapping entry are left untouched — callers should\n * feed the result of `buildDefaultSubstitutionMap` to ensure every\n * non-safe reference gets a fallback.\n */\nexport function applyFontSubstitution<T>(\n doc: T,\n mapping: Record<string, string>\n): ApplyFontSubstitutionResult<T> {\n const seen = new Map<string, string>();\n const rewritten = rewrite(doc, mapping, seen) as T;\n const substitutions: FontSubstitution[] = [];\n for (const [from, to] of seen) substitutions.push({ from, to });\n return { doc: rewritten, substitutions };\n}\n\nfunction rewrite(\n node: unknown,\n mapping: Record<string, string>,\n seen: Map<string, string>,\n parentKey?: string\n): unknown {\n if (node == null) return node;\n\n if (typeof node === 'string') {\n if (\n parentKey &&\n (FONT_NAME_KEYS.has(parentKey) || THEME_FONT_KEYS.has(parentKey))\n ) {\n return maybeSwap(node, mapping, seen);\n }\n return node;\n }\n\n if (Array.isArray(node)) {\n return node.map((item) => rewrite(item, mapping, seen, parentKey));\n }\n\n if (typeof node === 'object') {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(node as Record<string, unknown>)) {\n // Mirror of collect.ts: a fontRegistry subtree declares families, it\n // does not reference them. Rewriting `entry.family` would rename the\n // registration out from under every reference and key an entry on a\n // SAFE_FONTS name. Carry it through by reference (see the clone note\n // on applyFontSubstitution).\n if (FONT_DECLARATION_KEYS.has(k)) {\n out[k] = v;\n continue;\n }\n // Parallel to collect.ts: `theme.fonts` may hold plain strings\n // keyed by heading/body/mono/light. Those strings are font names\n // and need swapping just like `font.family` does.\n if (\n parentKey === 'theme' &&\n k === 'fonts' &&\n v &&\n typeof v === 'object'\n ) {\n const nextFonts: Record<string, unknown> = {};\n for (const [fk, fv] of Object.entries(v as Record<string, unknown>)) {\n if (typeof fv === 'string') {\n nextFonts[fk] = maybeSwap(fv, mapping, seen);\n } else if (fv && typeof fv === 'object') {\n const fam = (fv as Record<string, unknown>).family;\n nextFonts[fk] =\n typeof fam === 'string'\n ? { ...(fv as object), family: maybeSwap(fam, mapping, seen) }\n : rewrite(fv, mapping, seen, fk);\n } else {\n nextFonts[fk] = fv;\n }\n }\n out[k] = nextFonts;\n continue;\n }\n out[k] = rewrite(v, mapping, seen, k);\n }\n return out;\n }\n\n return node;\n}\n\nfunction maybeSwap(\n name: string,\n mapping: Record<string, string>,\n seen: Map<string, string>\n): string {\n const trimmed = name.trim();\n if (trimmed.length === 0) return name;\n if (isSafeFont(trimmed)) return name;\n // Case-insensitive mapping lookup. Users may feed mappings with\n // slightly different casing than the doc's reference (e.g. \"inter\" vs\n // \"Inter\") — honour the target verbatim, don't force case.\n const lowered = trimmed.toLowerCase();\n for (const [from, to] of Object.entries(mapping)) {\n if (from.toLowerCase() === lowered) {\n seen.set(trimmed, to);\n return to;\n }\n }\n return name;\n}\n\n// ---------------------------------------------------------------------------\n// Default-mapping builder\n// ---------------------------------------------------------------------------\n\n/**\n * Explicit overrides for the most common non-safe fonts we see. Chosen\n * for visual similarity: sans serifs map to Calibri, serifs to Georgia\n * or Cambria depending on axis proportion, monospace to Consolas.\n * Extendable as real usage surfaces more families.\n */\nconst EXPLICIT_OVERRIDES: Record<string, string> = {\n Inter: 'Calibri',\n Roboto: 'Calibri',\n 'Open Sans': 'Calibri',\n Lato: 'Calibri',\n 'Source Sans 3': 'Calibri',\n 'Source Sans Pro': 'Calibri',\n 'IBM Plex Sans': 'Calibri',\n Archivo: 'Calibri',\n Geist: 'Calibri',\n 'Space Grotesk': 'Calibri',\n 'Work Sans': 'Calibri',\n Manrope: 'Calibri',\n Nunito: 'Calibri',\n 'Nunito Sans': 'Calibri',\n Poppins: 'Calibri',\n Montserrat: 'Calibri',\n 'Playfair Display': 'Georgia',\n Merriweather: 'Georgia',\n 'Source Serif 4': 'Cambria',\n 'Source Serif Pro': 'Cambria',\n 'IBM Plex Serif': 'Cambria',\n 'Crimson Pro': 'Cambria',\n Lora: 'Georgia',\n 'PT Serif': 'Georgia',\n 'Cormorant Garamond': 'Cambria',\n 'JetBrains Mono': 'Consolas',\n 'Fira Code': 'Consolas',\n 'IBM Plex Mono': 'Consolas',\n 'Source Code Pro': 'Consolas',\n 'Roboto Mono': 'Consolas',\n 'Geist Mono': 'Consolas',\n};\n\nconst CATEGORY_FALLBACK: Record<string, string> = {\n sans: 'Calibri',\n serif: 'Georgia',\n mono: 'Consolas',\n display: 'Georgia',\n handwriting: 'Segoe UI',\n};\n\n/**\n * Pick the safe-font fallback for a single non-safe family. Precedence:\n * 1. Explicit override in `EXPLICIT_OVERRIDES`.\n * 2. Category lookup in `POPULAR_GOOGLE_FONTS`.\n * 3. Final default (`Calibri`).\n *\n * Exposed for the playground dialog so it can pre-populate the per-family\n * picker with the same defaults the CLI would apply.\n */\nexport function defaultSubstituteFor(family: string): string {\n const trimmed = family.trim();\n // Explicit (case-insensitive).\n for (const [from, to] of Object.entries(EXPLICIT_OVERRIDES)) {\n if (from.toLowerCase() === trimmed.toLowerCase()) return to;\n }\n // Category.\n const catalog = POPULAR_GOOGLE_FONTS.find(\n (f) => f.family.toLowerCase() === trimmed.toLowerCase()\n );\n if (catalog) {\n const cat = CATEGORY_FALLBACK[catalog.category];\n if (cat) return cat;\n }\n return 'Calibri';\n}\n\n/**\n * Build a substitution map for every non-safe family in `referencedNames`.\n * Safe fonts are omitted from the result since they don't need swapping.\n * Caller can override individual entries before passing to\n * `applyFontSubstitution`.\n */\nexport function buildDefaultSubstitutionMap(\n referencedNames: Iterable<string>\n): Record<string, string> {\n const out: Record<string, string> = {};\n for (const raw of referencedNames) {\n const name = raw.trim();\n if (name.length === 0) continue;\n if (isSafeFont(name)) continue;\n if (out[name]) continue;\n out[name] = defaultSubstituteFor(name);\n }\n return out;\n}\n\n/** Re-export SAFE_FONTS for dialog/CLI consumers that need the allowlist. */\nexport { SAFE_FONTS };\n\n// ---------------------------------------------------------------------------\n// Export-mode pre-pass\n// ---------------------------------------------------------------------------\n\nimport type { FontRuntimeOpts } from './types';\nimport { collectFontNames } from './collect';\n\nexport interface ApplyExportModeInput<D, T> {\n doc: D;\n theme: T;\n fonts?: FontRuntimeOpts;\n}\n\nexport interface ApplyExportModeWarning {\n code: 'FONT_MODE_CUSTOM' | 'FONT_MODE_SUBSTITUTED';\n message: string;\n}\n\nexport interface ApplyExportModeResult<D, T> {\n doc: D;\n theme: T;\n warnings: ApplyExportModeWarning[];\n}\n\n/**\n * Inspect `fonts.mode` and apply the pre-resolution rewrite for the\n * requested mode.\n *\n * - `'custom'` (default) — no rewrite. Font references stay as authored;\n * recipients need the font installed or Word falls back. The\n * LibreOffice preview stager registers resolved bytes so preview\n * fidelity matches the recipient-side experience when the font is\n * installed.\n * - `'substitute'` — rewrite every non-safe family in doc + theme to its\n * mapped safe equivalent. Fills in defaults via\n * `buildDefaultSubstitutionMap` for any non-safe reference not present\n * in `fonts.substitution`. Emits one `FONT_MODE_SUBSTITUTED` warning\n * listing every swap.\n */\nexport function applyExportMode<D, T>(\n input: ApplyExportModeInput<D, T>\n): ApplyExportModeResult<D, T> {\n const mode = input.fonts?.mode ?? 'custom';\n if (mode === 'custom') {\n // Suppress the advisory entirely when callers never passed a `fonts`\n // option: they opted out of font-mode handling, so noisy per-run\n // warnings would flood existing callers that predate the pipeline.\n if (!input.fonts) {\n return { doc: input.doc, theme: input.theme, warnings: [] };\n }\n // Only emit the advisory when the doc/theme actually references a\n // non-safe family — a safe-only doc has nothing for recipients to be\n // missing, and the warning would be noise.\n const referenced = new Set<string>([\n ...collectFontNames(input.doc),\n ...collectFontNames(input.theme),\n ]);\n const nonSafe = [...referenced].filter((name) => !isSafeFont(name.trim()));\n const warnings: ApplyExportModeWarning[] =\n nonSafe.length > 0\n ? [\n {\n code: 'FONT_MODE_CUSTOM',\n message: `Export mode \"custom\": non-safe font references (${nonSafe.join(', ')}) kept as-is. Recipients need these fonts installed locally; Word falls back to a generic substitute otherwise.`,\n },\n ]\n : [];\n return {\n doc: input.doc,\n theme: input.theme,\n warnings,\n };\n }\n // mode === 'substitute'\n const referenced = new Set<string>([\n ...collectFontNames(input.doc),\n ...collectFontNames(input.theme),\n ]);\n const defaults = buildDefaultSubstitutionMap(referenced);\n const mapping: Record<string, string> = {\n ...defaults,\n ...(input.fonts?.substitution ?? {}),\n };\n\n const docRewrite = applyFontSubstitution(input.doc, mapping);\n const themeRewrite = applyFontSubstitution(input.theme, mapping);\n const combined = new Map<string, string>();\n for (const s of docRewrite.substitutions) combined.set(s.from, s.to);\n for (const s of themeRewrite.substitutions) combined.set(s.from, s.to);\n\n const warnings: ApplyExportModeWarning[] = [];\n if (combined.size > 0) {\n const list = [...combined]\n .map(([from, to]) => `${from} → ${to}`)\n .join(', ');\n warnings.push({\n code: 'FONT_MODE_SUBSTITUTED',\n message: `Export mode \"substitute\": rewrote non-safe families to safe equivalents — ${list}. No fonts embedded; document renders identically on every machine.`,\n });\n }\n return {\n doc: docRewrite.doc,\n theme: themeRewrite.doc,\n warnings,\n };\n}\n","/**\n * Default series-color tokens for charts. Single source of truth for every\n * format: the PPTX `chart` and `highcharts` components and the DOCX\n * `highcharts` component all resolve this list, in this order, against the\n * active theme when the author sets no explicit colors. Both theme schemas\n * declare all six tokens (accent4-6 optional in each), so a theme that fills\n * every slot produces the same palette in a deck and in a document.\n *\n * Slots the theme leaves unset are skipped in both formats: the implicit\n * palette shrinks and the chart library cycles the shorter list rather than\n * repeating `primary` for every empty slot. A theme carrying only\n * primary/secondary/accent — which is what the bundled DOCX themes carry —\n * therefore paints series 4+ identically in a deck and in a document.\n *\n * Skipping compacts holes: a theme defining accent5 but not accent4 yields\n * [primary, secondary, accent, accent5], so accent5 paints series 4. The list\n * is a preference-ordered pool of candidate colors, not fixed per-series slots,\n * so keeping a color the theme did define beats dropping or duplicating one.\n *\n * A slot may also hold another token's name (`\"accent4\": \"primary\"`) — both\n * theme schemas allow it — and both formats walk that reference to hex before\n * using it, so a chained slot lands on the same color in a deck as in a\n * document. A slot whose value reaches no hex (`\"accent4\": \"nonsense\"`, or a\n * reference cycle) is dropped from the implicit palette in both formats rather\n * than emitted verbatim: PowerPoint and Highcharts both answer an unparseable\n * color with silent black. Parity here covers the token names the two schemas\n * share; each format also has private color keys (DOCX `textSecondary`, PPTX\n * `text2`) that only resolve in their own format.\n *\n * Only the implicit palette skips. An author who names a token explicitly\n * (PPTX `chartColors: ['accent4']`) still gets the `primary` fallback and a\n * warning — naming an undefined token is an authoring error and stays loud.\n * PPTX warns THEME_COLOR_FALLBACK for an unset slot and UNKNOWN_COLOR for one\n * holding an unresolvable value; DOCX throws.\n */\nexport const DEFAULT_CHART_THEME_COLORS = [\n 'primary',\n 'secondary',\n 'accent',\n 'accent4',\n 'accent5',\n 'accent6',\n];\n","/**\n * Deep Merge Utilities\n * Generic deep-merge helpers used by both docx and pptx\n * componentDefaults resolution systems.\n */\n\nfunction isObject(item: any): boolean {\n return item !== null && typeof item === 'object' && !Array.isArray(item);\n}\n\nfunction deepMerge<T>(target: any, source: any): T {\n const output = { ...target };\n\n if (isObject(target) && isObject(source)) {\n Object.keys(source).forEach((key) => {\n if (isObject(source[key])) {\n if (!(key in target)) {\n output[key] = source[key];\n } else {\n output[key] = deepMerge(target[key], source[key]);\n }\n } else {\n output[key] = source[key];\n }\n });\n }\n\n return output as T;\n}\n\n/**\n * Merge theme defaults with user-provided configuration.\n * User config takes precedence over theme defaults.\n * Uses deep merge to preserve nested objects.\n * Arrays are replaced wholesale, not merged per-element.\n */\nexport function mergeWithDefaults<T>(\n userConfig: T,\n themeDefaults: Partial<T>\n): T {\n return deepMerge<T>(themeDefaults, userConfig);\n}\n"],"mappings":"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","names":["cacheKey","referenced","warnings"]}
1
+ {"version":3,"sources":["../src/types/services.ts","../src/fonts/collect.ts","../src/fonts/document-registry.ts","../src/fonts/validator.ts","../src/fonts/synthesize.ts","../src/fonts/sources/data-loader.ts","../src/fonts/sources/google-fetcher.ts","../src/fonts/sources/url-fetcher.ts","../src/fonts/sources/ttf-validate.ts","../src/fonts/cache/memory-cache.ts","../src/fonts/registry.ts","../src/fonts/catalog/popular-google.ts","../src/fonts/catalog/upstream-overrides.ts","../src/fonts/substitute.ts","../src/theme/chart-palette.ts","../src/utils/deepMerge.ts"],"sourcesContent":["/**\n * Service configuration types for external integrations (e.g. Highcharts export server)\n */\n\n// ============================================================================\n// Visual rasterization policy — single source of truth for DPI bounds shared\n// by the visual schema, the in-process rasterizer, the flatten transform, and\n// both HTTP /rasterize surfaces. Keep these in sync in one place.\n// ============================================================================\n\n/** Default raster resolution when a `visual` does not specify one. */\nexport const DEFAULT_VISUAL_DPI = 200;\n/** Minimum accepted raster resolution. */\nexport const MIN_VISUAL_DPI = 36;\n/** Maximum accepted raster resolution (bounds bitmap size / DoS surface). */\nexport const MAX_VISUAL_DPI = 600;\n\n/** Clamp an arbitrary dpi to [MIN_VISUAL_DPI, MAX_VISUAL_DPI]; non-finite → default. */\nexport function clampVisualDpi(dpi: unknown): number {\n if (typeof dpi !== 'number' || !Number.isFinite(dpi))\n return DEFAULT_VISUAL_DPI;\n return Math.min(MAX_VISUAL_DPI, Math.max(MIN_VISUAL_DPI, Math.round(dpi)));\n}\n\nexport type HighchartsHeaders = Record<string, string>;\n\nexport type HighchartsHeadersResolver = (\n body: unknown\n) => HighchartsHeaders | Promise<HighchartsHeaders>;\n\nexport interface HighchartsServiceConfig {\n serverUrl?: string;\n headers?: HighchartsHeaders | HighchartsHeadersResolver;\n}\n\n// ============================================================================\n// PPTX rasterization service (used by the docx `visual` component)\n// ============================================================================\n\nexport type PptxServiceHeaders = Record<string, string>;\n\nexport type PptxServiceHeadersResolver = (\n body: unknown\n) => PptxServiceHeaders | Promise<PptxServiceHeaders>;\n\n/** Max font faces accepted in one rasterize request. */\nexport const MAX_RASTERIZE_FONTS = 32;\n/** Max total DECODED font bytes accepted in one rasterize request. */\nexport const MAX_RASTERIZE_FONT_BYTES = 8 * 1024 * 1024;\n\n/**\n * One font face shipped alongside a rasterize request so the rasterizer's\n * out-of-process LibreOffice can render the slide with the document's real\n * fonts instead of a system fallback.\n *\n * `data` is base64 of the raw font file — NO `data:` URI prefix — so the\n * request stays plain serializable JSON.\n *\n * `family` is the CATALOG family (`ResolvedFont.family`, e.g. \"Inter\"), not\n * the synthesized sub-family the presentation references. The receiving\n * stager applies `synthesizeFamilyName` + `rewriteFontFamilyName` itself,\n * exactly as it does for the PDF-preview path; pre-synthesizing here would\n * double-apply the suffix (\"Inter Light Light\").\n */\nexport interface RasterizeFontFace {\n family: string;\n weight: number;\n italic: boolean;\n /** Base64-encoded font file bytes (no `data:` prefix). */\n data: string;\n format?: 'ttf' | 'otf' | 'woff' | 'woff2';\n}\n\n/**\n * Request handed to a pptx rasterizer: a single-slide pptx presentation\n * component definition plus the target resolution.\n */\nexport interface PptxRasterizeRequest {\n /** A pptx presentation component definition ({ name: 'pptx', ... }) with one slide */\n presentation: unknown;\n /** Target raster resolution in dots-per-inch */\n dpi: number;\n /**\n * Directory that relative asset paths inside the presentation resolve\n * against — the originating document's own directory. Absent → the\n * rasterizer's cwd, the legacy behavior (#142).\n */\n baseDir?: string;\n /**\n * Font faces to stage for the rasterizer's LibreOffice launch. Absent →\n * system fonts only, which is what every non-font-aware caller (and every\n * pre-Area-6 client) sends.\n */\n fonts?: RasterizeFontFace[];\n}\n\n/**\n * Result returned by a pptx rasterizer.\n */\nexport interface PptxRasterizeResult {\n /** Rendered PNG as a base64 data URI (data:image/png;base64,...) */\n base64DataUri: string;\n /** Natural pixel width of the rendered image */\n width: number;\n /** Natural pixel height of the rendered image */\n height: number;\n}\n\n/**\n * In-process rasterizer callback. Implementations build the .pptx from the\n * presentation JSON and rasterize it to a PNG (e.g. via LibreOffice + poppler).\n */\nexport type PptxRasterizer = (\n request: PptxRasterizeRequest\n) => Promise<PptxRasterizeResult>;\n\n// ============================================================================\n// Batch rasterization (#153) — one request rasterizes many independent slides.\n//\n// Each slide is a complete single-slide presentation (the exact shape a\n// single {@link PptxRasterizeRequest} carries), NOT a slide fragment of one\n// merged deck. This keeps slides independent — each may use its own canvas\n// size, theme, and dpi, so callers never need to group visuals — and lets\n// implementations key per-slide caches identically to the single-slide path.\n// ============================================================================\n\n/**\n * Maximum slides accepted in one batch request. Shared by the HTTP surface\n * (request validation) and clients (chunk size) so the two cannot drift.\n * Bounds per-request work and response size the same way rate limits bound\n * request counts.\n */\nexport const MAX_RASTERIZE_BATCH_SLIDES = 32;\n\n/** One slide in a batch: a single-slide presentation plus its resolution. */\nexport interface PptxRasterizeBatchSlide {\n /** A pptx presentation component definition ({ name: 'pptx', ... }) with one slide */\n presentation: unknown;\n /** Target raster resolution in dots-per-inch (absent → service default) */\n dpi?: number;\n}\n\n/** Request handed to a batch pptx rasterizer. */\nexport interface PptxRasterizeBatchRequest {\n /** Slides to rasterize; results come back index-aligned with this array. */\n slides: PptxRasterizeBatchSlide[];\n /** Base directory for relative asset paths, shared by every slide (#142). */\n baseDir?: string;\n /**\n * Font faces staged for the batch's LibreOffice launch, shared by every\n * slide exactly like `baseDir`. Deliberately REQUEST-level and never\n * per-slide: {@link PptxRasterizeBatchSlide} stays `{presentation, dpi}` so\n * batch-internal dedupe and the per-slide disk-cache key stay uniform with\n * the single-slide path.\n */\n fonts?: RasterizeFontFace[];\n}\n\n/**\n * Pipeline stage a slide failed in. `build` failures are caused by the\n * slide's own JSON (safe to surface verbatim to callers); `convert` and\n * `rasterize` failures are environment/tooling errors whose raw messages may\n * carry host paths — HTTP surfaces sanitize those.\n */\nexport type PptxRasterizeFailureStage = 'build' | 'convert' | 'rasterize';\n\n/**\n * Per-slide outcome. A batch response is 200-with-item-errors rather than\n * all-or-nothing: one bad visual must not discard its siblings' pixels.\n */\nexport type PptxRasterizeBatchSlideResult =\n | ({ ok: true } & PptxRasterizeResult)\n | { ok: false; error: string; stage?: PptxRasterizeFailureStage };\n\n/** Result returned by a batch pptx rasterizer. */\nexport interface PptxRasterizeBatchResult {\n /** Index-aligned with the request's `slides` (same length, same order). */\n results: PptxRasterizeBatchSlideResult[];\n}\n\n/**\n * In-process batch rasterizer callback. Batch-level failures (missing\n * binaries, bad request) throw; per-slide failures land in `results`.\n */\nexport type PptxBatchRasterizer = (\n request: PptxRasterizeBatchRequest\n) => Promise<PptxRasterizeBatchResult>;\n\n/**\n * Configuration for the pptx rasterization service backing `visual` components.\n *\n * Mirrors {@link HighchartsServiceConfig}: the published packages depend on this\n * interface, never on a binary. A host injects either an in-process `render`\n * callback or an HTTP `serverUrl`.\n */\nexport interface PptxServiceConfig {\n /**\n * In-process rasterizer. Takes precedence over `serverUrl` when provided.\n * Ideal for tests (no binaries) and single-process hosts.\n */\n render?: PptxRasterizer;\n /**\n * In-process batch rasterizer. When provided, the docx renderer coalesces a\n * document's visuals into batch calls (#153) instead of one `render` call\n * per visual. Like `render`, takes precedence over `serverUrl`.\n */\n renderBatch?: PptxBatchRasterizer;\n /**\n * HTTP rasterization service URL. The service receives\n * `{ presentation, dpi }` and returns a {@link PptxRasterizeResult}.\n */\n serverUrl?: string;\n /** Optional headers (or async resolver) for the HTTP service. */\n headers?: PptxServiceHeaders | PptxServiceHeadersResolver;\n /** Default DPI applied when a `visual` does not specify one. */\n dpi?: number;\n}\n\nexport interface ServicesConfig {\n highcharts?: HighchartsServiceConfig;\n pptx?: PptxServiceConfig;\n}\n","/**\n * Walk a document tree and collect every font family name referenced.\n *\n * Matches both DOCX conventions (`family` nested under `font`, theme.fonts.*)\n * and PPTX conventions (`fontFace`, chart `titleFontFace` etc., theme.fonts.*).\n * Key-name matching is intentionally permissive so future component schemas\n * that reuse the same conventions pick up automatically.\n */\n\nexport const FONT_NAME_KEYS = new Set([\n 'family',\n 'fontFace',\n 'titleFontFace',\n 'legendFontFace',\n 'dataLabelFontFace',\n 'catAxisLabelFontFace',\n 'valAxisLabelFontFace',\n]);\n\nexport const THEME_FONT_KEYS = new Set(['heading', 'body', 'mono', 'light']);\n\n/**\n * Subtrees that declare fonts rather than reference them. `fontRegistry`\n * entries carry `family` (and `sources[].family` for kind:'safe'/'google'),\n * which the depth-agnostic `family` match would otherwise scoop up as\n * references — a registry would self-satisfy its own validation, and a\n * `kind:'google'` source family would appear as a phantom reference.\n * substitute.ts's rewriter skips the same key; the two MUST stay in sync.\n */\nexport const FONT_DECLARATION_KEYS = new Set(['fontRegistry']);\n\nfunction collect(node: unknown, out: Set<string>, parentKey?: string): void {\n if (node == null) return;\n\n if (typeof node === 'string') {\n // String values under a font-name key or under theme.fonts.{heading,body,...}\n if (\n parentKey &&\n (FONT_NAME_KEYS.has(parentKey) || THEME_FONT_KEYS.has(parentKey))\n ) {\n const trimmed = node.trim();\n if (trimmed.length > 0) out.add(trimmed);\n }\n return;\n }\n\n if (Array.isArray(node)) {\n // Forward parentKey so font-name arrays (e.g. theme.fonts.heading: [...])\n // are walked with the same context as the non-array case. Mirrors\n // substitute.ts's rewrite walker — the two must stay in sync or\n // substitution silently misses array-shaped references.\n for (const item of node) collect(item, out, parentKey);\n return;\n }\n\n if (typeof node === 'object') {\n // Special-cased: theme.fonts is an object whose values (or values.family) are font names.\n const maybeFonts = (node as Record<string, unknown>).fonts;\n if (parentKey === 'theme' && maybeFonts && typeof maybeFonts === 'object') {\n for (const [k, v] of Object.entries(\n maybeFonts as Record<string, unknown>\n )) {\n if (typeof v === 'string') {\n const trimmed = v.trim();\n if (trimmed.length > 0) out.add(trimmed);\n } else if (v && typeof v === 'object') {\n const fam = (v as Record<string, unknown>).family;\n if (typeof fam === 'string' && fam.trim().length > 0) {\n out.add(fam.trim());\n }\n }\n void k;\n }\n }\n\n for (const [k, v] of Object.entries(node as Record<string, unknown>)) {\n // Declarations, not references — see FONT_DECLARATION_KEYS.\n if (FONT_DECLARATION_KEYS.has(k)) continue;\n collect(v, out, k);\n }\n }\n}\n\n/** Scan an arbitrary doc tree (DOCX or PPTX) for every font family referenced. */\nexport function collectFontNames(doc: unknown): Set<string> {\n const out = new Set<string>();\n collect(doc, out);\n return out;\n}\n\n/** Scan a DOCX document tree for every font family name referenced. */\nexport const collectFontNamesFromDocx = collectFontNames;\n\n/** Scan a PPTX presentation tree for every font family name referenced. */\nexport const collectFontNamesFromPptx = collectFontNames;\n","/**\n * Read the document-scoped and theme-scoped font registries and merge them\n * with runtime `fonts.extraEntries`.\n *\n * Precedence (last wins, matching registry.ts's documented resolution rules\n * and FontRuntimeOpts.extraEntries's \"merged over the document's\n * fontRegistry\"):\n *\n * theme.fontRegistry < document.props.fontRegistry < fonts.extraEntries\n *\n * Merging happens here rather than inside FontRegistry because\n * `validateFontReferences` needs the same merged list, and two merge sites\n * would eventually disagree — which would show up as a font that validates\n * but never renders, or vice versa.\n */\n\nimport type { FontRegistryEntry } from '../schemas/font-catalog';\n\nfunction isEntry(v: unknown): v is FontRegistryEntry {\n if (!v || typeof v !== 'object') return false;\n const e = v as Record<string, unknown>;\n return (\n typeof e.id === 'string' &&\n typeof e.family === 'string' &&\n Array.isArray(e.sources)\n );\n}\n\nfunction readAt(node: unknown, key: string): FontRegistryEntry[] {\n if (!node || typeof node !== 'object') return [];\n const raw = (node as Record<string, unknown>)[key];\n return Array.isArray(raw) ? raw.filter(isEntry) : [];\n}\n\n/** `document.props.fontRegistry`, defensively (props may be absent/null). */\nexport function documentFontRegistry(document: unknown): FontRegistryEntry[] {\n if (!document || typeof document !== 'object') return [];\n return readAt((document as Record<string, unknown>).props, 'fontRegistry');\n}\n\n/** `theme.fontRegistry`, defensively. */\nexport function themeFontRegistry(theme: unknown): FontRegistryEntry[] {\n return readAt(theme, 'fontRegistry');\n}\n\n/**\n * Merge entry groups in precedence order — later groups win on a collision of\n * `family` OR `id`, case-insensitively, which are the same two keys\n * `FontRegistry.addEntry` indexes on. Returns a flat list safe to hand to both\n * `validateFontReferences` and `new FontRegistry({ opts: { extraEntries } })`.\n */\nexport function mergeFontRegistries(\n ...groups: (FontRegistryEntry[] | undefined)[]\n): FontRegistryEntry[] {\n // Order IS the contract: `FontRegistry` replays this array through\n // `addEntry`, indexing on family and id with last-write-wins, so an entry\n // only outranks another by sitting later. Concatenating the groups in\n // precedence order is therefore the whole mechanism.\n //\n // Deliberately NOT de-duped through a Map keyed by family/id: `Map.set` on\n // an existing key keeps the original insertion *position*, so a\n // higher-precedence entry colliding with an earlier one would be emitted\n // early and then lose the replay to the entry it was supposed to beat.\n const out: FontRegistryEntry[] = [];\n for (const group of groups) {\n for (const entry of group ?? []) {\n const family = entry.family.toLowerCase();\n const id = entry.id.toLowerCase();\n // Drop only a true replacement — same family AND same id. An entry that\n // shares just one of the two still owns the other key in FontRegistry's\n // index, so removing it here would make that name unresolvable.\n for (let i = out.length - 1; i >= 0; i--) {\n if (\n out[i].family.toLowerCase() === family &&\n out[i].id.toLowerCase() === id\n ) {\n out.splice(i, 1);\n }\n }\n out.push(entry);\n }\n }\n return out;\n}\n","/**\n * Validate that every font name referenced in a document is either\n * in SAFE_FONTS or present in the document's fontRegistry / runtime overrides.\n *\n * Used at generate-start; emits warnings for unresolved names so pipelines\n * can surface them via their existing warning channels.\n */\n\nimport { SAFE_FONTS, isSafeFont } from '../schemas/font-catalog';\nimport type { FontRegistryEntry } from '../schemas/font-catalog';\n\n/**\n * Warning codes for font resolution + rendering.\n *\n * - `FONT_UNRESOLVED` — family not in SAFE_FONTS and not registered.\n * - `FONT_MODE_SUBSTITUTED` — non-safe families rewritten to safe equivalents.\n * - `FONT_MODE_CUSTOM` — export mode \"custom\" — refs kept as-is.\n */\nexport type FontIssueCode =\n | 'FONT_UNRESOLVED'\n | 'FONT_MODE_SUBSTITUTED'\n | 'FONT_MODE_CUSTOM';\n\nexport interface FontResolutionIssue {\n code: FontIssueCode;\n family: string;\n message: string;\n}\n\nexport interface FontValidationResult {\n /** Names that resolved via SAFE_FONTS or the registry. */\n resolved: string[];\n /** Names with no resolution path. */\n unresolved: string[];\n /** One warning per unresolved name. */\n warnings: FontResolutionIssue[];\n}\n\nexport interface FontValidationInput {\n /** Font names referenced in the document (from collectFontNamesFromDocx / FromPptx). */\n referencedNames: Iterable<string>;\n /** Runtime-registered entries (e.g. from FontRuntimeOpts.extraEntries). */\n registeredEntries?: FontRegistryEntry[];\n}\n\nfunction buildRegistryIndex(\n registeredEntries?: FontRegistryEntry[]\n): Set<string> {\n const idx = new Set<string>();\n for (const e of registeredEntries ?? []) {\n idx.add(e.family.toLowerCase());\n idx.add(e.id.toLowerCase());\n }\n return idx;\n}\n\n/**\n * Validate referenced font names against SAFE_FONTS + runtime-registered entries.\n * Does not perform network fetches — this runs purely off schema + opts content.\n */\nexport function validateFontReferences(\n input: FontValidationInput\n): FontValidationResult {\n const registryIdx = buildRegistryIndex(input.registeredEntries);\n const resolved: string[] = [];\n const unresolved: string[] = [];\n const warnings: FontResolutionIssue[] = [];\n\n for (const name of input.referencedNames) {\n if (isSafeFont(name) || registryIdx.has(name.toLowerCase())) {\n resolved.push(name);\n continue;\n }\n unresolved.push(name);\n warnings.push({\n code: 'FONT_UNRESOLVED',\n family: name,\n message:\n `Font \"${name}\" is not a SAFE_FONTS entry and is not registered via fonts.extraEntries. ` +\n `It will render with a host fallback on machines lacking the font. ` +\n `Safe fonts: ${SAFE_FONTS.join(', ')}.`,\n });\n }\n\n return { resolved, unresolved, warnings };\n}\n","/**\n * Map a (family, weight, italic) tuple to the pair of\n * `(familyName, { bold, italic })` the renderer should actually use.\n *\n * OOXML runs can only carry a bold/italic toggle, not a numeric weight.\n * For weights outside the RIBBI quad (400/700 × roman/italic), Word\n * resolves intermediate weights via **separate sub-family faces** whose\n * internal family name is the canonical Google-Fonts-style subfamily,\n * e.g. `Inter Light`, `Inter ExtraBold Italic`. Rewriting the run's\n * `family` to that synthetic name lets Word pick the right face when the\n * recipient has the full family installed, and lets the LibreOffice\n * preview resolve the matching staged TTF by its internal name.\n *\n * No embedding involved — this is purely a name transform applied at\n * render time. Safe fonts and unrecognised weights fall back to the\n * bold-only heuristic (`weight >= 600 → bold`).\n */\n\n/** Human-readable labels for the canonical font-weight numbers. */\nexport const WEIGHT_LABELS: Record<number, string> = {\n 100: 'Thin',\n 200: 'ExtraLight',\n 300: 'Light',\n 400: 'Regular',\n 500: 'Medium',\n 600: 'SemiBold',\n 700: 'Bold',\n 800: 'ExtraBold',\n 900: 'Black',\n};\n\nexport interface SynthesizedFamily {\n /** The family name to emit in `rFonts`/`fontFace`. */\n family: string;\n /** Whether to also set the run's bold toggle. */\n bold: boolean;\n /** Whether to also set the run's italic toggle. */\n italic: boolean;\n /**\n * `true` when the input `weight` was not one of the canonical\n * 100/200/.../900 labels. The canonical family name is returned with\n * a `weight >= 600 → bold` fallback, but the run will not match a\n * dedicated sub-family face — callers should surface this so authors\n * know the weight was effectively rounded to Regular or Bold.\n */\n nonCanonicalWeight: boolean;\n}\n\n/**\n * Translate `(family, weight, italic)` into the rendering-time family name\n * plus the bold/italic toggles to emit on the run.\n *\n * - RIBBI (weights 400 + 700, roman + italic) stays on the canonical family\n * name and uses native bold/italic toggles.\n * - Other canonical weights become `\"<Family> <Weight>\"` (e.g.\n * `\"Inter Light\"`) with bold/italic toggles cleared; any italic flag is\n * folded into the name (`\"Inter Light Italic\"`).\n * - Non-canonical weights (floating or out-of-range) fall back to\n * `bold = weight >= 600` and leave the family name untouched.\n */\nexport function synthesizeFamilyName(\n family: string,\n weight: number | undefined,\n italic: boolean\n): SynthesizedFamily {\n if (weight == null) {\n return { family, bold: false, italic, nonCanonicalWeight: false };\n }\n // RIBBI — no rewrite needed, let native bold/italic do the work.\n if (weight === 400) {\n return { family, bold: false, italic, nonCanonicalWeight: false };\n }\n if (weight === 700) {\n return { family, bold: true, italic, nonCanonicalWeight: false };\n }\n const label = WEIGHT_LABELS[weight];\n if (!label) {\n // Non-canonical weight — best-effort bold fallback. Flag the result so\n // callers can warn: the run will render as Regular or Bold, not the\n // intermediate weight the author asked for.\n return {\n family,\n bold: weight >= 600,\n italic,\n nonCanonicalWeight: true,\n };\n }\n const suffix = italic ? ` ${label} Italic` : ` ${label}`;\n return {\n family: `${family}${suffix}`,\n bold: false,\n italic: false,\n nonCanonicalWeight: false,\n };\n}\n","/**\n * Decode a base64 / data-URL font payload to a Buffer.\n */\n\nimport type { ResolvedFontSource } from '../types';\nimport { detectFontFormat } from './format';\n\nexport interface DataSourceInput {\n data: string;\n weight?: number;\n italic?: boolean;\n}\n\n/**\n * Hard upper bound on the decoded font buffer. Real TTF/OTF faces are well\n * under 5 MB; even variable-axis fonts with CJK coverage rarely exceed 4 MB.\n * Rejecting oversized payloads before decoding prevents a malicious\n * `kind: 'data'` registry entry from allocating arbitrary server memory\n * when the generator runs behind an HTTP endpoint.\n */\nconst MAX_DATA_FONT_BYTES = 5 * 1024 * 1024;\n\n/**\n * Accepts either a bare base64 string or a data: URL.\n * Throws on invalid input; renderer catches and emits a warning.\n */\nexport function loadDataFontSource(input: DataSourceInput): ResolvedFontSource {\n const raw = input.data.trim();\n let b64: string;\n if (raw.startsWith('data:')) {\n const comma = raw.indexOf(',');\n if (comma < 0) throw new Error('Invalid data URL: no payload separator');\n // Only base64-encoded payloads are supported.\n const header = raw.slice(5, comma);\n if (!header.includes(';base64')) {\n throw new Error('Data URL must be base64-encoded');\n }\n b64 = raw.slice(comma + 1);\n } else {\n b64 = raw;\n }\n // Upper-bound the decoded size via base64 length before allocating. A\n // base64 string decodes to ~3/4 its character count, so a conservative\n // check on the encoded size avoids decoding a 50 MB payload just to\n // reject it afterward.\n const approxDecodedBytes = Math.floor((b64.length * 3) / 4);\n if (approxDecodedBytes > MAX_DATA_FONT_BYTES) {\n throw new Error(\n `Font data payload exceeds ${MAX_DATA_FONT_BYTES} byte limit`\n );\n }\n const data = Buffer.from(b64, 'base64');\n if (data.length === 0) throw new Error('Decoded font buffer is empty');\n if (data.length > MAX_DATA_FONT_BYTES) {\n throw new Error(\n `Font data payload exceeds ${MAX_DATA_FONT_BYTES} byte limit`\n );\n }\n // Base64 decoding silently discards invalid characters, so garbage input\n // yields a non-empty buffer that isn't a real font. Magic-byte check\n // rejects the garbage before it reaches the LibreOffice preview stager.\n const format = detectFontFormat(data);\n if (format === 'unknown') {\n throw new Error(\n 'Decoded font buffer is not a recognized font (expected TTF/OTF/WOFF/WOFF2)'\n );\n }\n // WOFF/WOFF2 flow through: Office output never embeds bytes, and the\n // LibreOffice preview stager handles them via fontconfig.\n return {\n data,\n weight: input.weight ?? 400,\n italic: input.italic ?? false,\n format,\n };\n}\n","/**\n * Google Fonts fetcher.\n *\n * Hits the CSS API v2 with an older User-Agent that returns TTF (default UA\n * gets WOFF2, which Office cannot embed as-is). Parses the `src: url(...)` line\n * and downloads the binary.\n *\n * Uses memory + optional disk cache keyed by `${family}|${weight}|${italic}`.\n */\n\nimport type { ResolvedFontSource } from '../types';\nimport { FontMemoryCache } from '../cache/memory-cache';\nimport { detectFontFormat } from './format';\n\ninterface FontDiskCacheLike {\n get(key: string): Promise<Buffer | undefined>;\n set(key: string, value: Buffer): Promise<void>;\n}\n\nexport interface GoogleFetchOptions {\n family: string;\n weights: number[];\n italics?: boolean;\n memoryCache?: FontMemoryCache;\n diskCache?: FontDiskCacheLike;\n fetchTimeoutMs?: number;\n /** Override for tests. */\n fetcher?: typeof fetch;\n}\n\nexport interface GoogleFetchResult {\n sources: ResolvedFontSource[];\n warnings: string[];\n}\n\nconst TTF_UA = 'Mozilla/4.0';\n\nasync function fetchWithTimeout(\n url: string,\n opts: {\n headers?: Record<string, string>;\n timeoutMs?: number;\n fetcher?: typeof fetch;\n }\n): Promise<Response> {\n const ctrl = new AbortController();\n const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 5000);\n try {\n const f = opts.fetcher ?? fetch;\n return await f(url, { headers: opts.headers, signal: ctrl.signal });\n } finally {\n clearTimeout(timer);\n }\n}\n\nfunction buildCssUrl(\n family: string,\n weights: number[],\n italics: boolean\n): string {\n // URL-encode then restore spaces as `+` (Google's CSS2 API convention).\n const famPart = encodeURIComponent(family).replace(/%20/g, '+');\n const sortedWeights = [...weights].sort((a, b) => a - b);\n if (italics) {\n const axis = sortedWeights.flatMap((w) => [`0,${w}`, `1,${w}`]).join(';');\n return `https://fonts.googleapis.com/css2?family=${famPart}:ital,wght@${axis}&display=swap`;\n }\n const wghtPart = sortedWeights.join(';');\n return `https://fonts.googleapis.com/css2?family=${famPart}:wght@${wghtPart}&display=swap`;\n}\n\n/**\n * Parse Google Fonts CSS response into { weight, italic, ttfUrl } tuples.\n * Each @font-face block contains the src + font-weight + font-style we need.\n */\nfunction parseCssFaces(\n css: string\n): { weight: number; italic: boolean; ttfUrl: string }[] {\n const out: { weight: number; italic: boolean; ttfUrl: string }[] = [];\n const faceRe = /@font-face\\s*\\{([^}]*)\\}/g;\n let m: RegExpExecArray | null;\n while ((m = faceRe.exec(css)) !== null) {\n const block = m[1];\n // Pin the CDN: only accept URLs whose hostname is fonts.gstatic.com so a\n // hijacked/mitm'd CSS response can't redirect downloads to arbitrary hosts.\n const urlM = block.match(\n /src:\\s*url\\((https:\\/\\/fonts\\.gstatic\\.com\\/[^)]+\\.ttf)\\)/\n );\n if (!urlM) continue;\n const weightM = block.match(/font-weight:\\s*(\\d+)/);\n const italicM = block.match(/font-style:\\s*italic/);\n out.push({\n weight: weightM ? parseInt(weightM[1], 10) : 400,\n italic: Boolean(italicM),\n ttfUrl: urlM[1],\n });\n }\n return out;\n}\n\nfunction cacheKey(family: string, weight: number, italic: boolean): string {\n return `google|${family}|${weight}|${italic ? 'i' : 'r'}`;\n}\n\nexport async function fetchGoogleFontSources(\n opts: GoogleFetchOptions\n): Promise<GoogleFetchResult> {\n const weights = opts.weights?.length ? opts.weights : [400, 700];\n const italics = opts.italics ?? false;\n const warnings: string[] = [];\n const sources: ResolvedFontSource[] = [];\n\n // Try every (weight, italic) combo — first against caches, then via fetch.\n const wanted: { weight: number; italic: boolean }[] = [];\n for (const w of weights) {\n wanted.push({ weight: w, italic: false });\n if (italics) wanted.push({ weight: w, italic: true });\n }\n\n // Resolve any cache hits first.\n const pending: { weight: number; italic: boolean }[] = [];\n for (const w of wanted) {\n const key = cacheKey(opts.family, w.weight, w.italic);\n const mem = opts.memoryCache?.get(key);\n if (mem) {\n sources.push({\n data: mem,\n weight: w.weight,\n italic: w.italic,\n format: detectFontFormat(mem),\n });\n continue;\n }\n const disk = await opts.diskCache?.get(key);\n if (disk) {\n opts.memoryCache?.set(key, disk);\n sources.push({\n data: disk,\n weight: w.weight,\n italic: w.italic,\n format: detectFontFormat(disk),\n });\n continue;\n }\n pending.push(w);\n }\n\n if (pending.length === 0) {\n return { sources, warnings };\n }\n\n // Single CSS request covers all pending variants.\n const needItalics = pending.some((p) => p.italic);\n const cssUrl = buildCssUrl(\n opts.family,\n Array.from(new Set(pending.map((p) => p.weight))),\n needItalics\n );\n let faces: { weight: number; italic: boolean; ttfUrl: string }[];\n try {\n const cssRes = await fetchWithTimeout(cssUrl, {\n headers: { 'User-Agent': TTF_UA },\n timeoutMs: opts.fetchTimeoutMs,\n fetcher: opts.fetcher,\n });\n if (!cssRes.ok) {\n warnings.push(\n `Google Fonts CSS fetch for \"${opts.family}\" returned ${cssRes.status}`\n );\n return { sources, warnings };\n }\n const css = await cssRes.text();\n faces = parseCssFaces(css);\n } catch (err) {\n warnings.push(\n `Google Fonts CSS fetch for \"${opts.family}\" failed: ${\n (err as Error).message\n }`\n );\n return { sources, warnings };\n }\n\n for (const need of pending) {\n const match = faces.find(\n (f) => f.weight === need.weight && f.italic === need.italic\n );\n if (!match) {\n warnings.push(\n `Google Fonts \"${opts.family}\" missing weight ${need.weight}${\n need.italic ? ' italic' : ''\n }`\n );\n continue;\n }\n try {\n const res = await fetchWithTimeout(match.ttfUrl, {\n timeoutMs: opts.fetchTimeoutMs,\n fetcher: opts.fetcher,\n });\n if (!res.ok) {\n warnings.push(\n `Google Fonts TTF fetch for \"${opts.family}\" ${need.weight} returned ${res.status}`\n );\n continue;\n }\n const ab = await res.arrayBuffer();\n // Metadata validation (weight class, name-table defects, fsType) runs\n // centrally in FontRegistry.materializeEntry so file/data/url/google\n // sources are all checked under one code path.\n const buf = Buffer.from(ab);\n const key = cacheKey(opts.family, need.weight, need.italic);\n opts.memoryCache?.set(key, buf);\n await opts.diskCache?.set(key, buf);\n sources.push({\n data: buf,\n weight: need.weight,\n italic: need.italic,\n format: detectFontFormat(buf),\n });\n } catch (err) {\n warnings.push(\n `Google Fonts TTF fetch for \"${opts.family}\" ${need.weight} failed: ${\n (err as Error).message\n }`\n );\n }\n }\n\n return { sources, warnings };\n}\n","/**\n * Direct-URL font fetcher. Downloads a single TTF/OTF from an HTTPS URL and\n * returns it as a `ResolvedFontSource`. Cache-keyed the same way as the\n * Google Fonts fetcher so fetches are deduplicated across generations.\n *\n * Used as an escape hatch for families whose Google Fonts redistribution has\n * known defects (e.g. Inter's static Thin/ExtraLight shipping with a broken\n * `OS/2.usWeightClass`). The `UPSTREAM_OVERRIDES` catalog points affected\n * families at clean upstream sources like rsms/inter via jsDelivr.\n */\n\nimport type { ResolvedFontSource } from '../types';\nimport { detectFontFormat } from './format';\nimport { isAllowedFontUrl } from './url-allowlist';\n\nexport interface UrlFetchOptions {\n url: string;\n weight: number;\n italic: boolean;\n fetchTimeoutMs?: number;\n fetcher?: typeof fetch;\n memoryCache?: {\n get(key: string): Buffer | undefined;\n set(key: string, value: Buffer): void;\n };\n diskCache?: {\n get(key: string): Promise<Buffer | undefined>;\n set(key: string, value: Buffer): Promise<void>;\n };\n}\n\nfunction cacheKey(url: string, weight: number, italic: boolean): string {\n return `url|${url}|${weight}|${italic ? 'i' : 'r'}`;\n}\n\nexport async function fetchUrlFontSource(\n opts: UrlFetchOptions\n): Promise<{ source?: ResolvedFontSource; warnings?: string[] }> {\n if (!isAllowedFontUrl(opts.url)) {\n return {\n warnings: [\n `URL font fetch rejected (host not in allowlist or non-HTTPS): ${opts.url}`,\n ],\n };\n }\n const key = cacheKey(opts.url, opts.weight, opts.italic);\n const mem = opts.memoryCache?.get(key);\n if (mem) {\n return {\n source: {\n data: mem,\n weight: opts.weight,\n italic: opts.italic,\n format: detectFontFormat(mem),\n },\n warnings: [],\n };\n }\n const disk = await opts.diskCache?.get(key);\n if (disk) {\n opts.memoryCache?.set(key, disk);\n return {\n source: {\n data: disk,\n weight: opts.weight,\n italic: opts.italic,\n format: detectFontFormat(disk),\n },\n warnings: [],\n };\n }\n\n const ctrl = new AbortController();\n const timer = setTimeout(() => ctrl.abort(), opts.fetchTimeoutMs ?? 10000);\n try {\n const f = opts.fetcher ?? fetch;\n // redirect: 'manual' prevents the allowlist from being bypassed via\n // a 3xx Location pointing at an off-list host. We re-validate the\n // Location header against the allowlist before following.\n let res = await f(opts.url, { signal: ctrl.signal, redirect: 'manual' });\n let hops = 0;\n while (res.status >= 300 && res.status < 400 && res.status !== 304) {\n const next = res.headers.get('location');\n if (!next) {\n return {\n warnings: [\n `URL font fetch \"${opts.url}\" ${res.status} with no Location header`,\n ],\n };\n }\n const resolved = new URL(next, opts.url).toString();\n if (!isAllowedFontUrl(resolved)) {\n return {\n warnings: [\n `URL font fetch \"${opts.url}\" redirected to disallowed host: ${resolved}`,\n ],\n };\n }\n if (++hops > 3) {\n return {\n warnings: [`URL font fetch \"${opts.url}\" too many redirects`],\n };\n }\n res = await f(resolved, { signal: ctrl.signal, redirect: 'manual' });\n }\n if (!res.ok) {\n return {\n warnings: [`URL font fetch \"${opts.url}\" returned ${res.status}`],\n };\n }\n const ab = await res.arrayBuffer();\n const raw = Buffer.from(ab);\n // Reject obvious non-font responses — e.g. jsDelivr 404 HTML, 200 OK\n // redirect pages, or aliased directory listings. Without this check the\n // bytes would sail through to the embed step and corrupt the output.\n const format = detectFontFormat(raw);\n if (format === 'unknown' || raw.length < 512) {\n return {\n warnings: [\n `URL font fetch \"${opts.url}\" returned ${raw.length} bytes of ${format} — not a TTF/OTF. Skipping.`,\n ],\n };\n }\n // WOFF/WOFF2 flow through: Office output never embeds bytes; the\n // LibreOffice preview stager handles them via fontconfig.\n // Metadata validation runs centrally in FontRegistry.materializeEntry.\n const buf = raw;\n opts.memoryCache?.set(key, buf);\n await opts.diskCache?.set(key, buf);\n return {\n source: {\n data: buf,\n weight: opts.weight,\n italic: opts.italic,\n format,\n },\n warnings: [],\n };\n } catch (err) {\n return {\n warnings: [\n `URL font fetch \"${opts.url}\" failed: ${(err as Error).message}`,\n ],\n };\n } finally {\n clearTimeout(timer);\n }\n}\n","/**\n * Post-fetch metadata validation for TTF/OTF bytes. Catches the three known\n * classes of defects that surface in Google Fonts' redistribution pipeline:\n *\n * 1. Wrong `OS/2.usWeightClass` (Chivo Light, Mada Regular, Petrona)\n * 2. Duplicate usWeightClass across weights (Exo Thin/ExtraLight)\n * 3. Non-unique `name` subfamily records (Inter, Manrope, Recursive)\n *\n * We don't throw on mismatch — the pipeline has already tried to fix the\n * bytes where it can (`rewriteFontSubfamilyNames` after variable-font\n * instancing; `rewriteFontFamilyName` at preview staging). The validator\n * returns human-readable diagnostics so the caller can emit warnings tagged\n * `FONT_METADATA_DEFECT`, pointing users at the upstream override escape\n * hatch before they ship a broken document.\n */\n\nimport { standardSubfamilyNames } from './ttf-name';\n\nconst HEADER_SIZE = 12;\nconst TABLE_RECORD_SIZE = 16;\n\ninterface NameProbe {\n platformID: number;\n nameID: number;\n value: string;\n}\n\nfunction readTable(\n ttf: Buffer,\n tag: string\n): { off: number; len: number } | null {\n if (ttf.length < HEADER_SIZE) return null;\n const version = ttf.readUInt32BE(0);\n if (version !== 0x00010000 && version !== 0x4f54544f) return null;\n const numTables = ttf.readUInt16BE(4);\n for (let i = 0; i < numTables; i++) {\n const r = HEADER_SIZE + i * TABLE_RECORD_SIZE;\n if (r + TABLE_RECORD_SIZE > ttf.length) return null;\n if (ttf.toString('ascii', r, r + 4) === tag) {\n return { off: ttf.readUInt32BE(r + 8), len: ttf.readUInt32BE(r + 12) };\n }\n }\n return null;\n}\n\nfunction readUsWeightClass(ttf: Buffer): number | null {\n const os2 = readTable(ttf, 'OS/2');\n if (!os2) return null;\n if (os2.off + 6 > ttf.length) return null;\n return ttf.readUInt16BE(os2.off + 4);\n}\n\nfunction readNames(ttf: Buffer, wanted: Set<number>): NameProbe[] {\n const nt = readTable(ttf, 'name');\n if (!nt) return [];\n const tableOff = nt.off;\n // Name-table header is 6 bytes (format, count, stringOffset) before the\n // first name record. Reject obviously-truncated tables up front so we\n // don't read count/storageRel past the buffer's end on malformed fonts.\n if (tableOff + 6 > ttf.length) return [];\n const count = ttf.readUInt16BE(tableOff + 2);\n const storageRel = ttf.readUInt16BE(tableOff + 4);\n const storage = tableOff + storageRel;\n const out: NameProbe[] = [];\n for (let j = 0; j < count; j++) {\n const r = tableOff + 6 + j * 12;\n // Each name record is 12 bytes. Stop as soon as the claimed count\n // would walk past the buffer — a malformed font claiming count=999999\n // would otherwise read garbage on each iteration.\n if (r + 12 > ttf.length) break;\n const platformID = ttf.readUInt16BE(r);\n const nameID = ttf.readUInt16BE(r + 6);\n if (!wanted.has(nameID)) continue;\n const length = ttf.readUInt16BE(r + 8);\n const offset = ttf.readUInt16BE(r + 10);\n const raw = ttf.slice(storage + offset, storage + offset + length);\n let value: string;\n if (platformID === 1) {\n value = raw.toString('ascii');\n } else {\n // Decode UTF-16BE. Buffer has no direct utf16be support; swap bytes.\n const swapped = Buffer.from(raw);\n if (swapped.length % 2 === 0) swapped.swap16();\n value = swapped.toString('utf16le');\n }\n out.push({ platformID, nameID, value });\n }\n return out;\n}\n\nexport interface FontMetadataDiagnostic {\n code:\n | 'WEIGHT_CLASS_MISMATCH'\n | 'SUBFAMILY_MISMATCH'\n | 'LEGACY_SUBFAMILY_MISMATCH';\n message: string;\n}\n\n/**\n * Inspect a font's metadata against the weight + italic we asked it to\n * represent. Returns one diagnostic per detected defect.\n */\nexport function validateFontMetadata(\n ttf: Buffer,\n weight: number,\n italic: boolean,\n familyLabel: string\n): FontMetadataDiagnostic[] {\n const diags: FontMetadataDiagnostic[] = [];\n const usWeight = readUsWeightClass(ttf);\n if (usWeight != null && usWeight !== weight) {\n diags.push({\n code: 'WEIGHT_CLASS_MISMATCH',\n message: `Font \"${familyLabel}\" weight ${weight}: OS/2.usWeightClass reports ${usWeight}. Likely a defective redistribution — consider adding an upstream override.`,\n });\n }\n\n // OS/2.fsType (embedding-permission bits) deliberately NOT checked. Office\n // output never embeds font bytes anymore — substitute mode rewrites to\n // SAFE_FONTS, custom mode ships references as-is, and the LibreOffice\n // preview stager only registers bytes transiently with the converter's\n // child process. Permission warnings would be pure noise for every\n // Google Fonts resolution.\n\n const std = standardSubfamilyNames(weight, italic);\n if (!std) return diags;\n const expected17 = std.typographic;\n const expected2 = std.legacy;\n\n const names = readNames(ttf, new Set([2, 17]));\n for (const n of names) {\n if (n.nameID === 17 && n.value !== expected17) {\n diags.push({\n code: 'SUBFAMILY_MISMATCH',\n message: `Font \"${familyLabel}\" weight ${weight}${italic ? ' italic' : ''}: name record (platform ${n.platformID}) nameID 17 = \"${n.value}\", expected \"${expected17}\".`,\n });\n }\n if (n.nameID === 2 && n.value !== expected2) {\n diags.push({\n code: 'LEGACY_SUBFAMILY_MISMATCH',\n message: `Font \"${familyLabel}\" weight ${weight}${italic ? ' italic' : ''}: name record (platform ${n.platformID}) nameID 2 = \"${n.value}\", expected \"${expected2}\".`,\n });\n }\n }\n return diags;\n}\n","/**\n * In-process LRU cache for resolved font buffers.\n * Scoped to a single process — do not share across requests on a server.\n */\n\nexport interface MemoryCacheOptions {\n /** Approximate soft cap in bytes. LRU-evict when exceeded. */\n maxBytes?: number;\n}\n\nexport class FontMemoryCache {\n private readonly store = new Map<string, Buffer>();\n private bytes = 0;\n private readonly maxBytes: number;\n\n constructor(opts: MemoryCacheOptions = {}) {\n this.maxBytes = opts.maxBytes ?? 20 * 1024 * 1024; // 20 MB default\n }\n\n get(key: string): Buffer | undefined {\n const v = this.store.get(key);\n if (!v) return undefined;\n // Refresh LRU position.\n this.store.delete(key);\n this.store.set(key, v);\n return v;\n }\n\n set(key: string, value: Buffer): void {\n const existing = this.store.get(key);\n if (existing) this.bytes -= existing.byteLength;\n this.store.set(key, value);\n this.bytes += value.byteLength;\n while (this.bytes > this.maxBytes && this.store.size > 0) {\n const oldest = this.store.keys().next().value as string | undefined;\n if (!oldest) break;\n const removed = this.store.get(oldest);\n this.store.delete(oldest);\n if (removed) this.bytes -= removed.byteLength;\n }\n }\n\n size(): number {\n return this.store.size;\n }\n}\n","/**\n * FontRegistry — merges catalog + document registry + runtime entries\n * and materializes referenced fonts into ResolvedFont records.\n *\n * Resolution rules, per referenced name:\n * 1. Registry match (by family or id, case-insensitive). Runtime entries win\n * on collision with document entries. Materialize each source.\n * 2. SAFE_FONTS membership → empty sources.\n * 3. Otherwise → empty sources with FONT_UNRESOLVED warning.\n */\n\nimport { isSafeFont } from '../schemas/font-catalog';\nimport type { FontRegistryEntry, FontSource } from '../schemas/font-catalog';\nimport type {\n FontRuntimeOpts,\n ResolvedFont,\n ResolvedFontSource,\n} from './types';\nimport { loadDataFontSource } from './sources/data-loader';\nimport { fetchGoogleFontSources } from './sources/google-fetcher';\nimport { fetchUrlFontSource } from './sources/url-fetcher';\nimport { validateFontMetadata } from './sources/ttf-validate';\nimport { FontMemoryCache } from './cache/memory-cache';\n\n/**\n * Minimal interface the registry needs from a disk cache. The concrete\n * implementation ships in `./cache/disk-cache` but is Node-only (uses fs/crypto).\n * Callers on Node inject an instance; browser callers pass nothing.\n */\nexport interface FontDiskCacheLike {\n get(key: string): Promise<Buffer | undefined>;\n set(key: string, value: Buffer): Promise<void>;\n}\n\n/**\n * Minimal interface for a file-loader. Same reasoning as FontDiskCacheLike:\n * concrete impl is Node-only, callers inject when on Node.\n */\nexport type FontFileLoader = (input: {\n path: string;\n weight?: number;\n italic?: boolean;\n baseDir?: string;\n}) => Promise<ResolvedFontSource>;\n\n/**\n * Minimal interface for the variable-font fetcher. `subset-font` (the\n * harfbuzz-wasm wrapper we use for axis pinning) reaches for `fs` at\n * init time, which crashes in the browser. Injection keeps that import\n * behind the Node-only subpath; browser bundles never pull it in, and\n * browser callers simply won't see `kind: 'variable'` fonts resolved\n * (the registry warns and skips instead).\n */\nexport type FontVariableLoader = (input: {\n url: string;\n weight: number;\n italic: boolean;\n axes?: Record<string, number>;\n fetchTimeoutMs?: number;\n memoryCache?: {\n get(key: string): Buffer | undefined;\n set(key: string, value: Buffer): void;\n };\n diskCache?: {\n get(key: string): Promise<Buffer | undefined>;\n set(key: string, value: Buffer): Promise<void>;\n };\n}) => Promise<{ source?: ResolvedFontSource; warnings?: string[] }>;\n\nexport interface FontRegistryInput {\n /** Runtime options — entries come from opts.extraEntries. */\n opts?: FontRuntimeOpts;\n /** Optional disk cache (Node only). Pass an instance of FontDiskCache. */\n diskCache?: FontDiskCacheLike;\n /**\n * Optional `kind: \"file\"` loader (Node only). Inject `loadFileFontSource`\n * from `@json-to-office/shared/fonts/sources/file-loader` on Node. Browser\n * callers pass nothing; `kind: \"file\"` sources then warn and skip.\n */\n fileLoader?: FontFileLoader;\n /**\n * Optional `kind: \"variable\"` loader (Node only). Inject\n * `fetchVariableFontSource` from `@json-to-office/shared/fonts/node` on\n * Node. Browser callers pass nothing; `kind: \"variable\"` sources then\n * warn and skip. Keeping this injected avoids dragging subset-font (and\n * its `fs.promises.readFile` bootstrap) into client bundles.\n */\n variableLoader?: FontVariableLoader;\n}\n\nexport class FontRegistry {\n private readonly index: Map<string, FontRegistryEntry>;\n private readonly cache: Map<string, ResolvedFont>;\n private readonly opts: FontRuntimeOpts;\n private readonly memoryCache: FontMemoryCache;\n private readonly diskCache: FontDiskCacheLike | undefined;\n private readonly fileLoader: FontFileLoader | undefined;\n private readonly variableLoader: FontVariableLoader | undefined;\n\n constructor(input: FontRegistryInput = {}) {\n this.opts = input.opts ?? {};\n this.index = new Map();\n this.cache = new Map();\n this.memoryCache = new FontMemoryCache();\n this.diskCache = input.diskCache;\n this.fileLoader = input.fileLoader;\n this.variableLoader = input.variableLoader;\n\n for (const e of this.opts.extraEntries ?? []) this.addEntry(e);\n }\n\n private addEntry(entry: FontRegistryEntry): void {\n this.index.set(entry.family.toLowerCase(), entry);\n this.index.set(entry.id.toLowerCase(), entry);\n }\n\n /** Resolve every referenced name in one pass. Order preserved. */\n async resolveMany(names: Iterable<string>): Promise<ResolvedFont[]> {\n const out: ResolvedFont[] = [];\n for (const n of names) out.push(await this.resolve(n));\n return out;\n }\n\n async resolve(name: string): Promise<ResolvedFont> {\n const key = name.toLowerCase();\n const cached = this.cache.get(key);\n if (cached) return cached;\n\n const entry = this.index.get(key);\n let result: ResolvedFont;\n\n if (entry) {\n result = await this.materializeEntry(entry);\n } else if (isSafeFont(name)) {\n result = { family: name, sources: [], warnings: [] };\n } else {\n result = {\n family: name,\n sources: [],\n warnings: [\n `Font \"${name}\" is not registered and not in SAFE_FONTS; will rely on host fallback.`,\n ],\n };\n }\n this.cache.set(key, result);\n return result;\n }\n\n private async materializeEntry(\n entry: FontRegistryEntry\n ): Promise<ResolvedFont> {\n const sources: ResolvedFontSource[] = [];\n const warnings: string[] = [];\n\n for (const source of entry.sources) {\n try {\n const materialized = await this.materializeSource(source, warnings);\n for (const s of materialized) {\n if (s.format === 'ttf' || s.format === 'otf') {\n for (const d of validateFontMetadata(\n s.data,\n s.weight,\n s.italic,\n entry.family\n )) {\n warnings.push(`[FONT_METADATA_DEFECT:${d.code}] ${d.message}`);\n }\n }\n sources.push(s);\n }\n } catch (err) {\n warnings.push(\n `Font \"${entry.family}\" source (${source.kind}) failed: ${\n (err as Error).message\n }`\n );\n }\n }\n\n return {\n family: entry.family,\n sources,\n warnings,\n };\n }\n\n private async materializeSource(\n source: FontSource,\n warnings: string[]\n ): Promise<ResolvedFontSource[]> {\n switch (source.kind) {\n case 'safe':\n // System-installed; no embedding data.\n return [];\n case 'file': {\n if (!this.fileLoader) {\n warnings.push(\n `kind:\"file\" source for \"${source.path}\" requires a fileLoader (Node-only); skipping.`\n );\n return [];\n }\n return [\n await this.fileLoader({\n path: source.path,\n weight: source.weight,\n italic: source.italic,\n baseDir: this.opts.baseDir,\n }),\n ];\n }\n case 'data':\n return [\n loadDataFontSource({\n data: source.data,\n weight: source.weight,\n italic: source.italic,\n }),\n ];\n case 'google': {\n const gf = this.opts.googleFonts;\n if (gf?.enabled === false) {\n warnings.push(\n `Google Fonts fetch disabled — skipping \"${source.family}\".`\n );\n return [];\n }\n const { sources: fetched, warnings: fetchWarnings } =\n await fetchGoogleFontSources({\n family: source.family,\n weights: source.weights ?? [400, 700],\n italics: source.italics ?? false,\n memoryCache: this.memoryCache,\n diskCache: this.diskCache,\n fetchTimeoutMs: gf?.fetchTimeoutMs,\n });\n warnings.push(...fetchWarnings);\n return fetched;\n }\n case 'url': {\n const gf = this.opts.googleFonts;\n const { source: fetched, warnings: fetchWarnings } =\n await fetchUrlFontSource({\n url: source.url,\n weight: source.weight ?? 400,\n italic: source.italic ?? false,\n memoryCache: this.memoryCache,\n diskCache: this.diskCache,\n fetchTimeoutMs: gf?.fetchTimeoutMs,\n });\n if (fetchWarnings) warnings.push(...fetchWarnings);\n return fetched ? [fetched] : [];\n }\n case 'variable': {\n // Variable-font instancing: fetch the variable TTF once (disk-\n // cached), then harfbuzz-pin the `wght` axis to produce a clean\n // static for this weight. Requires a Node-injected loader because\n // `subset-font` pulls in `fs` at init; without it, browser\n // bundles would break. Callers on Node pass `fetchVariableFontSource`\n // from `@json-to-office/shared/fonts/node`.\n if (!this.variableLoader) {\n warnings.push(\n `kind:\"variable\" source for \"${source.url}\" requires a variableLoader (Node-only); skipping.`\n );\n return [];\n }\n const gf = this.opts.googleFonts;\n const { source: fetched, warnings: fetchWarnings } =\n await this.variableLoader({\n url: source.url,\n weight: source.weight,\n italic: source.italic ?? false,\n axes: source.axes,\n memoryCache: this.memoryCache,\n diskCache: this.diskCache,\n fetchTimeoutMs: gf?.fetchTimeoutMs,\n });\n if (fetchWarnings) warnings.push(...fetchWarnings);\n return fetched ? [fetched] : [];\n }\n default:\n // Exhaustiveness guard — new kind added to schema without handler\n throw new Error(\n `Unknown font source kind: ${(source as { kind: string }).kind}`\n );\n }\n }\n}\n","/**\n * Curated list of popular Google Fonts for picker autocomplete.\n *\n * Not exhaustive — the full Google Fonts library has ~1500 families.\n * This is ~37 names known to cover most real-world use cases.\n */\n\nexport interface PopularGoogleFont {\n family: string;\n category: 'sans' | 'serif' | 'mono' | 'display' | 'handwriting';\n /** Weights available on Google Fonts for this family. */\n weights: number[];\n /** Whether italic variants exist. */\n hasItalic: boolean;\n}\n\nexport const POPULAR_GOOGLE_FONTS: readonly PopularGoogleFont[] = [\n // Sans-serif\n {\n family: 'Inter',\n category: 'sans',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n hasItalic: false,\n },\n {\n family: 'Roboto',\n category: 'sans',\n weights: [100, 300, 400, 500, 700, 900],\n hasItalic: true,\n },\n {\n family: 'Open Sans',\n category: 'sans',\n weights: [300, 400, 500, 600, 700, 800],\n hasItalic: true,\n },\n {\n family: 'Lato',\n category: 'sans',\n weights: [100, 300, 400, 700, 900],\n hasItalic: true,\n },\n {\n family: 'Montserrat',\n category: 'sans',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n hasItalic: true,\n },\n {\n family: 'Poppins',\n category: 'sans',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n hasItalic: true,\n },\n {\n family: 'Work Sans',\n category: 'sans',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n hasItalic: true,\n },\n {\n family: 'Nunito',\n category: 'sans',\n weights: [200, 300, 400, 500, 600, 700, 800, 900],\n hasItalic: true,\n },\n {\n family: 'DM Sans',\n category: 'sans',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n hasItalic: true,\n },\n {\n family: 'Rubik',\n category: 'sans',\n weights: [300, 400, 500, 600, 700, 800, 900],\n hasItalic: true,\n },\n {\n family: 'Source Sans 3',\n category: 'sans',\n weights: [200, 300, 400, 500, 600, 700, 800, 900],\n hasItalic: true,\n },\n {\n family: 'Manrope',\n category: 'sans',\n weights: [200, 300, 400, 500, 600, 700, 800],\n hasItalic: false,\n },\n {\n family: 'Plus Jakarta Sans',\n category: 'sans',\n weights: [200, 300, 400, 500, 600, 700, 800],\n hasItalic: true,\n },\n {\n family: 'IBM Plex Sans',\n category: 'sans',\n weights: [100, 200, 300, 400, 500, 600, 700],\n hasItalic: true,\n },\n {\n family: 'Archivo',\n category: 'sans',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n hasItalic: true,\n },\n {\n family: 'Geist',\n category: 'sans',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n hasItalic: true,\n },\n {\n family: 'Space Grotesk',\n category: 'sans',\n weights: [300, 400, 500, 600, 700],\n hasItalic: false,\n },\n\n // Serif\n {\n family: 'Playfair Display',\n category: 'serif',\n weights: [400, 500, 600, 700, 800, 900],\n hasItalic: true,\n },\n {\n family: 'Merriweather',\n category: 'serif',\n weights: [300, 400, 700, 900],\n hasItalic: true,\n },\n {\n family: 'Lora',\n category: 'serif',\n weights: [400, 500, 600, 700],\n hasItalic: true,\n },\n {\n family: 'Source Serif 4',\n category: 'serif',\n weights: [200, 300, 400, 500, 600, 700, 800, 900],\n hasItalic: true,\n },\n {\n family: 'DM Serif Display',\n category: 'serif',\n weights: [400],\n hasItalic: true,\n },\n {\n family: 'Crimson Pro',\n category: 'serif',\n weights: [200, 300, 400, 500, 600, 700, 800, 900],\n hasItalic: true,\n },\n {\n family: 'Cormorant Garamond',\n category: 'serif',\n weights: [300, 400, 500, 600, 700],\n hasItalic: true,\n },\n\n // Monospace\n {\n family: 'JetBrains Mono',\n category: 'mono',\n weights: [100, 200, 300, 400, 500, 600, 700, 800],\n hasItalic: true,\n },\n {\n family: 'Fira Code',\n category: 'mono',\n weights: [300, 400, 500, 600, 700],\n hasItalic: false,\n },\n {\n family: 'IBM Plex Mono',\n category: 'mono',\n weights: [100, 200, 300, 400, 500, 600, 700],\n hasItalic: true,\n },\n {\n family: 'Source Code Pro',\n category: 'mono',\n weights: [200, 300, 400, 500, 600, 700, 800, 900],\n hasItalic: true,\n },\n {\n family: 'Space Mono',\n category: 'mono',\n weights: [400, 700],\n hasItalic: true,\n },\n {\n family: 'Geist Mono',\n category: 'mono',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n hasItalic: true,\n },\n\n // Display\n {\n family: 'Bebas Neue',\n category: 'display',\n weights: [400],\n hasItalic: false,\n },\n {\n family: 'Abril Fatface',\n category: 'display',\n weights: [400],\n hasItalic: false,\n },\n {\n family: 'Archivo Black',\n category: 'display',\n weights: [400],\n hasItalic: false,\n },\n {\n family: 'Oswald',\n category: 'display',\n weights: [200, 300, 400, 500, 600, 700],\n hasItalic: false,\n },\n\n // Handwriting\n {\n family: 'Caveat',\n category: 'handwriting',\n weights: [400, 500, 600, 700],\n hasItalic: false,\n },\n {\n family: 'Pacifico',\n category: 'handwriting',\n weights: [400],\n hasItalic: false,\n },\n];\n","/**\n * Per-family upstream overrides for popular Google Fonts whose\n * redistribution on fonts.google.com has known defects we can't fix via\n * metadata patching alone.\n *\n * When `autoGoogleFontEntries` hits a family present in this table, it\n * builds override sources instead of issuing `kind: \"google\"` CSS requests.\n * Each entry is either:\n *\n * - `{ kind: \"url\", url, weight, italic? }` — a direct HTTPS TTF/OTF.\n * Use when a clean per-weight static exists on a stable CDN.\n *\n * - `{ kind: \"variable\", url, weight, italic? }` — points at a variable\n * TTF with an `fvar` table. The registry downloads the variable font\n * once and harfbuzz-pins the `wght` axis to the specified weight,\n * producing a clean static TTF. Use when the upstream ships a variable\n * font but no per-weight statics on a CDN (rsms/inter is the\n * canonical example — variable font on jsDelivr, per-weight statics\n * only in GitHub release zips).\n *\n * Pick `variable` over `url` when both are available: the instancer\n * produces per-weight glyph outlines that diverge correctly at every\n * axis value. Google's static redistributions collapse adjacent weights\n * onto the same instance — Inter Thin and ExtraLight both source at\n * ~wght=250 in Google's pipeline, so their static TTFs have 98% identical\n * glyph outlines. Instancing the upstream variable font at exactly wght=100\n * vs wght=200 gives properly distinct geometry.\n *\n * Validate new entries with a HEAD request before adding — the fetchers\n * reject non-TTF responses, but a failed override silently falls back to\n * the Google path, defeating the purpose.\n */\n\n/** One upstream variant source. Type matches the FontSource schema so we\n * can pass the entry directly into `FontRegistry`'s materialize pipeline. */\nexport type UpstreamVariant =\n | {\n kind: 'url';\n url: string;\n weight: number;\n italic?: boolean;\n }\n | {\n kind: 'variable';\n url: string;\n weight: number;\n italic?: boolean;\n /** Extra axis pins merged on top of the derived `wght` pin. */\n axes?: Record<string, number>;\n };\n\nexport interface UpstreamOverride {\n /** Human-readable for logs/diagnostics only. */\n reason: string;\n variants: UpstreamVariant[];\n}\n\n/**\n * rsms/inter publishes the upright variable master as `InterVariable.ttf`\n * on jsDelivr, but the italic master ONLY as `InterVariable-Italic.woff2`\n * — no italic `.ttf` exists under `docs/font-files/` at any tag, so a\n * `.ttf` italic URL 404s and every italic Inter run silently falls back\n * to host defaults. The variable fetcher accepts woff2 sources (fontverter\n * converts to sfnt before instancing), so point at the woff2 directly.\n *\n * We instance each master at every advertised weight so Inter Thin (100),\n * ExtraLight (200), Light (300), Medium (500), SemiBold (600), ExtraBold\n * (800), and Black (900) come out with distinct glyph outlines instead of\n * the near-duplicates Google's static redistribution ships. Regular (400)\n * and Bold (700) from Google were already clean, but instancing them from\n * the same variable font keeps the full family visually consistent.\n *\n * Version pin: `@v4.1` — the last stable rsms/inter release at the time\n * of writing. jsDelivr caches the file aggressively; a version bump here\n * invalidates that cache for users on a subsequent generate.\n */\nconst INTER_VARIABLE_URL =\n 'https://cdn.jsdelivr.net/gh/rsms/inter@v4.1/docs/font-files/InterVariable.ttf';\nconst INTER_VARIABLE_ITALIC_URL =\n 'https://cdn.jsdelivr.net/gh/rsms/inter@v4.1/docs/font-files/InterVariable-Italic.woff2';\n\nfunction interVariants(): UpstreamVariant[] {\n const weights = [100, 200, 300, 400, 500, 600, 700, 800, 900];\n const upright = weights.map((weight) => ({\n kind: 'variable' as const,\n url: INTER_VARIABLE_URL,\n weight,\n italic: false,\n }));\n const italic = weights.map((weight) => ({\n kind: 'variable' as const,\n url: INTER_VARIABLE_ITALIC_URL,\n weight,\n italic: true,\n }));\n return [...upright, ...italic];\n}\n\nexport const UPSTREAM_OVERRIDES: Record<string, UpstreamOverride> = {\n inter: {\n reason:\n \"Google's static Inter Thin/ExtraLight both carry usWeightClass=250 and near-identical glyph outlines (xAvgCharWidth differs by 1.8%); instancing the upstream variable font per weight produces properly distinct statics.\",\n variants: interVariants(),\n },\n};\n\n/** Case-insensitive lookup. Returns undefined when the family has no override. */\nexport function getUpstreamOverride(\n family: string\n): UpstreamOverride | undefined {\n return UPSTREAM_OVERRIDES[family.toLowerCase()];\n}\n","/**\n * Font family substitution: rewrite every non-safe family reference in\n * the doc tree + theme to a SAFE_FONTS equivalent. Used by the\n * `'substitute'` export mode (`FontRuntimeOpts.mode`) so that non-safe\n * fonts (Playfair Display, Inter, …) ship as Georgia/Calibri and the\n * document renders identically on every recipient machine — no embed\n * bytes, no Word-for-Mac intermediate-weight surprises.\n *\n * The walker mirrors the shape used by `collectFontNamesFromDocx/Pptx`\n * so the two stay in sync: whatever `collect` scans, `rewrite` will\n * rewrite. Future component-schema additions that introduce new font\n * keys go in `FONT_NAME_KEYS` / `THEME_FONT_KEYS` once, both sides pick\n * them up.\n */\n\nimport { SAFE_FONTS, isSafeFont } from '../schemas/font-catalog';\nimport { POPULAR_GOOGLE_FONTS } from './catalog/popular-google';\nimport {\n FONT_NAME_KEYS,\n THEME_FONT_KEYS,\n FONT_DECLARATION_KEYS,\n} from './collect';\n\n/** One swap recorded during a rewrite. */\nexport interface FontSubstitution {\n from: string;\n to: string;\n}\n\nexport interface ApplyFontSubstitutionResult<T> {\n doc: T;\n substitutions: FontSubstitution[];\n}\n\n/**\n * Walk a doc tree + swap every non-safe family reference per `mapping`.\n * Returns a new tree (structural clone) plus the list of `(from, to)`\n * swaps made, deduped by source name.\n *\n * One deliberate exception to the clone: `fontRegistry` subtrees are carried\n * through by reference, since they declare fonts rather than reference them\n * and nothing downstream mutates them.\n *\n * Families already in SAFE_FONTS are never rewritten (even if a mapping\n * entry targets them as a key — safe fonts don't need substitution).\n * Families with no mapping entry are left untouched — callers should\n * feed the result of `buildDefaultSubstitutionMap` to ensure every\n * non-safe reference gets a fallback.\n */\nexport function applyFontSubstitution<T>(\n doc: T,\n mapping: Record<string, string>\n): ApplyFontSubstitutionResult<T> {\n const seen = new Map<string, string>();\n const rewritten = rewrite(doc, mapping, seen) as T;\n const substitutions: FontSubstitution[] = [];\n for (const [from, to] of seen) substitutions.push({ from, to });\n return { doc: rewritten, substitutions };\n}\n\nfunction rewrite(\n node: unknown,\n mapping: Record<string, string>,\n seen: Map<string, string>,\n parentKey?: string\n): unknown {\n if (node == null) return node;\n\n if (typeof node === 'string') {\n if (\n parentKey &&\n (FONT_NAME_KEYS.has(parentKey) || THEME_FONT_KEYS.has(parentKey))\n ) {\n return maybeSwap(node, mapping, seen);\n }\n return node;\n }\n\n if (Array.isArray(node)) {\n return node.map((item) => rewrite(item, mapping, seen, parentKey));\n }\n\n if (typeof node === 'object') {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(node as Record<string, unknown>)) {\n // Mirror of collect.ts: a fontRegistry subtree declares families, it\n // does not reference them. Rewriting `entry.family` would rename the\n // registration out from under every reference and key an entry on a\n // SAFE_FONTS name. Carry it through by reference (see the clone note\n // on applyFontSubstitution).\n if (FONT_DECLARATION_KEYS.has(k)) {\n out[k] = v;\n continue;\n }\n // Parallel to collect.ts: `theme.fonts` may hold plain strings\n // keyed by heading/body/mono/light. Those strings are font names\n // and need swapping just like `font.family` does.\n if (\n parentKey === 'theme' &&\n k === 'fonts' &&\n v &&\n typeof v === 'object'\n ) {\n const nextFonts: Record<string, unknown> = {};\n for (const [fk, fv] of Object.entries(v as Record<string, unknown>)) {\n if (typeof fv === 'string') {\n nextFonts[fk] = maybeSwap(fv, mapping, seen);\n } else if (fv && typeof fv === 'object') {\n const fam = (fv as Record<string, unknown>).family;\n nextFonts[fk] =\n typeof fam === 'string'\n ? { ...(fv as object), family: maybeSwap(fam, mapping, seen) }\n : rewrite(fv, mapping, seen, fk);\n } else {\n nextFonts[fk] = fv;\n }\n }\n out[k] = nextFonts;\n continue;\n }\n out[k] = rewrite(v, mapping, seen, k);\n }\n return out;\n }\n\n return node;\n}\n\nfunction maybeSwap(\n name: string,\n mapping: Record<string, string>,\n seen: Map<string, string>\n): string {\n const trimmed = name.trim();\n if (trimmed.length === 0) return name;\n if (isSafeFont(trimmed)) return name;\n // Case-insensitive mapping lookup. Users may feed mappings with\n // slightly different casing than the doc's reference (e.g. \"inter\" vs\n // \"Inter\") — honour the target verbatim, don't force case.\n const lowered = trimmed.toLowerCase();\n for (const [from, to] of Object.entries(mapping)) {\n if (from.toLowerCase() === lowered) {\n seen.set(trimmed, to);\n return to;\n }\n }\n return name;\n}\n\n// ---------------------------------------------------------------------------\n// Default-mapping builder\n// ---------------------------------------------------------------------------\n\n/**\n * Explicit overrides for the most common non-safe fonts we see. Chosen\n * for visual similarity: sans serifs map to Calibri, serifs to Georgia\n * or Cambria depending on axis proportion, monospace to Consolas.\n * Extendable as real usage surfaces more families.\n */\nconst EXPLICIT_OVERRIDES: Record<string, string> = {\n Inter: 'Calibri',\n Roboto: 'Calibri',\n 'Open Sans': 'Calibri',\n Lato: 'Calibri',\n 'Source Sans 3': 'Calibri',\n 'Source Sans Pro': 'Calibri',\n 'IBM Plex Sans': 'Calibri',\n Archivo: 'Calibri',\n Geist: 'Calibri',\n 'Space Grotesk': 'Calibri',\n 'Work Sans': 'Calibri',\n Manrope: 'Calibri',\n Nunito: 'Calibri',\n 'Nunito Sans': 'Calibri',\n Poppins: 'Calibri',\n Montserrat: 'Calibri',\n 'Playfair Display': 'Georgia',\n Merriweather: 'Georgia',\n 'Source Serif 4': 'Cambria',\n 'Source Serif Pro': 'Cambria',\n 'IBM Plex Serif': 'Cambria',\n 'Crimson Pro': 'Cambria',\n Lora: 'Georgia',\n 'PT Serif': 'Georgia',\n 'Cormorant Garamond': 'Cambria',\n 'JetBrains Mono': 'Consolas',\n 'Fira Code': 'Consolas',\n 'IBM Plex Mono': 'Consolas',\n 'Source Code Pro': 'Consolas',\n 'Roboto Mono': 'Consolas',\n 'Geist Mono': 'Consolas',\n};\n\nconst CATEGORY_FALLBACK: Record<string, string> = {\n sans: 'Calibri',\n serif: 'Georgia',\n mono: 'Consolas',\n display: 'Georgia',\n handwriting: 'Segoe UI',\n};\n\n/**\n * Pick the safe-font fallback for a single non-safe family. Precedence:\n * 1. Explicit override in `EXPLICIT_OVERRIDES`.\n * 2. Category lookup in `POPULAR_GOOGLE_FONTS`.\n * 3. Final default (`Calibri`).\n *\n * Exposed for the playground dialog so it can pre-populate the per-family\n * picker with the same defaults the CLI would apply.\n */\nexport function defaultSubstituteFor(family: string): string {\n const trimmed = family.trim();\n // Explicit (case-insensitive).\n for (const [from, to] of Object.entries(EXPLICIT_OVERRIDES)) {\n if (from.toLowerCase() === trimmed.toLowerCase()) return to;\n }\n // Category.\n const catalog = POPULAR_GOOGLE_FONTS.find(\n (f) => f.family.toLowerCase() === trimmed.toLowerCase()\n );\n if (catalog) {\n const cat = CATEGORY_FALLBACK[catalog.category];\n if (cat) return cat;\n }\n return 'Calibri';\n}\n\n/**\n * Build a substitution map for every non-safe family in `referencedNames`.\n * Safe fonts are omitted from the result since they don't need swapping.\n * Caller can override individual entries before passing to\n * `applyFontSubstitution`.\n */\nexport function buildDefaultSubstitutionMap(\n referencedNames: Iterable<string>\n): Record<string, string> {\n const out: Record<string, string> = {};\n for (const raw of referencedNames) {\n const name = raw.trim();\n if (name.length === 0) continue;\n if (isSafeFont(name)) continue;\n if (out[name]) continue;\n out[name] = defaultSubstituteFor(name);\n }\n return out;\n}\n\n/** Re-export SAFE_FONTS for dialog/CLI consumers that need the allowlist. */\nexport { SAFE_FONTS };\n\n// ---------------------------------------------------------------------------\n// Export-mode pre-pass\n// ---------------------------------------------------------------------------\n\nimport type { FontRuntimeOpts } from './types';\nimport { collectFontNames } from './collect';\n\nexport interface ApplyExportModeInput<D, T> {\n doc: D;\n theme: T;\n fonts?: FontRuntimeOpts;\n}\n\nexport interface ApplyExportModeWarning {\n code: 'FONT_MODE_CUSTOM' | 'FONT_MODE_SUBSTITUTED';\n message: string;\n}\n\nexport interface ApplyExportModeResult<D, T> {\n doc: D;\n theme: T;\n warnings: ApplyExportModeWarning[];\n}\n\n/**\n * Inspect `fonts.mode` and apply the pre-resolution rewrite for the\n * requested mode.\n *\n * - `'custom'` (default) — no rewrite. Font references stay as authored;\n * recipients need the font installed or Word falls back. The\n * LibreOffice preview stager registers resolved bytes so preview\n * fidelity matches the recipient-side experience when the font is\n * installed.\n * - `'substitute'` — rewrite every non-safe family in doc + theme to its\n * mapped safe equivalent. Fills in defaults via\n * `buildDefaultSubstitutionMap` for any non-safe reference not present\n * in `fonts.substitution`. Emits one `FONT_MODE_SUBSTITUTED` warning\n * listing every swap.\n */\nexport function applyExportMode<D, T>(\n input: ApplyExportModeInput<D, T>\n): ApplyExportModeResult<D, T> {\n const mode = input.fonts?.mode ?? 'custom';\n if (mode === 'custom') {\n // Suppress the advisory entirely when callers never passed a `fonts`\n // option: they opted out of font-mode handling, so noisy per-run\n // warnings would flood existing callers that predate the pipeline.\n if (!input.fonts) {\n return { doc: input.doc, theme: input.theme, warnings: [] };\n }\n // Only emit the advisory when the doc/theme actually references a\n // non-safe family — a safe-only doc has nothing for recipients to be\n // missing, and the warning would be noise.\n const referenced = new Set<string>([\n ...collectFontNames(input.doc),\n ...collectFontNames(input.theme),\n ]);\n const nonSafe = [...referenced].filter((name) => !isSafeFont(name.trim()));\n const warnings: ApplyExportModeWarning[] =\n nonSafe.length > 0\n ? [\n {\n code: 'FONT_MODE_CUSTOM',\n message: `Export mode \"custom\": non-safe font references (${nonSafe.join(', ')}) kept as-is. Recipients need these fonts installed locally; Word falls back to a generic substitute otherwise.`,\n },\n ]\n : [];\n return {\n doc: input.doc,\n theme: input.theme,\n warnings,\n };\n }\n // mode === 'substitute'\n const referenced = new Set<string>([\n ...collectFontNames(input.doc),\n ...collectFontNames(input.theme),\n ]);\n const defaults = buildDefaultSubstitutionMap(referenced);\n const mapping: Record<string, string> = {\n ...defaults,\n ...(input.fonts?.substitution ?? {}),\n };\n\n const docRewrite = applyFontSubstitution(input.doc, mapping);\n const themeRewrite = applyFontSubstitution(input.theme, mapping);\n const combined = new Map<string, string>();\n for (const s of docRewrite.substitutions) combined.set(s.from, s.to);\n for (const s of themeRewrite.substitutions) combined.set(s.from, s.to);\n\n const warnings: ApplyExportModeWarning[] = [];\n if (combined.size > 0) {\n const list = [...combined]\n .map(([from, to]) => `${from} → ${to}`)\n .join(', ');\n warnings.push({\n code: 'FONT_MODE_SUBSTITUTED',\n message: `Export mode \"substitute\": rewrote non-safe families to safe equivalents — ${list}. No fonts embedded; document renders identically on every machine.`,\n });\n }\n return {\n doc: docRewrite.doc,\n theme: themeRewrite.doc,\n warnings,\n };\n}\n","/**\n * Default series-color tokens for charts. Single source of truth for every\n * format: the PPTX `chart` and `highcharts` components and the DOCX\n * `highcharts` component all resolve this list, in this order, against the\n * active theme when the author sets no explicit colors. Both theme schemas\n * declare all six tokens (accent4-6 optional in each), so a theme that fills\n * every slot produces the same palette in a deck and in a document.\n *\n * Slots the theme leaves unset are skipped in both formats: the implicit\n * palette shrinks and the chart library cycles the shorter list rather than\n * repeating `primary` for every empty slot. A theme carrying only\n * primary/secondary/accent — which is what the bundled DOCX themes carry —\n * therefore paints series 4+ identically in a deck and in a document.\n *\n * Skipping compacts holes: a theme defining accent5 but not accent4 yields\n * [primary, secondary, accent, accent5], so accent5 paints series 4. The list\n * is a preference-ordered pool of candidate colors, not fixed per-series slots,\n * so keeping a color the theme did define beats dropping or duplicating one.\n *\n * A slot may also hold another token's name (`\"accent4\": \"primary\"`) — both\n * theme schemas allow it — and both formats walk that reference to hex before\n * using it, so a chained slot lands on the same color in a deck as in a\n * document. A slot whose value reaches no hex (`\"accent4\": \"nonsense\"`, or a\n * reference cycle) is dropped from the implicit palette in both formats rather\n * than emitted verbatim: PowerPoint and Highcharts both answer an unparseable\n * color with silent black. Parity here covers the token names the two schemas\n * share; each format also has private color keys (DOCX `textSecondary`, PPTX\n * `text2`) that only resolve in their own format.\n *\n * Only the implicit palette skips. An author who names a token explicitly\n * (PPTX `chartColors: ['accent4']`) still gets the `primary` fallback and a\n * warning — naming an undefined token is an authoring error and stays loud.\n * PPTX warns THEME_COLOR_FALLBACK for an unset slot and UNKNOWN_COLOR for one\n * holding an unresolvable value; DOCX throws.\n */\nexport const DEFAULT_CHART_THEME_COLORS = [\n 'primary',\n 'secondary',\n 'accent',\n 'accent4',\n 'accent5',\n 'accent6',\n];\n","/**\n * Deep Merge Utilities\n * Generic deep-merge helpers used by both docx and pptx\n * componentDefaults resolution systems.\n */\n\nfunction isObject(item: any): boolean {\n return item !== null && typeof item === 'object' && !Array.isArray(item);\n}\n\nfunction deepMerge<T>(target: any, source: any): T {\n const output = { ...target };\n\n if (isObject(target) && isObject(source)) {\n Object.keys(source).forEach((key) => {\n if (isObject(source[key])) {\n // Recursing into a non-object default would spread it — a scalar\n // theme default (`borderColor: '#f0f0f0'`) turned a user's per-side\n // object into `{0: '#', 1: 'f', …}`, silently erasing the user value\n // it was supposed to yield to.\n if (!(key in target) || !isObject(target[key])) {\n output[key] = source[key];\n } else {\n output[key] = deepMerge(target[key], source[key]);\n }\n } else {\n output[key] = source[key];\n }\n });\n }\n\n return output as T;\n}\n\n/**\n * Merge theme defaults with user-provided configuration.\n * User config takes precedence over theme defaults.\n * Uses deep merge to preserve nested objects.\n * Arrays are replaced wholesale, not merged per-element.\n */\nexport function mergeWithDefaults<T>(\n userConfig: T,\n themeDefaults: Partial<T>\n): T {\n return deepMerge<T>(themeDefaults, userConfig);\n}\n"],"mappings":"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","names":["cacheKey","referenced","warnings"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@json-to-office/shared",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "Format-agnostic shared types, schemas and validation utilities",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -52,7 +52,7 @@
52
52
  "ajv": "8.17.1",
53
53
  "ajv-formats": "3.0.1",
54
54
  "subset-font": "^2.5.0",
55
- "@json-to-office/quality": "^1.5.0"
55
+ "@json-to-office/quality": "^1.6.0"
56
56
  },
57
57
  "devDependencies": {
58
58
  "@types/node": "20.11.0",