@json-to-office/shared 2.5.0 → 2.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/fonts/node.d.ts +9 -1
- package/dist/fonts/node.js +23 -0
- package/dist/fonts/node.js.map +1 -1
- package/dist/index.d.ts +90 -3
- package/dist/index.js +155 -36
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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/sources/ttf-structure.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 * 4. A family name that isn't the one we resolved the bytes as — an\n * instanced `InterVariable.ttf` still calls itself \"Inter Variable\", a\n * vendor CDN static may call itself anything at all\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 {\n readFontFamilyNames,\n readNameRecords,\n standardSubfamilyNames,\n} from './ttf-name';\n\nconst HEADER_SIZE = 12;\nconst TABLE_RECORD_SIZE = 16;\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\nexport interface FontMetadataDiagnostic {\n code:\n | 'WEIGHT_CLASS_MISMATCH'\n | 'SUBFAMILY_MISMATCH'\n | 'LEGACY_SUBFAMILY_MISMATCH'\n | 'FAMILY_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 // The bytes must answer to the family they were resolved as, or nothing\n // downstream can find them: a run says `rFonts w:ascii=\"Inter\"` and the\n // host matches that against the font's own name table, never against the\n // registry entry or the filename. `FontRegistry` repairs this before\n // validating, so reaching here means the repair could not run (no name\n // table, a format-1 one, non-sfnt bytes) — i.e. the face really will be\n // unreachable under `familyLabel`.\n const declaredFamilies = readFontFamilyNames(ttf);\n if (\n declaredFamilies.length > 0 &&\n !declaredFamilies.includes(familyLabel.trim())\n ) {\n diags.push({\n code: 'FAMILY_MISMATCH',\n message: `Font \"${familyLabel}\" weight ${weight}${italic ? ' italic' : ''}: name table declares ${declaredFamilies\n .map((f) => `\"${f}\"`)\n .join(\n ' / '\n )}, not \"${familyLabel}\". Referencing runs will not resolve this face.`,\n });\n }\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 = readNameRecords(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 * Structural sanity check for TTF/OTF bytes — can a font system load this at\n * all? Answered before `validateFontMetadata`, which asks the narrower\n * question of whether a loadable font's metadata says the right things.\n *\n * The gap this closes: `detectFontFormat` classifies by the four magic bytes\n * alone, so a download truncated anywhere after byte four is still 'ttf' and\n * flows on as if it were a font. Nothing downstream notices. The metadata\n * validator has no name records to check and usually no readable OS/2\n * either, so it stays silent by design; `FontRegistry`'s family stamp finds\n * no declared family to contradict and no-ops; and the bytes stage as a\n * `.ttf` that fontconfig and Core Text refuse, so the document renders in a\n * fallback face with nothing anywhere saying why.\n *\n * Deliberately NOT a full sfnt parser. It answers \"will this load\", not \"is\n * this well-formed\" — checksums, table-specific contents and glyph data are\n * out of scope. Every check here is one a font system performs before it can\n * index a face at all, which is what makes a failure worth a warning rather\n * than a matter of taste.\n */\n\nimport { readNameRecords } from './ttf-name';\n\nexport interface FontStructureDiagnostic {\n code: 'FONT_UNREADABLE';\n message: string;\n}\n\n/** sfnt versions a font system will attempt to load. */\nconst SFNT_VERSIONS = new Set<number>([\n 0x00010000, // TrueType outlines\n 0x4f54544f, // 'OTTO' — CFF outlines\n 0x74727565, // 'true'\n 0x74797031, // 'typ1'\n]);\n\nconst HEADER_SIZE = 12;\nconst TABLE_RECORD_SIZE = 16;\n\n/**\n * Inspect the sfnt envelope of a font we are about to hand to a font system.\n * Returns the first thing that makes it unloadable, or null.\n *\n * One diagnostic, not a list: past the first structural failure every later\n * check is reading rubble, and a caller can only act on the file as a whole\n * anyway.\n */\nexport function validateFontStructure(\n ttf: Buffer,\n weight: number,\n italic: boolean,\n familyLabel: string\n): FontStructureDiagnostic | null {\n const face = `Font \"${familyLabel}\" weight ${weight}${italic ? ' italic' : ''}`;\n const unreadable = (reason: string): FontStructureDiagnostic => ({\n code: 'FONT_UNREADABLE',\n message: `${face}: ${reason} The face will not resolve; text referencing it renders in a fallback.`,\n });\n\n if (ttf.length < HEADER_SIZE) {\n return unreadable(\n `${ttf.length} bytes is shorter than an sfnt header — the download is truncated or empty.`\n );\n }\n const version = ttf.readUInt32BE(0);\n if (!SFNT_VERSIONS.has(version)) {\n return unreadable(\n `sfnt version 0x${version.toString(16).padStart(8, '0')} is neither TrueType nor OpenType.`\n );\n }\n\n const numTables = ttf.readUInt16BE(4);\n if (numTables === 0) return unreadable('its table directory is empty.');\n const directoryEnd = HEADER_SIZE + numTables * TABLE_RECORD_SIZE;\n if (directoryEnd > ttf.length) {\n return unreadable(\n `its directory claims ${numTables} tables (${directoryEnd} bytes) but the file is ${ttf.length} bytes — truncated.`\n );\n }\n\n // Directory offsets in range. A table pointing past the end is the shape a\n // truncated download takes once it keeps enough bytes for the directory.\n const tags = new Set<string>();\n for (let i = 0; i < numTables; i += 1) {\n const ro = HEADER_SIZE + i * TABLE_RECORD_SIZE;\n const tag = ttf.toString('ascii', ro, ro + 4);\n const offset = ttf.readUInt32BE(ro + 8);\n const length = ttf.readUInt32BE(ro + 12);\n if (offset + length > ttf.length) {\n return unreadable(\n `its \"${tag}\" table runs to byte ${offset + length} but the file is ${ttf.length} bytes — truncated.`\n );\n }\n tags.add(tag);\n }\n\n // `name` carries the family every consumer looks a face up by, and `head`\n // the units-per-em every consumer scales it with. Neither is optional.\n for (const required of ['name', 'head']) {\n if (!tags.has(required)) {\n return unreadable(`it has no \"${required}\" table.`);\n }\n }\n\n // Outlines: glyf + loca (TrueType) or a CFF table (OpenType). Without them\n // there is nothing to draw, whatever else the file carries.\n const hasTrueTypeOutlines = tags.has('glyf') && tags.has('loca');\n const hasCffOutlines = tags.has('CFF ') || tags.has('CFF2');\n if (!hasTrueTypeOutlines && !hasCffOutlines) {\n return unreadable(\n 'it carries no glyph outlines (neither \"glyf\" + \"loca\" nor \"CFF \").'\n );\n }\n\n // Present is not the same as readable: the reader bounds every record by\n // the table's own extent, so a `name` table with a corrupt header or\n // out-of-range string offsets yields nothing and the face is unindexable.\n if (readNameRecords(ttf).length === 0) {\n return unreadable('its \"name\" table carries no readable records.');\n }\n\n return null;\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 { validateFontStructure } from './sources/ttf-structure';\nimport {\n readFontFamilyNames,\n rewriteFontFamilyName,\n standardSubfamilyNames,\n} from './sources/ttf-name';\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\n/**\n * Make the bytes declare the family they were resolved as.\n *\n * `ResolvedFont.family` is a claim about the bytes that every consumer\n * relies on and that nothing used to enforce. A host — Core Text,\n * fontconfig, GDI — finds a face by the family in its `name` table, never\n * by the registry entry or the filename, so a source whose name table says\n * something else is unreachable from the runs that reference it. It fails\n * silently: on a machine that happens to have the real family installed the\n * reference lands there instead, and only a host without it (a container,\n * a colleague's laptop) shows the fallback.\n *\n * The case that forced this: Inter resolves through an upstream override\n * that instances `InterVariable.ttf` per weight, and harfbuzz keeps the\n * master's name table — so every instance called itself \"Inter Variable\".\n * The weighted faces were saved by the preview stager renaming them to\n * \"Inter Medium\" / \"Inter SemiBold\" on the way out; weights 400 and 700\n * kept the family name unchanged (they ride the run's bold/italic toggles\n * instead), so those alone were staged under a name no run asks for.\n *\n * Matching on ANY declared name (family or typographic family) keeps a\n * legitimately-named static untouched: LifeSans-Medium.ttf registered as\n * \"Life Sans\" already answers to it via nameID 16, even though nameID 1\n * says \"Life Sans Medium\". Only bytes that answer to nothing get rewritten.\n */\nfunction stampResolvedFamily(\n source: ResolvedFontSource,\n family: string\n): ResolvedFontSource {\n if (source.format !== 'ttf' && source.format !== 'otf') return source;\n const declared = readFontFamilyNames(source.data);\n // Nothing declared: no claim to contradict, and nothing to repair against.\n if (declared.length === 0 || declared.includes(family)) return source;\n // The style this face occupies within `family`. The family IDs become\n // `family` for all four RIBBI faces, so full/PostScript names have to\n // carry the style or roman and italic collide on both.\n const subfamily = standardSubfamilyNames(\n source.weight,\n source.italic\n )?.legacy;\n return {\n ...source,\n data: rewriteFontFamilyName(source.data, family, subfamily),\n };\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 raw of materialized) {\n const sfnt = raw.format === 'ttf' || raw.format === 'otf';\n // Can a font system load these bytes at all? Asked first, and on\n // failure asked instead of everything below: the stamp has no name\n // table to write into and the metadata checks no records to read,\n // so both would quietly do nothing and report nothing. The source\n // is still returned — this diagnoses the file, it doesn't decide\n // for the caller whether to ship it.\n const broken = sfnt\n ? validateFontStructure(\n raw.data,\n raw.weight,\n raw.italic,\n entry.family\n )\n : null;\n if (broken) {\n warnings.push(`[${broken.code}] ${broken.message}`);\n sources.push(raw);\n continue;\n }\n const s = stampResolvedFamily(raw, entry.family);\n if (sfnt) {\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 * DOCX legacy slots and both formats' new `palette` roles may name another\n * token. Legacy PPTX `colors` slots accept hex only at validation, though\n * both runtime resolvers walk references 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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAWO,IAAM,qBAAqB;AAE3B,IAAM,iBAAiB;AAEvB,IAAM,iBAAiB;AAGvB,SAAS,eAAe,KAAsB;AACnD,MAAI,OAAO,QAAQ,YAAY,CAAC,OAAO,SAAS,GAAG;AACjD,WAAO;AACT,SAAO,KAAK,IAAI,gBAAgB,KAAK,IAAI,gBAAgB,KAAK,MAAM,GAAG,CAAC,CAAC;AAC3E;AAwBO,IAAM,sBAAsB;AAE5B,IAAM,2BAA2B,IAAI,OAAO;AAoF5C,IAAM,6BAA6B;;;AC3HnC,IAAM,iBAAiB,oBAAI,IAAI;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,IAAM,kBAAkB,oBAAI,IAAI,CAAC,WAAW,QAAQ,QAAQ,OAAO,CAAC;AAUpE,IAAM,wBAAwB,oBAAI,IAAI,CAAC,cAAc,CAAC;AAE7D,SAAS,QAAQ,MAAe,KAAkB,WAA0B;AAC1E,MAAI,QAAQ,KAAM;AAElB,MAAI,OAAO,SAAS,UAAU;AAE5B,QACE,cACC,eAAe,IAAI,SAAS,KAAK,gBAAgB,IAAI,SAAS,IAC/D;AACA,YAAM,UAAU,KAAK,KAAK;AAC1B,UAAI,QAAQ,SAAS,EAAG,KAAI,IAAI,OAAO;AAAA,IACzC;AACA;AAAA,EACF;AAEA,MAAI,MAAM,QAAQ,IAAI,GAAG;AAKvB,eAAW,QAAQ,KAAM,SAAQ,MAAM,KAAK,SAAS;AACrD;AAAA,EACF;AAEA,MAAI,OAAO,SAAS,UAAU;AAE5B,UAAM,aAAc,KAAiC;AACrD,QAAI,cAAc,WAAW,cAAc,OAAO,eAAe,UAAU;AACzE,iBAAW,CAAC,GAAG,CAAC,KAAK,OAAO;AAAA,QAC1B;AAAA,MACF,GAAG;AACD,YAAI,OAAO,MAAM,UAAU;AACzB,gBAAM,UAAU,EAAE,KAAK;AACvB,cAAI,QAAQ,SAAS,EAAG,KAAI,IAAI,OAAO;AAAA,QACzC,WAAW,KAAK,OAAO,MAAM,UAAU;AACrC,gBAAM,MAAO,EAA8B;AAC3C,cAAI,OAAO,QAAQ,YAAY,IAAI,KAAK,EAAE,SAAS,GAAG;AACpD,gBAAI,IAAI,IAAI,KAAK,CAAC;AAAA,UACpB;AAAA,QACF;AACA,aAAK;AAAA,MACP;AAAA,IACF;AAEA,eAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,IAA+B,GAAG;AAEpE,UAAI,sBAAsB,IAAI,CAAC,EAAG;AAClC,cAAQ,GAAG,KAAK,CAAC;AAAA,IACnB;AAAA,EACF;AACF;AAGO,SAAS,iBAAiB,KAA2B;AAC1D,QAAM,MAAM,oBAAI,IAAY;AAC5B,UAAQ,KAAK,GAAG;AAChB,SAAO;AACT;AAGO,IAAM,2BAA2B;AAGjC,IAAM,2BAA2B;;;AC5ExC,SAAS,QAAQ,GAAoC;AACnD,MAAI,CAAC,KAAK,OAAO,MAAM,SAAU,QAAO;AACxC,QAAM,IAAI;AACV,SACE,OAAO,EAAE,OAAO,YAChB,OAAO,EAAE,WAAW,YACpB,MAAM,QAAQ,EAAE,OAAO;AAE3B;AAEA,SAAS,OAAO,MAAe,KAAkC;AAC/D,MAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO,CAAC;AAC/C,QAAM,MAAO,KAAiC,GAAG;AACjD,SAAO,MAAM,QAAQ,GAAG,IAAI,IAAI,OAAO,OAAO,IAAI,CAAC;AACrD;AAGO,SAAS,qBAAqB,UAAwC;AAC3E,MAAI,CAAC,YAAY,OAAO,aAAa,SAAU,QAAO,CAAC;AACvD,SAAO,OAAQ,SAAqC,OAAO,cAAc;AAC3E;AAGO,SAAS,kBAAkB,OAAqC;AACrE,SAAO,OAAO,OAAO,cAAc;AACrC;AAQO,SAAS,uBACX,QACkB;AAUrB,QAAM,MAA2B,CAAC;AAClC,aAAW,SAAS,QAAQ;AAC1B,eAAW,SAAS,SAAS,CAAC,GAAG;AAC/B,YAAM,SAAS,MAAM,OAAO,YAAY;AACxC,YAAM,KAAK,MAAM,GAAG,YAAY;AAIhC,eAAS,IAAI,IAAI,SAAS,GAAG,KAAK,GAAG,KAAK;AACxC,YACE,IAAI,CAAC,EAAE,OAAO,YAAY,MAAM,UAChC,IAAI,CAAC,EAAE,GAAG,YAAY,MAAM,IAC5B;AACA,cAAI,OAAO,GAAG,CAAC;AAAA,QACjB;AAAA,MACF;AACA,UAAI,KAAK,KAAK;AAAA,IAChB;AAAA,EACF;AACA,SAAO;AACT;;;ACtCA,SAAS,mBACP,mBACa;AACb,QAAM,MAAM,oBAAI,IAAY;AAC5B,aAAW,KAAK,qBAAqB,CAAC,GAAG;AACvC,QAAI,IAAI,EAAE,OAAO,YAAY,CAAC;AAC9B,QAAI,IAAI,EAAE,GAAG,YAAY,CAAC;AAAA,EAC5B;AACA,SAAO;AACT;AAMO,SAAS,uBACd,OACsB;AACtB,QAAM,cAAc,mBAAmB,MAAM,iBAAiB;AAC9D,QAAM,WAAqB,CAAC;AAC5B,QAAM,aAAuB,CAAC;AAC9B,QAAM,WAAkC,CAAC;AAEzC,aAAW,QAAQ,MAAM,iBAAiB;AACxC,QAAI,WAAW,IAAI,KAAK,YAAY,IAAI,KAAK,YAAY,CAAC,GAAG;AAC3D,eAAS,KAAK,IAAI;AAClB;AAAA,IACF;AACA,eAAW,KAAK,IAAI;AACpB,aAAS,KAAK;AAAA,MACZ,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,SACE,SAAS,IAAI,2JAEE,WAAW,KAAK,IAAI,CAAC;AAAA,IACxC,CAAC;AAAA,EACH;AAEA,SAAO,EAAE,UAAU,YAAY,SAAS;AAC1C;;;AClEO,IAAM,gBAAwC;AAAA,EACnD,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AACP;AA+BO,SAAS,qBACd,QACA,QACA,QACmB;AACnB,MAAI,UAAU,MAAM;AAClB,WAAO,EAAE,QAAQ,MAAM,OAAO,QAAQ,oBAAoB,MAAM;AAAA,EAClE;AAEA,MAAI,WAAW,KAAK;AAClB,WAAO,EAAE,QAAQ,MAAM,OAAO,QAAQ,oBAAoB,MAAM;AAAA,EAClE;AACA,MAAI,WAAW,KAAK;AAClB,WAAO,EAAE,QAAQ,MAAM,MAAM,QAAQ,oBAAoB,MAAM;AAAA,EACjE;AACA,QAAM,QAAQ,cAAc,MAAM;AAClC,MAAI,CAAC,OAAO;AAIV,WAAO;AAAA,MACL;AAAA,MACA,MAAM,UAAU;AAAA,MAChB;AAAA,MACA,oBAAoB;AAAA,IACtB;AAAA,EACF;AACA,QAAM,SAAS,SAAS,IAAI,KAAK,YAAY,IAAI,KAAK;AACtD,SAAO;AAAA,IACL,QAAQ,GAAG,MAAM,GAAG,MAAM;AAAA,IAC1B,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,oBAAoB;AAAA,EACtB;AACF;;;AC1EA,IAAM,sBAAsB,IAAI,OAAO;AAMhC,SAAS,mBAAmB,OAA4C;AAC7E,QAAM,MAAM,MAAM,KAAK,KAAK;AAC5B,MAAI;AACJ,MAAI,IAAI,WAAW,OAAO,GAAG;AAC3B,UAAM,QAAQ,IAAI,QAAQ,GAAG;AAC7B,QAAI,QAAQ,EAAG,OAAM,IAAI,MAAM,wCAAwC;AAEvE,UAAM,SAAS,IAAI,MAAM,GAAG,KAAK;AACjC,QAAI,CAAC,OAAO,SAAS,SAAS,GAAG;AAC/B,YAAM,IAAI,MAAM,iCAAiC;AAAA,IACnD;AACA,UAAM,IAAI,MAAM,QAAQ,CAAC;AAAA,EAC3B,OAAO;AACL,UAAM;AAAA,EACR;AAKA,QAAM,qBAAqB,KAAK,MAAO,IAAI,SAAS,IAAK,CAAC;AAC1D,MAAI,qBAAqB,qBAAqB;AAC5C,UAAM,IAAI;AAAA,MACR,6BAA6B,mBAAmB;AAAA,IAClD;AAAA,EACF;AACA,QAAM,OAAO,OAAO,KAAK,KAAK,QAAQ;AACtC,MAAI,KAAK,WAAW,EAAG,OAAM,IAAI,MAAM,8BAA8B;AACrE,MAAI,KAAK,SAAS,qBAAqB;AACrC,UAAM,IAAI;AAAA,MACR,6BAA6B,mBAAmB;AAAA,IAClD;AAAA,EACF;AAIA,QAAM,SAAS,iBAAiB,IAAI;AACpC,MAAI,WAAW,WAAW;AACxB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAGA,SAAO;AAAA,IACL;AAAA,IACA,QAAQ,MAAM,UAAU;AAAA,IACxB,QAAQ,MAAM,UAAU;AAAA,IACxB;AAAA,EACF;AACF;;;ACxCA,IAAM,SAAS;AAEf,eAAe,iBACb,KACA,MAKmB;AACnB,QAAM,OAAO,IAAI,gBAAgB;AACjC,QAAM,QAAQ,WAAW,MAAM,KAAK,MAAM,GAAG,KAAK,aAAa,GAAI;AACnE,MAAI;AACF,UAAM,IAAI,KAAK,WAAW;AAC1B,WAAO,MAAM,EAAE,KAAK,EAAE,SAAS,KAAK,SAAS,QAAQ,KAAK,OAAO,CAAC;AAAA,EACpE,UAAE;AACA,iBAAa,KAAK;AAAA,EACpB;AACF;AAEA,SAAS,YACP,QACA,SACA,SACQ;AAER,QAAM,UAAU,mBAAmB,MAAM,EAAE,QAAQ,QAAQ,GAAG;AAC9D,QAAM,gBAAgB,CAAC,GAAG,OAAO,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AACvD,MAAI,SAAS;AACX,UAAM,OAAO,cAAc,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC,EAAE,KAAK,GAAG;AACxE,WAAO,4CAA4C,OAAO,cAAc,IAAI;AAAA,EAC9E;AACA,QAAM,WAAW,cAAc,KAAK,GAAG;AACvC,SAAO,4CAA4C,OAAO,SAAS,QAAQ;AAC7E;AAMA,SAAS,cACP,KACuD;AACvD,QAAM,MAA6D,CAAC;AACpE,QAAM,SAAS;AACf,MAAI;AACJ,UAAQ,IAAI,OAAO,KAAK,GAAG,OAAO,MAAM;AACtC,UAAM,QAAQ,EAAE,CAAC;AAGjB,UAAM,OAAO,MAAM;AAAA,MACjB;AAAA,IACF;AACA,QAAI,CAAC,KAAM;AACX,UAAM,UAAU,MAAM,MAAM,sBAAsB;AAClD,UAAM,UAAU,MAAM,MAAM,sBAAsB;AAClD,QAAI,KAAK;AAAA,MACP,QAAQ,UAAU,SAAS,QAAQ,CAAC,GAAG,EAAE,IAAI;AAAA,MAC7C,QAAQ,QAAQ,OAAO;AAAA,MACvB,QAAQ,KAAK,CAAC;AAAA,IAChB,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAEA,SAAS,SAAS,QAAgB,QAAgB,QAAyB;AACzE,SAAO,UAAU,MAAM,IAAI,MAAM,IAAI,SAAS,MAAM,GAAG;AACzD;AAEA,eAAsB,uBACpB,MAC4B;AAC5B,QAAM,UAAU,KAAK,SAAS,SAAS,KAAK,UAAU,CAAC,KAAK,GAAG;AAC/D,QAAM,UAAU,KAAK,WAAW;AAChC,QAAM,WAAqB,CAAC;AAC5B,QAAM,UAAgC,CAAC;AAGvC,QAAM,SAAgD,CAAC;AACvD,aAAW,KAAK,SAAS;AACvB,WAAO,KAAK,EAAE,QAAQ,GAAG,QAAQ,MAAM,CAAC;AACxC,QAAI,QAAS,QAAO,KAAK,EAAE,QAAQ,GAAG,QAAQ,KAAK,CAAC;AAAA,EACtD;AAGA,QAAM,UAAiD,CAAC;AACxD,aAAW,KAAK,QAAQ;AACtB,UAAM,MAAM,SAAS,KAAK,QAAQ,EAAE,QAAQ,EAAE,MAAM;AACpD,UAAM,MAAM,KAAK,aAAa,IAAI,GAAG;AACrC,QAAI,KAAK;AACP,cAAQ,KAAK;AAAA,QACX,MAAM;AAAA,QACN,QAAQ,EAAE;AAAA,QACV,QAAQ,EAAE;AAAA,QACV,QAAQ,iBAAiB,GAAG;AAAA,MAC9B,CAAC;AACD;AAAA,IACF;AACA,UAAM,OAAO,MAAM,KAAK,WAAW,IAAI,GAAG;AAC1C,QAAI,MAAM;AACR,WAAK,aAAa,IAAI,KAAK,IAAI;AAC/B,cAAQ,KAAK;AAAA,QACX,MAAM;AAAA,QACN,QAAQ,EAAE;AAAA,QACV,QAAQ,EAAE;AAAA,QACV,QAAQ,iBAAiB,IAAI;AAAA,MAC/B,CAAC;AACD;AAAA,IACF;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,QAAQ,WAAW,GAAG;AACxB,WAAO,EAAE,SAAS,SAAS;AAAA,EAC7B;AAGA,QAAM,cAAc,QAAQ,KAAK,CAAC,MAAM,EAAE,MAAM;AAChD,QAAM,SAAS;AAAA,IACb,KAAK;AAAA,IACL,MAAM,KAAK,IAAI,IAAI,QAAQ,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAAA,IAChD;AAAA,EACF;AACA,MAAI;AACJ,MAAI;AACF,UAAM,SAAS,MAAM,iBAAiB,QAAQ;AAAA,MAC5C,SAAS,EAAE,cAAc,OAAO;AAAA,MAChC,WAAW,KAAK;AAAA,MAChB,SAAS,KAAK;AAAA,IAChB,CAAC;AACD,QAAI,CAAC,OAAO,IAAI;AACd,eAAS;AAAA,QACP,+BAA+B,KAAK,MAAM,cAAc,OAAO,MAAM;AAAA,MACvE;AACA,aAAO,EAAE,SAAS,SAAS;AAAA,IAC7B;AACA,UAAM,MAAM,MAAM,OAAO,KAAK;AAC9B,YAAQ,cAAc,GAAG;AAAA,EAC3B,SAAS,KAAK;AACZ,aAAS;AAAA,MACP,+BAA+B,KAAK,MAAM,aACvC,IAAc,OACjB;AAAA,IACF;AACA,WAAO,EAAE,SAAS,SAAS;AAAA,EAC7B;AAEA,aAAW,QAAQ,SAAS;AAC1B,UAAM,QAAQ,MAAM;AAAA,MAClB,CAAC,MAAM,EAAE,WAAW,KAAK,UAAU,EAAE,WAAW,KAAK;AAAA,IACvD;AACA,QAAI,CAAC,OAAO;AACV,eAAS;AAAA,QACP,iBAAiB,KAAK,MAAM,oBAAoB,KAAK,MAAM,GACzD,KAAK,SAAS,YAAY,EAC5B;AAAA,MACF;AACA;AAAA,IACF;AACA,QAAI;AACF,YAAM,MAAM,MAAM,iBAAiB,MAAM,QAAQ;AAAA,QAC/C,WAAW,KAAK;AAAA,QAChB,SAAS,KAAK;AAAA,MAChB,CAAC;AACD,UAAI,CAAC,IAAI,IAAI;AACX,iBAAS;AAAA,UACP,+BAA+B,KAAK,MAAM,KAAK,KAAK,MAAM,aAAa,IAAI,MAAM;AAAA,QACnF;AACA;AAAA,MACF;AACA,YAAM,KAAK,MAAM,IAAI,YAAY;AAIjC,YAAM,MAAM,OAAO,KAAK,EAAE;AAC1B,YAAM,MAAM,SAAS,KAAK,QAAQ,KAAK,QAAQ,KAAK,MAAM;AAC1D,WAAK,aAAa,IAAI,KAAK,GAAG;AAC9B,YAAM,KAAK,WAAW,IAAI,KAAK,GAAG;AAClC,cAAQ,KAAK;AAAA,QACX,MAAM;AAAA,QACN,QAAQ,KAAK;AAAA,QACb,QAAQ,KAAK;AAAA,QACb,QAAQ,iBAAiB,GAAG;AAAA,MAC9B,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,eAAS;AAAA,QACP,+BAA+B,KAAK,MAAM,KAAK,KAAK,MAAM,YACvD,IAAc,OACjB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,SAAS,SAAS;AAC7B;;;ACtMA,SAASA,UAAS,KAAa,QAAgB,QAAyB;AACtE,SAAO,OAAO,GAAG,IAAI,MAAM,IAAI,SAAS,MAAM,GAAG;AACnD;AAEA,eAAsB,mBACpB,MAC+D;AAC/D,MAAI,CAAC,iBAAiB,KAAK,GAAG,GAAG;AAC/B,WAAO;AAAA,MACL,UAAU;AAAA,QACR,iEAAiE,KAAK,GAAG;AAAA,MAC3E;AAAA,IACF;AAAA,EACF;AACA,QAAM,MAAMA,UAAS,KAAK,KAAK,KAAK,QAAQ,KAAK,MAAM;AACvD,QAAM,MAAM,KAAK,aAAa,IAAI,GAAG;AACrC,MAAI,KAAK;AACP,WAAO;AAAA,MACL,QAAQ;AAAA,QACN,MAAM;AAAA,QACN,QAAQ,KAAK;AAAA,QACb,QAAQ,KAAK;AAAA,QACb,QAAQ,iBAAiB,GAAG;AAAA,MAC9B;AAAA,MACA,UAAU,CAAC;AAAA,IACb;AAAA,EACF;AACA,QAAM,OAAO,MAAM,KAAK,WAAW,IAAI,GAAG;AAC1C,MAAI,MAAM;AACR,SAAK,aAAa,IAAI,KAAK,IAAI;AAC/B,WAAO;AAAA,MACL,QAAQ;AAAA,QACN,MAAM;AAAA,QACN,QAAQ,KAAK;AAAA,QACb,QAAQ,KAAK;AAAA,QACb,QAAQ,iBAAiB,IAAI;AAAA,MAC/B;AAAA,MACA,UAAU,CAAC;AAAA,IACb;AAAA,EACF;AAEA,QAAM,OAAO,IAAI,gBAAgB;AACjC,QAAM,QAAQ,WAAW,MAAM,KAAK,MAAM,GAAG,KAAK,kBAAkB,GAAK;AACzE,MAAI;AACF,UAAM,IAAI,KAAK,WAAW;AAI1B,QAAI,MAAM,MAAM,EAAE,KAAK,KAAK,EAAE,QAAQ,KAAK,QAAQ,UAAU,SAAS,CAAC;AACvE,QAAI,OAAO;AACX,WAAO,IAAI,UAAU,OAAO,IAAI,SAAS,OAAO,IAAI,WAAW,KAAK;AAClE,YAAM,OAAO,IAAI,QAAQ,IAAI,UAAU;AACvC,UAAI,CAAC,MAAM;AACT,eAAO;AAAA,UACL,UAAU;AAAA,YACR,mBAAmB,KAAK,GAAG,KAAK,IAAI,MAAM;AAAA,UAC5C;AAAA,QACF;AAAA,MACF;AACA,YAAM,WAAW,IAAI,IAAI,MAAM,KAAK,GAAG,EAAE,SAAS;AAClD,UAAI,CAAC,iBAAiB,QAAQ,GAAG;AAC/B,eAAO;AAAA,UACL,UAAU;AAAA,YACR,mBAAmB,KAAK,GAAG,oCAAoC,QAAQ;AAAA,UACzE;AAAA,QACF;AAAA,MACF;AACA,UAAI,EAAE,OAAO,GAAG;AACd,eAAO;AAAA,UACL,UAAU,CAAC,mBAAmB,KAAK,GAAG,sBAAsB;AAAA,QAC9D;AAAA,MACF;AACA,YAAM,MAAM,EAAE,UAAU,EAAE,QAAQ,KAAK,QAAQ,UAAU,SAAS,CAAC;AAAA,IACrE;AACA,QAAI,CAAC,IAAI,IAAI;AACX,aAAO;AAAA,QACL,UAAU,CAAC,mBAAmB,KAAK,GAAG,cAAc,IAAI,MAAM,EAAE;AAAA,MAClE;AAAA,IACF;AACA,UAAM,KAAK,MAAM,IAAI,YAAY;AACjC,UAAM,MAAM,OAAO,KAAK,EAAE;AAI1B,UAAM,SAAS,iBAAiB,GAAG;AACnC,QAAI,WAAW,aAAa,IAAI,SAAS,KAAK;AAC5C,aAAO;AAAA,QACL,UAAU;AAAA,UACR,mBAAmB,KAAK,GAAG,cAAc,IAAI,MAAM,aAAa,MAAM;AAAA,QACxE;AAAA,MACF;AAAA,IACF;AAIA,UAAM,MAAM;AACZ,SAAK,aAAa,IAAI,KAAK,GAAG;AAC9B,UAAM,KAAK,WAAW,IAAI,KAAK,GAAG;AAClC,WAAO;AAAA,MACL,QAAQ;AAAA,QACN,MAAM;AAAA,QACN,QAAQ,KAAK;AAAA,QACb,QAAQ,KAAK;AAAA,QACb;AAAA,MACF;AAAA,MACA,UAAU,CAAC;AAAA,IACb;AAAA,EACF,SAAS,KAAK;AACZ,WAAO;AAAA,MACL,UAAU;AAAA,QACR,mBAAmB,KAAK,GAAG,aAAc,IAAc,OAAO;AAAA,MAChE;AAAA,IACF;AAAA,EACF,UAAE;AACA,iBAAa,KAAK;AAAA,EACpB;AACF;;;AC1HA,IAAM,cAAc;AACpB,IAAM,oBAAoB;AAE1B,SAAS,UACP,KACA,KACqC;AACrC,MAAI,IAAI,SAAS,YAAa,QAAO;AACrC,QAAM,UAAU,IAAI,aAAa,CAAC;AAClC,MAAI,YAAY,SAAc,YAAY,WAAY,QAAO;AAC7D,QAAM,YAAY,IAAI,aAAa,CAAC;AACpC,WAAS,IAAI,GAAG,IAAI,WAAW,KAAK;AAClC,UAAM,IAAI,cAAc,IAAI;AAC5B,QAAI,IAAI,oBAAoB,IAAI,OAAQ,QAAO;AAC/C,QAAI,IAAI,SAAS,SAAS,GAAG,IAAI,CAAC,MAAM,KAAK;AAC3C,aAAO,EAAE,KAAK,IAAI,aAAa,IAAI,CAAC,GAAG,KAAK,IAAI,aAAa,IAAI,EAAE,EAAE;AAAA,IACvE;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,kBAAkB,KAA4B;AACrD,QAAM,MAAM,UAAU,KAAK,MAAM;AACjC,MAAI,CAAC,IAAK,QAAO;AACjB,MAAI,IAAI,MAAM,IAAI,IAAI,OAAQ,QAAO;AACrC,SAAO,IAAI,aAAa,IAAI,MAAM,CAAC;AACrC;AAeO,SAAS,qBACd,KACA,QACA,QACA,aAC0B;AAC1B,QAAM,QAAkC,CAAC;AACzC,QAAM,WAAW,kBAAkB,GAAG;AACtC,MAAI,YAAY,QAAQ,aAAa,QAAQ;AAC3C,UAAM,KAAK;AAAA,MACT,MAAM;AAAA,MACN,SAAS,SAAS,WAAW,YAAY,MAAM,gCAAgC,QAAQ;AAAA,IACzF,CAAC;AAAA,EACH;AAgBA,QAAM,mBAAmB,oBAAoB,GAAG;AAChD,MACE,iBAAiB,SAAS,KAC1B,CAAC,iBAAiB,SAAS,YAAY,KAAK,CAAC,GAC7C;AACA,UAAM,KAAK;AAAA,MACT,MAAM;AAAA,MACN,SAAS,SAAS,WAAW,YAAY,MAAM,GAAG,SAAS,YAAY,EAAE,yBAAyB,iBAC/F,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,EACnB;AAAA,QACC;AAAA,MACF,CAAC,UAAU,WAAW;AAAA,IAC1B,CAAC;AAAA,EACH;AAEA,QAAM,MAAM,uBAAuB,QAAQ,MAAM;AACjD,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,aAAa,IAAI;AACvB,QAAM,YAAY,IAAI;AAEtB,QAAM,QAAQ,gBAAgB,KAAK,oBAAI,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;AACnD,aAAW,KAAK,OAAO;AACrB,QAAI,EAAE,WAAW,MAAM,EAAE,UAAU,YAAY;AAC7C,YAAM,KAAK;AAAA,QACT,MAAM;AAAA,QACN,SAAS,SAAS,WAAW,YAAY,MAAM,GAAG,SAAS,YAAY,EAAE,2BAA2B,EAAE,UAAU,kBAAkB,EAAE,KAAK,gBAAgB,UAAU;AAAA,MACrK,CAAC;AAAA,IACH;AACA,QAAI,EAAE,WAAW,KAAK,EAAE,UAAU,WAAW;AAC3C,YAAM,KAAK;AAAA,QACT,MAAM;AAAA,QACN,SAAS,SAAS,WAAW,YAAY,MAAM,GAAG,SAAS,YAAY,EAAE,2BAA2B,EAAE,UAAU,iBAAiB,EAAE,KAAK,gBAAgB,SAAS;AAAA,MACnK,CAAC;AAAA,IACH;AAAA,EACF;AACA,SAAO;AACT;;;ACtGA,IAAM,gBAAgB,oBAAI,IAAY;AAAA,EACpC;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AACF,CAAC;AAED,IAAMC,eAAc;AACpB,IAAMC,qBAAoB;AAUnB,SAAS,sBACd,KACA,QACA,QACA,aACgC;AAChC,QAAM,OAAO,SAAS,WAAW,YAAY,MAAM,GAAG,SAAS,YAAY,EAAE;AAC7E,QAAM,aAAa,CAAC,YAA6C;AAAA,IAC/D,MAAM;AAAA,IACN,SAAS,GAAG,IAAI,KAAK,MAAM;AAAA,EAC7B;AAEA,MAAI,IAAI,SAASD,cAAa;AAC5B,WAAO;AAAA,MACL,GAAG,IAAI,MAAM;AAAA,IACf;AAAA,EACF;AACA,QAAM,UAAU,IAAI,aAAa,CAAC;AAClC,MAAI,CAAC,cAAc,IAAI,OAAO,GAAG;AAC/B,WAAO;AAAA,MACL,kBAAkB,QAAQ,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC;AAAA,IACzD;AAAA,EACF;AAEA,QAAM,YAAY,IAAI,aAAa,CAAC;AACpC,MAAI,cAAc,EAAG,QAAO,WAAW,+BAA+B;AACtE,QAAM,eAAeA,eAAc,YAAYC;AAC/C,MAAI,eAAe,IAAI,QAAQ;AAC7B,WAAO;AAAA,MACL,wBAAwB,SAAS,YAAY,YAAY,2BAA2B,IAAI,MAAM;AAAA,IAChG;AAAA,EACF;AAIA,QAAM,OAAO,oBAAI,IAAY;AAC7B,WAAS,IAAI,GAAG,IAAI,WAAW,KAAK,GAAG;AACrC,UAAM,KAAKD,eAAc,IAAIC;AAC7B,UAAM,MAAM,IAAI,SAAS,SAAS,IAAI,KAAK,CAAC;AAC5C,UAAM,SAAS,IAAI,aAAa,KAAK,CAAC;AACtC,UAAM,SAAS,IAAI,aAAa,KAAK,EAAE;AACvC,QAAI,SAAS,SAAS,IAAI,QAAQ;AAChC,aAAO;AAAA,QACL,QAAQ,GAAG,wBAAwB,SAAS,MAAM,oBAAoB,IAAI,MAAM;AAAA,MAClF;AAAA,IACF;AACA,SAAK,IAAI,GAAG;AAAA,EACd;AAIA,aAAW,YAAY,CAAC,QAAQ,MAAM,GAAG;AACvC,QAAI,CAAC,KAAK,IAAI,QAAQ,GAAG;AACvB,aAAO,WAAW,cAAc,QAAQ,UAAU;AAAA,IACpD;AAAA,EACF;AAIA,QAAM,sBAAsB,KAAK,IAAI,MAAM,KAAK,KAAK,IAAI,MAAM;AAC/D,QAAM,iBAAiB,KAAK,IAAI,MAAM,KAAK,KAAK,IAAI,MAAM;AAC1D,MAAI,CAAC,uBAAuB,CAAC,gBAAgB;AAC3C,WAAO;AAAA,MACL;AAAA,IACF;AAAA,EACF;AAKA,MAAI,gBAAgB,GAAG,EAAE,WAAW,GAAG;AACrC,WAAO,WAAW,+CAA+C;AAAA,EACnE;AAEA,SAAO;AACT;;;AChHO,IAAM,kBAAN,MAAsB;AAAA,EACV,QAAQ,oBAAI,IAAoB;AAAA,EACzC,QAAQ;AAAA,EACC;AAAA,EAEjB,YAAY,OAA2B,CAAC,GAAG;AACzC,SAAK,WAAW,KAAK,YAAY,KAAK,OAAO;AAAA,EAC/C;AAAA,EAEA,IAAI,KAAiC;AACnC,UAAM,IAAI,KAAK,MAAM,IAAI,GAAG;AAC5B,QAAI,CAAC,EAAG,QAAO;AAEf,SAAK,MAAM,OAAO,GAAG;AACrB,SAAK,MAAM,IAAI,KAAK,CAAC;AACrB,WAAO;AAAA,EACT;AAAA,EAEA,IAAI,KAAa,OAAqB;AACpC,UAAM,WAAW,KAAK,MAAM,IAAI,GAAG;AACnC,QAAI,SAAU,MAAK,SAAS,SAAS;AACrC,SAAK,MAAM,IAAI,KAAK,KAAK;AACzB,SAAK,SAAS,MAAM;AACpB,WAAO,KAAK,QAAQ,KAAK,YAAY,KAAK,MAAM,OAAO,GAAG;AACxD,YAAM,SAAS,KAAK,MAAM,KAAK,EAAE,KAAK,EAAE;AACxC,UAAI,CAAC,OAAQ;AACb,YAAM,UAAU,KAAK,MAAM,IAAI,MAAM;AACrC,WAAK,MAAM,OAAO,MAAM;AACxB,UAAI,QAAS,MAAK,SAAS,QAAQ;AAAA,IACrC;AAAA,EACF;AAAA,EAEA,OAAe;AACb,WAAO,KAAK,MAAM;AAAA,EACpB;AACF;;;AC4EA,SAAS,oBACP,QACA,QACoB;AACpB,MAAI,OAAO,WAAW,SAAS,OAAO,WAAW,MAAO,QAAO;AAC/D,QAAM,WAAW,oBAAoB,OAAO,IAAI;AAEhD,MAAI,SAAS,WAAW,KAAK,SAAS,SAAS,MAAM,EAAG,QAAO;AAI/D,QAAM,YAAY;AAAA,IAChB,OAAO;AAAA,IACP,OAAO;AAAA,EACT,GAAG;AACH,SAAO;AAAA,IACL,GAAG;AAAA,IACH,MAAM,sBAAsB,OAAO,MAAM,QAAQ,SAAS;AAAA,EAC5D;AACF;AAEO,IAAM,eAAN,MAAmB;AAAA,EACP;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,QAA2B,CAAC,GAAG;AACzC,SAAK,OAAO,MAAM,QAAQ,CAAC;AAC3B,SAAK,QAAQ,oBAAI,IAAI;AACrB,SAAK,QAAQ,oBAAI,IAAI;AACrB,SAAK,cAAc,IAAI,gBAAgB;AACvC,SAAK,YAAY,MAAM;AACvB,SAAK,aAAa,MAAM;AACxB,SAAK,iBAAiB,MAAM;AAE5B,eAAW,KAAK,KAAK,KAAK,gBAAgB,CAAC,EAAG,MAAK,SAAS,CAAC;AAAA,EAC/D;AAAA,EAEQ,SAAS,OAAgC;AAC/C,SAAK,MAAM,IAAI,MAAM,OAAO,YAAY,GAAG,KAAK;AAChD,SAAK,MAAM,IAAI,MAAM,GAAG,YAAY,GAAG,KAAK;AAAA,EAC9C;AAAA;AAAA,EAGA,MAAM,YAAY,OAAkD;AAClE,UAAM,MAAsB,CAAC;AAC7B,eAAW,KAAK,MAAO,KAAI,KAAK,MAAM,KAAK,QAAQ,CAAC,CAAC;AACrD,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,QAAQ,MAAqC;AACjD,UAAM,MAAM,KAAK,YAAY;AAC7B,UAAM,SAAS,KAAK,MAAM,IAAI,GAAG;AACjC,QAAI,OAAQ,QAAO;AAEnB,UAAM,QAAQ,KAAK,MAAM,IAAI,GAAG;AAChC,QAAI;AAEJ,QAAI,OAAO;AACT,eAAS,MAAM,KAAK,iBAAiB,KAAK;AAAA,IAC5C,WAAW,WAAW,IAAI,GAAG;AAC3B,eAAS,EAAE,QAAQ,MAAM,SAAS,CAAC,GAAG,UAAU,CAAC,EAAE;AAAA,IACrD,OAAO;AACL,eAAS;AAAA,QACP,QAAQ;AAAA,QACR,SAAS,CAAC;AAAA,QACV,UAAU;AAAA,UACR,SAAS,IAAI;AAAA,QACf;AAAA,MACF;AAAA,IACF;AACA,SAAK,MAAM,IAAI,KAAK,MAAM;AAC1B,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,iBACZ,OACuB;AACvB,UAAM,UAAgC,CAAC;AACvC,UAAM,WAAqB,CAAC;AAE5B,eAAW,UAAU,MAAM,SAAS;AAClC,UAAI;AACF,cAAM,eAAe,MAAM,KAAK,kBAAkB,QAAQ,QAAQ;AAClE,mBAAW,OAAO,cAAc;AAC9B,gBAAM,OAAO,IAAI,WAAW,SAAS,IAAI,WAAW;AAOpD,gBAAM,SAAS,OACX;AAAA,YACE,IAAI;AAAA,YACJ,IAAI;AAAA,YACJ,IAAI;AAAA,YACJ,MAAM;AAAA,UACR,IACA;AACJ,cAAI,QAAQ;AACV,qBAAS,KAAK,IAAI,OAAO,IAAI,KAAK,OAAO,OAAO,EAAE;AAClD,oBAAQ,KAAK,GAAG;AAChB;AAAA,UACF;AACA,gBAAM,IAAI,oBAAoB,KAAK,MAAM,MAAM;AAC/C,cAAI,MAAM;AACR,uBAAW,KAAK;AAAA,cACd,EAAE;AAAA,cACF,EAAE;AAAA,cACF,EAAE;AAAA,cACF,MAAM;AAAA,YACR,GAAG;AACD,uBAAS,KAAK,yBAAyB,EAAE,IAAI,KAAK,EAAE,OAAO,EAAE;AAAA,YAC/D;AAAA,UACF;AACA,kBAAQ,KAAK,CAAC;AAAA,QAChB;AAAA,MACF,SAAS,KAAK;AACZ,iBAAS;AAAA,UACP,SAAS,MAAM,MAAM,aAAa,OAAO,IAAI,aAC1C,IAAc,OACjB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,MACL,QAAQ,MAAM;AAAA,MACd;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,kBACZ,QACA,UAC+B;AAC/B,YAAQ,OAAO,MAAM;AAAA,MACnB,KAAK;AAEH,eAAO,CAAC;AAAA,MACV,KAAK,QAAQ;AACX,YAAI,CAAC,KAAK,YAAY;AACpB,mBAAS;AAAA,YACP,2BAA2B,OAAO,IAAI;AAAA,UACxC;AACA,iBAAO,CAAC;AAAA,QACV;AACA,eAAO;AAAA,UACL,MAAM,KAAK,WAAW;AAAA,YACpB,MAAM,OAAO;AAAA,YACb,QAAQ,OAAO;AAAA,YACf,QAAQ,OAAO;AAAA,YACf,SAAS,KAAK,KAAK;AAAA,UACrB,CAAC;AAAA,QACH;AAAA,MACF;AAAA,MACA,KAAK;AACH,eAAO;AAAA,UACL,mBAAmB;AAAA,YACjB,MAAM,OAAO;AAAA,YACb,QAAQ,OAAO;AAAA,YACf,QAAQ,OAAO;AAAA,UACjB,CAAC;AAAA,QACH;AAAA,MACF,KAAK,UAAU;AACb,cAAM,KAAK,KAAK,KAAK;AACrB,YAAI,IAAI,YAAY,OAAO;AACzB,mBAAS;AAAA,YACP,gDAA2C,OAAO,MAAM;AAAA,UAC1D;AACA,iBAAO,CAAC;AAAA,QACV;AACA,cAAM,EAAE,SAAS,SAAS,UAAU,cAAc,IAChD,MAAM,uBAAuB;AAAA,UAC3B,QAAQ,OAAO;AAAA,UACf,SAAS,OAAO,WAAW,CAAC,KAAK,GAAG;AAAA,UACpC,SAAS,OAAO,WAAW;AAAA,UAC3B,aAAa,KAAK;AAAA,UAClB,WAAW,KAAK;AAAA,UAChB,gBAAgB,IAAI;AAAA,QACtB,CAAC;AACH,iBAAS,KAAK,GAAG,aAAa;AAC9B,eAAO;AAAA,MACT;AAAA,MACA,KAAK,OAAO;AACV,cAAM,KAAK,KAAK,KAAK;AACrB,cAAM,EAAE,QAAQ,SAAS,UAAU,cAAc,IAC/C,MAAM,mBAAmB;AAAA,UACvB,KAAK,OAAO;AAAA,UACZ,QAAQ,OAAO,UAAU;AAAA,UACzB,QAAQ,OAAO,UAAU;AAAA,UACzB,aAAa,KAAK;AAAA,UAClB,WAAW,KAAK;AAAA,UAChB,gBAAgB,IAAI;AAAA,QACtB,CAAC;AACH,YAAI,cAAe,UAAS,KAAK,GAAG,aAAa;AACjD,eAAO,UAAU,CAAC,OAAO,IAAI,CAAC;AAAA,MAChC;AAAA,MACA,KAAK,YAAY;AAOf,YAAI,CAAC,KAAK,gBAAgB;AACxB,mBAAS;AAAA,YACP,+BAA+B,OAAO,GAAG;AAAA,UAC3C;AACA,iBAAO,CAAC;AAAA,QACV;AACA,cAAM,KAAK,KAAK,KAAK;AACrB,cAAM,EAAE,QAAQ,SAAS,UAAU,cAAc,IAC/C,MAAM,KAAK,eAAe;AAAA,UACxB,KAAK,OAAO;AAAA,UACZ,QAAQ,OAAO;AAAA,UACf,QAAQ,OAAO,UAAU;AAAA,UACzB,MAAM,OAAO;AAAA,UACb,aAAa,KAAK;AAAA,UAClB,WAAW,KAAK;AAAA,UAChB,gBAAgB,IAAI;AAAA,QACtB,CAAC;AACH,YAAI,cAAe,UAAS,KAAK,GAAG,aAAa;AACjD,eAAO,UAAU,CAAC,OAAO,IAAI,CAAC;AAAA,MAChC;AAAA,MACA;AAEE,cAAM,IAAI;AAAA,UACR,6BAA8B,OAA4B,IAAI;AAAA,QAChE;AAAA,IACJ;AAAA,EACF;AACF;;;ACvVO,IAAM,uBAAqD;AAAA;AAAA,EAEhE;AAAA,IACE,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACrD,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACtC,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACtC,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACjC,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACrD,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACrD,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACrD,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IAChD,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACrD,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IAC3C,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IAChD,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IAC3C,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IAC3C,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IAC3C,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACrD,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACrD,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACjC,WAAW;AAAA,EACb;AAAA;AAAA,EAGA;AAAA,IACE,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACtC,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,GAAG;AAAA,IAC5B,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,GAAG;AAAA,IAC5B,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IAChD,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IAChD,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACjC,WAAW;AAAA,EACb;AAAA;AAAA,EAGA;AAAA,IACE,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IAChD,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACjC,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IAC3C,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IAChD,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,GAAG;AAAA,IAClB,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACrD,WAAW;AAAA,EACb;AAAA;AAAA,EAGA;AAAA,IACE,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACtC,WAAW;AAAA,EACb;AAAA;AAAA,EAGA;AAAA,IACE,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,GAAG;AAAA,IAC5B,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,WAAW;AAAA,EACb;AACF;;;ACtKA,IAAM,qBACJ;AACF,IAAM,4BACJ;AAEF,SAAS,gBAAmC;AAC1C,QAAM,UAAU,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAC5D,QAAM,UAAU,QAAQ,IAAI,CAAC,YAAY;AAAA,IACvC,MAAM;AAAA,IACN,KAAK;AAAA,IACL;AAAA,IACA,QAAQ;AAAA,EACV,EAAE;AACF,QAAM,SAAS,QAAQ,IAAI,CAAC,YAAY;AAAA,IACtC,MAAM;AAAA,IACN,KAAK;AAAA,IACL;AAAA,IACA,QAAQ;AAAA,EACV,EAAE;AACF,SAAO,CAAC,GAAG,SAAS,GAAG,MAAM;AAC/B;AAEO,IAAM,qBAAuD;AAAA,EAClE,OAAO;AAAA,IACL,QACE;AAAA,IACF,UAAU,cAAc;AAAA,EAC1B;AACF;AAGO,SAAS,oBACd,QAC8B;AAC9B,SAAO,mBAAmB,OAAO,YAAY,CAAC;AAChD;;;AC9DO,SAAS,sBACd,KACA,SACgC;AAChC,QAAM,OAAO,oBAAI,IAAoB;AACrC,QAAM,YAAY,QAAQ,KAAK,SAAS,IAAI;AAC5C,QAAM,gBAAoC,CAAC;AAC3C,aAAW,CAAC,MAAM,EAAE,KAAK,KAAM,eAAc,KAAK,EAAE,MAAM,GAAG,CAAC;AAC9D,SAAO,EAAE,KAAK,WAAW,cAAc;AACzC;AAEA,SAAS,QACP,MACA,SACA,MACA,WACS;AACT,MAAI,QAAQ,KAAM,QAAO;AAEzB,MAAI,OAAO,SAAS,UAAU;AAC5B,QACE,cACC,eAAe,IAAI,SAAS,KAAK,gBAAgB,IAAI,SAAS,IAC/D;AACA,aAAO,UAAU,MAAM,SAAS,IAAI;AAAA,IACtC;AACA,WAAO;AAAA,EACT;AAEA,MAAI,MAAM,QAAQ,IAAI,GAAG;AACvB,WAAO,KAAK,IAAI,CAAC,SAAS,QAAQ,MAAM,SAAS,MAAM,SAAS,CAAC;AAAA,EACnE;AAEA,MAAI,OAAO,SAAS,UAAU;AAC5B,UAAM,MAA+B,CAAC;AACtC,eAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,IAA+B,GAAG;AAMpE,UAAI,sBAAsB,IAAI,CAAC,GAAG;AAChC,YAAI,CAAC,IAAI;AACT;AAAA,MACF;AAIA,UACE,cAAc,WACd,MAAM,WACN,KACA,OAAO,MAAM,UACb;AACA,cAAM,YAAqC,CAAC;AAC5C,mBAAW,CAAC,IAAI,EAAE,KAAK,OAAO,QAAQ,CAA4B,GAAG;AACnE,cAAI,OAAO,OAAO,UAAU;AAC1B,sBAAU,EAAE,IAAI,UAAU,IAAI,SAAS,IAAI;AAAA,UAC7C,WAAW,MAAM,OAAO,OAAO,UAAU;AACvC,kBAAM,MAAO,GAA+B;AAC5C,sBAAU,EAAE,IACV,OAAO,QAAQ,WACX,EAAE,GAAI,IAAe,QAAQ,UAAU,KAAK,SAAS,IAAI,EAAE,IAC3D,QAAQ,IAAI,SAAS,MAAM,EAAE;AAAA,UACrC,OAAO;AACL,sBAAU,EAAE,IAAI;AAAA,UAClB;AAAA,QACF;AACA,YAAI,CAAC,IAAI;AACT;AAAA,MACF;AACA,UAAI,CAAC,IAAI,QAAQ,GAAG,SAAS,MAAM,CAAC;AAAA,IACtC;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAEA,SAAS,UACP,MACA,SACA,MACQ;AACR,QAAM,UAAU,KAAK,KAAK;AAC1B,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,MAAI,WAAW,OAAO,EAAG,QAAO;AAIhC,QAAM,UAAU,QAAQ,YAAY;AACpC,aAAW,CAAC,MAAM,EAAE,KAAK,OAAO,QAAQ,OAAO,GAAG;AAChD,QAAI,KAAK,YAAY,MAAM,SAAS;AAClC,WAAK,IAAI,SAAS,EAAE;AACpB,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAYA,IAAM,qBAA6C;AAAA,EACjD,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,MAAM;AAAA,EACN,iBAAiB;AAAA,EACjB,mBAAmB;AAAA,EACnB,iBAAiB;AAAA,EACjB,SAAS;AAAA,EACT,OAAO;AAAA,EACP,iBAAiB;AAAA,EACjB,aAAa;AAAA,EACb,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,eAAe;AAAA,EACf,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,oBAAoB;AAAA,EACpB,cAAc;AAAA,EACd,kBAAkB;AAAA,EAClB,oBAAoB;AAAA,EACpB,kBAAkB;AAAA,EAClB,eAAe;AAAA,EACf,MAAM;AAAA,EACN,YAAY;AAAA,EACZ,sBAAsB;AAAA,EACtB,kBAAkB;AAAA,EAClB,aAAa;AAAA,EACb,iBAAiB;AAAA,EACjB,mBAAmB;AAAA,EACnB,eAAe;AAAA,EACf,cAAc;AAChB;AAEA,IAAM,oBAA4C;AAAA,EAChD,MAAM;AAAA,EACN,OAAO;AAAA,EACP,MAAM;AAAA,EACN,SAAS;AAAA,EACT,aAAa;AACf;AAWO,SAAS,qBAAqB,QAAwB;AAC3D,QAAM,UAAU,OAAO,KAAK;AAE5B,aAAW,CAAC,MAAM,EAAE,KAAK,OAAO,QAAQ,kBAAkB,GAAG;AAC3D,QAAI,KAAK,YAAY,MAAM,QAAQ,YAAY,EAAG,QAAO;AAAA,EAC3D;AAEA,QAAM,UAAU,qBAAqB;AAAA,IACnC,CAAC,MAAM,EAAE,OAAO,YAAY,MAAM,QAAQ,YAAY;AAAA,EACxD;AACA,MAAI,SAAS;AACX,UAAM,MAAM,kBAAkB,QAAQ,QAAQ;AAC9C,QAAI,IAAK,QAAO;AAAA,EAClB;AACA,SAAO;AACT;AAQO,SAAS,4BACd,iBACwB;AACxB,QAAM,MAA8B,CAAC;AACrC,aAAW,OAAO,iBAAiB;AACjC,UAAM,OAAO,IAAI,KAAK;AACtB,QAAI,KAAK,WAAW,EAAG;AACvB,QAAI,WAAW,IAAI,EAAG;AACtB,QAAI,IAAI,IAAI,EAAG;AACf,QAAI,IAAI,IAAI,qBAAqB,IAAI;AAAA,EACvC;AACA,SAAO;AACT;AA4CO,SAAS,gBACd,OAC6B;AAC7B,QAAM,OAAO,MAAM,OAAO,QAAQ;AAClC,MAAI,SAAS,UAAU;AAIrB,QAAI,CAAC,MAAM,OAAO;AAChB,aAAO,EAAE,KAAK,MAAM,KAAK,OAAO,MAAM,OAAO,UAAU,CAAC,EAAE;AAAA,IAC5D;AAIA,UAAMC,cAAa,oBAAI,IAAY;AAAA,MACjC,GAAG,iBAAiB,MAAM,GAAG;AAAA,MAC7B,GAAG,iBAAiB,MAAM,KAAK;AAAA,IACjC,CAAC;AACD,UAAM,UAAU,CAAC,GAAGA,WAAU,EAAE,OAAO,CAAC,SAAS,CAAC,WAAW,KAAK,KAAK,CAAC,CAAC;AACzE,UAAMC,YACJ,QAAQ,SAAS,IACb;AAAA,MACE;AAAA,QACE,MAAM;AAAA,QACN,SAAS,mDAAmD,QAAQ,KAAK,IAAI,CAAC;AAAA,MAChF;AAAA,IACF,IACA,CAAC;AACP,WAAO;AAAA,MACL,KAAK,MAAM;AAAA,MACX,OAAO,MAAM;AAAA,MACb,UAAAA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,aAAa,oBAAI,IAAY;AAAA,IACjC,GAAG,iBAAiB,MAAM,GAAG;AAAA,IAC7B,GAAG,iBAAiB,MAAM,KAAK;AAAA,EACjC,CAAC;AACD,QAAM,WAAW,4BAA4B,UAAU;AACvD,QAAM,UAAkC;AAAA,IACtC,GAAG;AAAA,IACH,GAAI,MAAM,OAAO,gBAAgB,CAAC;AAAA,EACpC;AAEA,QAAM,aAAa,sBAAsB,MAAM,KAAK,OAAO;AAC3D,QAAM,eAAe,sBAAsB,MAAM,OAAO,OAAO;AAC/D,QAAM,WAAW,oBAAI,IAAoB;AACzC,aAAW,KAAK,WAAW,cAAe,UAAS,IAAI,EAAE,MAAM,EAAE,EAAE;AACnE,aAAW,KAAK,aAAa,cAAe,UAAS,IAAI,EAAE,MAAM,EAAE,EAAE;AAErE,QAAM,WAAqC,CAAC;AAC5C,MAAI,SAAS,OAAO,GAAG;AACrB,UAAM,OAAO,CAAC,GAAG,QAAQ,EACtB,IAAI,CAAC,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,WAAM,EAAE,EAAE,EACrC,KAAK,IAAI;AACZ,aAAS,KAAK;AAAA,MACZ,MAAM;AAAA,MACN,SAAS,kFAA6E,IAAI;AAAA,IAC5F,CAAC;AAAA,EACH;AACA,SAAO;AAAA,IACL,KAAK,WAAW;AAAA,IAChB,OAAO,aAAa;AAAA,IACpB;AAAA,EACF;AACF;;;AC/TO,IAAM,6BAA6B;AAAA,EACxC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;;;ACrCA,SAAS,SAAS,MAAoB;AACpC,SAAO,SAAS,QAAQ,OAAO,SAAS,YAAY,CAAC,MAAM,QAAQ,IAAI;AACzE;AAEA,SAAS,UAAa,QAAa,QAAgB;AACjD,QAAM,SAAS,EAAE,GAAG,OAAO;AAE3B,MAAI,SAAS,MAAM,KAAK,SAAS,MAAM,GAAG;AACxC,WAAO,KAAK,MAAM,EAAE,QAAQ,CAAC,QAAQ;AACnC,UAAI,SAAS,OAAO,GAAG,CAAC,GAAG;AAKzB,YAAI,EAAE,OAAO,WAAW,CAAC,SAAS,OAAO,GAAG,CAAC,GAAG;AAC9C,iBAAO,GAAG,IAAI,OAAO,GAAG;AAAA,QAC1B,OAAO;AACL,iBAAO,GAAG,IAAI,UAAU,OAAO,GAAG,GAAG,OAAO,GAAG,CAAC;AAAA,QAClD;AAAA,MACF,OAAO;AACL,eAAO,GAAG,IAAI,OAAO,GAAG;AAAA,MAC1B;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAQO,SAAS,kBACd,YACA,eACG;AACH,SAAO,UAAa,eAAe,UAAU;AAC/C;","names":["cacheKey","HEADER_SIZE","TABLE_RECORD_SIZE","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/sources/ttf-structure.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/theme/chart-typography.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 * 4. A family name that isn't the one we resolved the bytes as — an\n * instanced `InterVariable.ttf` still calls itself \"Inter Variable\", a\n * vendor CDN static may call itself anything at all\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 {\n readFontFamilyNames,\n readNameRecords,\n standardSubfamilyNames,\n} from './ttf-name';\n\nconst HEADER_SIZE = 12;\nconst TABLE_RECORD_SIZE = 16;\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\nexport interface FontMetadataDiagnostic {\n code:\n | 'WEIGHT_CLASS_MISMATCH'\n | 'SUBFAMILY_MISMATCH'\n | 'LEGACY_SUBFAMILY_MISMATCH'\n | 'FAMILY_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 // The bytes must answer to the family they were resolved as, or nothing\n // downstream can find them: a run says `rFonts w:ascii=\"Inter\"` and the\n // host matches that against the font's own name table, never against the\n // registry entry or the filename. `FontRegistry` repairs this before\n // validating, so reaching here means the repair could not run (no name\n // table, a format-1 one, non-sfnt bytes) — i.e. the face really will be\n // unreachable under `familyLabel`.\n const declaredFamilies = readFontFamilyNames(ttf);\n if (\n declaredFamilies.length > 0 &&\n !declaredFamilies.includes(familyLabel.trim())\n ) {\n diags.push({\n code: 'FAMILY_MISMATCH',\n message: `Font \"${familyLabel}\" weight ${weight}${italic ? ' italic' : ''}: name table declares ${declaredFamilies\n .map((f) => `\"${f}\"`)\n .join(\n ' / '\n )}, not \"${familyLabel}\". Referencing runs will not resolve this face.`,\n });\n }\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 = readNameRecords(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 * Structural sanity check for TTF/OTF bytes — can a font system load this at\n * all? Answered before `validateFontMetadata`, which asks the narrower\n * question of whether a loadable font's metadata says the right things.\n *\n * The gap this closes: `detectFontFormat` classifies by the four magic bytes\n * alone, so a download truncated anywhere after byte four is still 'ttf' and\n * flows on as if it were a font. Nothing downstream notices. The metadata\n * validator has no name records to check and usually no readable OS/2\n * either, so it stays silent by design; `FontRegistry`'s family stamp finds\n * no declared family to contradict and no-ops; and the bytes stage as a\n * `.ttf` that fontconfig and Core Text refuse, so the document renders in a\n * fallback face with nothing anywhere saying why.\n *\n * Deliberately NOT a full sfnt parser. It answers \"will this load\", not \"is\n * this well-formed\" — checksums, table-specific contents and glyph data are\n * out of scope. Every check here is one a font system performs before it can\n * index a face at all, which is what makes a failure worth a warning rather\n * than a matter of taste.\n */\n\nimport { readNameRecords } from './ttf-name';\n\nexport interface FontStructureDiagnostic {\n code: 'FONT_UNREADABLE';\n message: string;\n}\n\n/** sfnt versions a font system will attempt to load. */\nconst SFNT_VERSIONS = new Set<number>([\n 0x00010000, // TrueType outlines\n 0x4f54544f, // 'OTTO' — CFF outlines\n 0x74727565, // 'true'\n 0x74797031, // 'typ1'\n]);\n\nconst HEADER_SIZE = 12;\nconst TABLE_RECORD_SIZE = 16;\n\n/**\n * Inspect the sfnt envelope of a font we are about to hand to a font system.\n * Returns the first thing that makes it unloadable, or null.\n *\n * One diagnostic, not a list: past the first structural failure every later\n * check is reading rubble, and a caller can only act on the file as a whole\n * anyway.\n */\nexport function validateFontStructure(\n ttf: Buffer,\n weight: number,\n italic: boolean,\n familyLabel: string\n): FontStructureDiagnostic | null {\n const face = `Font \"${familyLabel}\" weight ${weight}${italic ? ' italic' : ''}`;\n const unreadable = (reason: string): FontStructureDiagnostic => ({\n code: 'FONT_UNREADABLE',\n message: `${face}: ${reason} The face will not resolve; text referencing it renders in a fallback.`,\n });\n\n if (ttf.length < HEADER_SIZE) {\n return unreadable(\n `${ttf.length} bytes is shorter than an sfnt header — the download is truncated or empty.`\n );\n }\n const version = ttf.readUInt32BE(0);\n if (!SFNT_VERSIONS.has(version)) {\n return unreadable(\n `sfnt version 0x${version.toString(16).padStart(8, '0')} is neither TrueType nor OpenType.`\n );\n }\n\n const numTables = ttf.readUInt16BE(4);\n if (numTables === 0) return unreadable('its table directory is empty.');\n const directoryEnd = HEADER_SIZE + numTables * TABLE_RECORD_SIZE;\n if (directoryEnd > ttf.length) {\n return unreadable(\n `its directory claims ${numTables} tables (${directoryEnd} bytes) but the file is ${ttf.length} bytes — truncated.`\n );\n }\n\n // Directory offsets in range. A table pointing past the end is the shape a\n // truncated download takes once it keeps enough bytes for the directory.\n const tags = new Set<string>();\n for (let i = 0; i < numTables; i += 1) {\n const ro = HEADER_SIZE + i * TABLE_RECORD_SIZE;\n const tag = ttf.toString('ascii', ro, ro + 4);\n const offset = ttf.readUInt32BE(ro + 8);\n const length = ttf.readUInt32BE(ro + 12);\n if (offset + length > ttf.length) {\n return unreadable(\n `its \"${tag}\" table runs to byte ${offset + length} but the file is ${ttf.length} bytes — truncated.`\n );\n }\n tags.add(tag);\n }\n\n // `name` carries the family every consumer looks a face up by, and `head`\n // the units-per-em every consumer scales it with. Neither is optional.\n for (const required of ['name', 'head']) {\n if (!tags.has(required)) {\n return unreadable(`it has no \"${required}\" table.`);\n }\n }\n\n // Outlines: glyf + loca (TrueType) or a CFF table (OpenType). Without them\n // there is nothing to draw, whatever else the file carries.\n const hasTrueTypeOutlines = tags.has('glyf') && tags.has('loca');\n const hasCffOutlines = tags.has('CFF ') || tags.has('CFF2');\n if (!hasTrueTypeOutlines && !hasCffOutlines) {\n return unreadable(\n 'it carries no glyph outlines (neither \"glyf\" + \"loca\" nor \"CFF \").'\n );\n }\n\n // Present is not the same as readable: the reader bounds every record by\n // the table's own extent, so a `name` table with a corrupt header or\n // out-of-range string offsets yields nothing and the face is unindexable.\n if (readNameRecords(ttf).length === 0) {\n return unreadable('its \"name\" table carries no readable records.');\n }\n\n return null;\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 { validateFontStructure } from './sources/ttf-structure';\nimport {\n readFontFamilyNames,\n rewriteFontFamilyName,\n standardSubfamilyNames,\n} from './sources/ttf-name';\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\n/**\n * Make the bytes declare the family they were resolved as.\n *\n * `ResolvedFont.family` is a claim about the bytes that every consumer\n * relies on and that nothing used to enforce. A host — Core Text,\n * fontconfig, GDI — finds a face by the family in its `name` table, never\n * by the registry entry or the filename, so a source whose name table says\n * something else is unreachable from the runs that reference it. It fails\n * silently: on a machine that happens to have the real family installed the\n * reference lands there instead, and only a host without it (a container,\n * a colleague's laptop) shows the fallback.\n *\n * The case that forced this: Inter resolves through an upstream override\n * that instances `InterVariable.ttf` per weight, and harfbuzz keeps the\n * master's name table — so every instance called itself \"Inter Variable\".\n * The weighted faces were saved by the preview stager renaming them to\n * \"Inter Medium\" / \"Inter SemiBold\" on the way out; weights 400 and 700\n * kept the family name unchanged (they ride the run's bold/italic toggles\n * instead), so those alone were staged under a name no run asks for.\n *\n * Matching on ANY declared name (family or typographic family) keeps a\n * legitimately-named static untouched: LifeSans-Medium.ttf registered as\n * \"Life Sans\" already answers to it via nameID 16, even though nameID 1\n * says \"Life Sans Medium\". Only bytes that answer to nothing get rewritten.\n */\nfunction stampResolvedFamily(\n source: ResolvedFontSource,\n family: string\n): ResolvedFontSource {\n if (source.format !== 'ttf' && source.format !== 'otf') return source;\n const declared = readFontFamilyNames(source.data);\n // Nothing declared: no claim to contradict, and nothing to repair against.\n if (declared.length === 0 || declared.includes(family)) return source;\n // The style this face occupies within `family`. The family IDs become\n // `family` for all four RIBBI faces, so full/PostScript names have to\n // carry the style or roman and italic collide on both.\n const subfamily = standardSubfamilyNames(\n source.weight,\n source.italic\n )?.legacy;\n return {\n ...source,\n data: rewriteFontFamilyName(source.data, family, subfamily),\n };\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 raw of materialized) {\n const sfnt = raw.format === 'ttf' || raw.format === 'otf';\n // Can a font system load these bytes at all? Asked first, and on\n // failure asked instead of everything below: the stamp has no name\n // table to write into and the metadata checks no records to read,\n // so both would quietly do nothing and report nothing. The source\n // is still returned — this diagnoses the file, it doesn't decide\n // for the caller whether to ship it.\n const broken = sfnt\n ? validateFontStructure(\n raw.data,\n raw.weight,\n raw.italic,\n entry.family\n )\n : null;\n if (broken) {\n warnings.push(`[${broken.code}] ${broken.message}`);\n sources.push(raw);\n continue;\n }\n const s = stampResolvedFamily(raw, entry.family);\n if (sfnt) {\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 * DOCX legacy slots and both formats' new `palette` roles may name another\n * token. Legacy PPTX `colors` slots accept hex only at validation, though\n * both runtime resolvers walk references 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 * The document's typography, expressed as Highcharts options.\n *\n * A `highcharts` component is a PNG drawn by a browser that has never seen the\n * document, so nothing about the page's type reaches the chart on its own: the\n * axis labels, title and legend come out in the export server's default face\n * at Highcharts' own sizes, visibly foreign to the prose around them. The\n * palette already carries (see `chart-palette.ts`); this carries the type.\n *\n * Format-neutral on purpose. Each core reads its own theme shape into a\n * `ChartTypography` — family, colours and sizes in document points — and this\n * module turns that into the option paths Highcharts styles text through. An\n * explicit author value keeps winning, property by property, exactly as\n * `options.colors` does.\n *\n * Sizes are converted from points to chart pixels through the scale the chart\n * is placed at: a 900px chart set into a 450pt measure shrinks by half, so a\n * label that must read as 9pt on the page is drawn at 18px.\n */\n\nimport type { RasterizeFontFace } from '../types/services';\nimport type { FontRegistryEntry } from '../schemas/font-catalog';\nimport { themeFontRegistry } from '../fonts/document-registry';\n\nexport interface ChartTypography {\n /** CSS `font-family` for everything not styled otherwise (see `cssFontFamily`). */\n bodyFamily: string;\n /** CSS `font-family` for the chart title. */\n headingFamily: string;\n /** `#RRGGBB` for the title, legend and data labels. */\n textColor: string;\n /** `#RRGGBB` for axis text, subtitle, caption and credits. */\n mutedColor: string;\n /** Axis labels, axis titles, legend, subtitle and data labels, in points. */\n labelPt: number;\n /** Weight for legend items and data labels; Highcharts' own default when unset. */\n labelWeight?: number;\n /** Chart title, in points. */\n titlePt: number;\n /** Chart title weight; Highcharts' own default when unset. */\n titleWeight?: number;\n /** Credits (the source line) and caption, in points. */\n sourcePt: number;\n}\n\n/** Points per CSS pixel at the 96 dpi both formats assume for an unplaced chart. */\nexport const POINTS_PER_PIXEL_96DPI = 0.75;\n\n/**\n * How many document points one chart pixel occupies once the image is placed.\n * Unknown or degenerate widths fall back to 96 dpi, the size an unscaled\n * chart has in both formats.\n */\nexport function chartPointsPerPixel(\n chartWidthPx: number,\n placedWidthPt: number | undefined\n): number {\n if (\n !Number.isFinite(chartWidthPx) ||\n chartWidthPx <= 0 ||\n placedWidthPt === undefined ||\n !Number.isFinite(placedWidthPt) ||\n placedWidthPt <= 0\n ) {\n return POINTS_PER_PIXEL_96DPI;\n }\n return placedWidthPt / chartWidthPx;\n}\n\nconst SERIF_FAMILIES = new Set(['georgia', 'times new roman', 'cambria']);\nconst MONO_FAMILIES = new Set(['consolas', 'courier new', 'menlo', 'monaco']);\n\ntype FontCategory = NonNullable<FontRegistryEntry['category']>;\n\n/**\n * A CSS `font-family` list: the family, quoted, then the generic it belongs\n * to — from the registry category when the font is registered, from the\n * SAFE_FONTS list otherwise — so a face the export server lacks degrades to\n * the right shape rather than to the browser's default.\n */\nexport function cssFontFamily(family: string, category?: FontCategory): string {\n const generic =\n category === 'serif'\n ? 'serif'\n : category === 'mono'\n ? 'monospace'\n : category === 'handwriting'\n ? 'cursive'\n : category === undefined && SERIF_FAMILIES.has(family.toLowerCase())\n ? 'serif'\n : category === undefined && MONO_FAMILIES.has(family.toLowerCase())\n ? 'monospace'\n : 'sans-serif';\n return `\"${family.replace(/[\"\\\\]/g, '\\\\$&')}\", ${generic}`;\n}\n\n/**\n * `cssFontFamily` bound to a theme: a registered family answers with its\n * registry category, an unregistered one with what SAFE_FONTS says of it.\n */\nexport function chartFamilyResolver(\n theme: unknown\n): (family: string) => string {\n const categories = new Map(\n themeFontRegistry(theme).map((entry) => [\n entry.family.toLowerCase(),\n entry.category,\n ])\n );\n return (family) =>\n cssFontFamily(family, categories.get(family.toLowerCase()));\n}\n\ntype Options = Record<string, unknown>;\n\nfunction isPlainObject(value: unknown): value is Options {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * `defaults` beneath `authored`: an authored key is never replaced, and an\n * authored value that is not an object — `null`, `false` — is kept as it is\n * rather than turned into a defaulted object.\n */\nfunction fill(authored: unknown, defaults: Options): unknown {\n if (authored !== undefined && !isPlainObject(authored)) return authored;\n const base: Options = isPlainObject(authored) ? { ...authored } : {};\n for (const [key, value] of Object.entries(defaults)) {\n if (value === undefined) continue;\n const current = base[key];\n if (current === undefined) {\n base[key] = isPlainObject(value) ? fill(undefined, value) : value;\n } else if (isPlainObject(current) && isPlainObject(value)) {\n base[key] = fill(current, value);\n }\n }\n return base;\n}\n\n/** Highcharts axes may be one object or an array of them. */\nfunction fillAxis(authored: unknown, defaults: Options): unknown {\n if (Array.isArray(authored)) {\n return authored.map((axis) => fill(axis, defaults));\n }\n return fill(authored, defaults);\n}\n\nfunction weight(value: number | undefined): string | undefined {\n return value === undefined ? undefined : String(value);\n}\n\n/**\n * The document's typography written into every Highcharts option path that\n * styles text, beneath whatever the author set. `ptPerPx` is the placement\n * scale from `chartPointsPerPixel`.\n */\nexport function withChartTypography<T extends Options>(\n options: T,\n typography: ChartTypography,\n ptPerPx: number\n): T {\n const px = (points: number): string =>\n `${Math.round((points / ptPerPx) * 10) / 10}px`;\n const labelPx = px(typography.labelPt);\n const sourcePx = px(typography.sourcePt);\n const mutedText = { fontSize: labelPx, color: typography.mutedColor };\n const mutedSource = { fontSize: sourcePx, color: typography.mutedColor };\n const labelText = {\n fontSize: labelPx,\n color: typography.textColor,\n fontWeight: weight(typography.labelWeight),\n };\n const axis = { labels: { style: mutedText }, title: { style: mutedText } };\n\n return {\n ...options,\n chart: fill(options.chart, {\n style: { fontFamily: typography.bodyFamily },\n }),\n title: fill(options.title, {\n style: {\n fontFamily: typography.headingFamily,\n fontSize: px(typography.titlePt),\n fontWeight: weight(typography.titleWeight),\n color: typography.textColor,\n },\n }),\n subtitle: fill(options.subtitle, { style: mutedText }),\n caption: fill(options.caption, { style: mutedSource }),\n xAxis: fillAxis(options.xAxis, axis),\n yAxis: fillAxis(options.yAxis, axis),\n legend: fill(options.legend, { itemStyle: labelText }),\n plotOptions: fill(options.plotOptions, {\n series: { dataLabels: { style: labelText } },\n }),\n credits: fill(options.credits, { style: mutedSource }),\n };\n}\n\nconst FONT_FORMATS: Record<\n NonNullable<RasterizeFontFace['format']>,\n { mime: string; format: string }\n> = {\n ttf: { mime: 'font/ttf', format: 'truetype' },\n otf: { mime: 'font/otf', format: 'opentype' },\n woff: { mime: 'font/woff', format: 'woff' },\n woff2: { mime: 'font/woff2', format: 'woff2' },\n};\n\n/**\n * `@font-face` rules for the faces of `families`, inlined as data URIs, so an\n * export server draws a registered font from the same bytes the document\n * stages rather than from whatever its host happens to have installed. The\n * bytes go only to the export server, which already receives every data\n * point of the chart. Empty when no face matches.\n */\nexport function chartFontFaceCss(\n faces: readonly RasterizeFontFace[],\n families: readonly string[]\n): string {\n const wanted = new Set(families.map((family) => family.toLowerCase()));\n return faces\n .filter((face) => wanted.has(face.family.toLowerCase()))\n .map((face) => {\n const { mime, format } = FONT_FORMATS[face.format ?? 'ttf'];\n return (\n `@font-face{font-family:\"${face.family.replace(/[\"\\\\]/g, '\\\\$&')}\";` +\n `font-weight:${face.weight};font-style:${face.italic ? 'italic' : 'normal'};` +\n `src:url(data:${mime};base64,${face.data}) format(\"${format}\")}`\n );\n })\n .join('\\n');\n}\n\n/**\n * The `@font-face` rules for `families` written into a chart's `resources.css`\n * ahead of whatever the author supplied there. Nothing changes when no face\n * matches, so a chart set in safe fonts posts the same request it always did.\n */\nexport function withChartFontFaceCss<\n T extends { resources?: { css?: string } },\n>(\n props: T,\n faces: readonly RasterizeFontFace[],\n families: readonly string[]\n): T {\n const css = chartFontFaceCss(faces, families);\n if (!css) return props;\n const authored = props.resources?.css;\n return {\n ...props,\n resources: {\n ...props.resources,\n css: authored ? `${css}\\n${authored}` : css,\n },\n };\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":["weight","weight","cacheKey","weight","weight","HEADER_SIZE","TABLE_RECORD_SIZE","weight","weight","referenced","warnings"]}
|