@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.
@@ -123,5 +123,13 @@ declare function toRasterizeFontFaces(fonts: readonly ResolvedFont[], warnings?:
123
123
  * matching how the registry keys resolved fonts.
124
124
  */
125
125
  declare function fromRasterizeFontFaces(faces: readonly RasterizeFontFace[]): ResolvedFont[];
126
+ /**
127
+ * Flatten resolved fonts into the faces a Highcharts export server can be
128
+ * handed as inline `@font-face` rules. Wider than `toRasterizeFontFaces`:
129
+ * the chart is drawn by Chromium, which reads WOFF and WOFF2 as readily as
130
+ * an sfnt, so only formats no browser loads are dropped. Safe-only fonts
131
+ * carry no bytes and are skipped; the server's own host faces cover them.
132
+ */
133
+ declare function toChartFontFaces(fonts: readonly ResolvedFont[]): RasterizeFontFace[];
126
134
 
127
- export { FontDiskCache, type VariableFetchOptions, fetchVariableFontSource, fromRasterizeFontFaces, loadFileFontSource, toRasterizeFontFaces };
135
+ export { FontDiskCache, type VariableFetchOptions, fetchVariableFontSource, fromRasterizeFontFaces, loadFileFontSource, toChartFontFaces, toRasterizeFontFaces };
@@ -279,11 +279,34 @@ function fromRasterizeFontFaces(faces) {
279
279
  }
280
280
  return [...byFamily.values()];
281
281
  }
282
+ var BROWSER_FORMATS = /* @__PURE__ */ new Set([
283
+ "ttf",
284
+ "otf",
285
+ "woff",
286
+ "woff2"
287
+ ]);
288
+ function toChartFontFaces(fonts) {
289
+ const faces = [];
290
+ for (const font of fonts) {
291
+ for (const source of font.sources) {
292
+ if (!BROWSER_FORMATS.has(source.format)) continue;
293
+ faces.push({
294
+ family: font.family,
295
+ weight: source.weight,
296
+ italic: source.italic,
297
+ data: source.data.toString("base64"),
298
+ format: source.format
299
+ });
300
+ }
301
+ }
302
+ return faces;
303
+ }
282
304
  export {
283
305
  FontDiskCache,
284
306
  fetchVariableFontSource,
285
307
  fromRasterizeFontFaces,
286
308
  loadFileFontSource,
309
+ toChartFontFaces,
287
310
  toRasterizeFontFaces
288
311
  };
289
312
  //# sourceMappingURL=node.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/fonts/sources/file-loader.ts","../../src/fonts/cache/disk-cache.ts","../../src/fonts/sources/variable-fetcher.ts","../../src/fonts/rasterize-faces.ts"],"sourcesContent":["/**\n * Load a .ttf/.otf file from disk.\n * Node-only — called from the render pipeline.\n */\n\nimport { readFile } from 'fs/promises';\nimport { isAbsolute, resolve as resolvePath } from 'path';\nimport type { ResolvedFontSource } from '../types';\nimport { detectFontFormat } from './format';\n\nexport interface FileSourceInput {\n path: string;\n weight?: number;\n italic?: boolean;\n baseDir?: string;\n}\n\n/** Read a font file and wrap as a ResolvedFontSource. */\nexport async function loadFileFontSource(\n input: FileSourceInput\n): Promise<ResolvedFontSource> {\n const fullPath = isAbsolute(input.path)\n ? input.path\n : resolvePath(input.baseDir ?? process.cwd(), input.path);\n const data = await readFile(fullPath);\n const format = detectFontFormat(data);\n if (format === 'unknown') {\n throw new Error(\n `Font file at \"${fullPath}\" is not a recognized font file (expected TTF/OTF/WOFF/WOFF2)`\n );\n }\n // No format rejection here: bytes flow to the LibreOffice preview\n // stager, which handles WOFF/WOFF2 natively via fontconfig on\n // Linux/macOS. Office output never embeds these bytes — substitute/\n // custom modes rely on recipient-side fonts.\n return {\n data,\n weight: input.weight ?? 400,\n italic: input.italic ?? false,\n format,\n };\n}\n","/**\n * On-disk cache for fetched Google Fonts TTFs.\n * Optional — only active when a cacheDir is provided. Node-only.\n */\n\nimport { createHash } from 'crypto';\nimport { mkdir, readFile, writeFile } from 'fs/promises';\nimport { join } from 'path';\n\nexport class FontDiskCache {\n private readonly dir: string;\n // In-flight promise dedupes the first-write mkdir across concurrent set()\n // calls. Without it, two simultaneous cold-cache writes could both see\n // `ensured=false`, both issue mkdir, and both flip the flag afterwards —\n // harmless today (recursive mkdir is idempotent) but the pattern is\n // right and leaves room to add per-directory locks if we ever need to.\n private ensurePromise: Promise<void> | null = null;\n\n constructor(dir: string) {\n this.dir = dir;\n }\n\n private ensureDir(): Promise<void> {\n if (!this.ensurePromise) {\n this.ensurePromise = mkdir(this.dir, { recursive: true }).then(\n () => undefined\n );\n }\n return this.ensurePromise;\n }\n\n private pathFor(key: string): string {\n const hash = createHash('sha256').update(key).digest('hex').slice(0, 24);\n return join(this.dir, `${hash}.bin`);\n }\n\n async get(key: string): Promise<Buffer | undefined> {\n try {\n return await readFile(this.pathFor(key));\n } catch {\n return undefined;\n }\n }\n\n async set(key: string, value: Buffer): Promise<void> {\n await this.ensureDir();\n await writeFile(this.pathFor(key), value);\n }\n}\n","/**\n * Variable-font instancer. Fetches a variable font once (disk-cached; TTF,\n * OTF, or WOFF/WOFF2 — fontverter converts compressed containers before\n * instancing), then pins its `wght` axis (plus any additional axes) to\n * produce a clean static TTF per requested weight. Uses harfbuzz via\n * `subset-font` — pure JS + WASM, no native toolchain.\n *\n * Why this exists. Google Fonts serves pre-instanced static TTFs for many\n * families, but the instancing step is lossy: Inter Thin (100) and\n * ExtraLight (200) both ship with `OS/2.usWeightClass=250` and near-\n * identical glyph outlines (xAvgCharWidth differs by 1.8%, glyf table\n * differs by 83 bytes out of 135 KB). Pinning the upstream variable TTF's\n * `wght` axis at exactly 100 vs 200 produces properly distinct instances.\n *\n * Cache strategy:\n * 1. Raw variable font cached at key `varsrc|<url>` — one download per URL\n * per process (+ optional disk layer).\n * 2. Instanced static TTF cached at `variable2|<url>|<weight>|<italic>` —\n * avoids re-running harfbuzz for weights we've already produced.\n *\n * Full-glyph retention. subset-font's `text` parameter drives which\n * codepoints' glyphs survive. We pass every BMP codepoint so the output\n * is effectively a full-glyph static (not a subset) for any Latin /\n * Cyrillic / Greek / Vietnamese-covering family — which includes every\n * entry in our POPULAR_GOOGLE_FONTS catalog. Supplementary-plane glyphs\n * (emoji) would be dropped, but those aren't in the variable families we\n * target. `preserveNameIds` keeps the human-readable name records our\n * downstream normalization expects.\n */\n\nimport type { ResolvedFontSource } from '../types';\nimport { detectFontFormat } from './format';\nimport { rewriteFontSubfamilyNames } from './ttf-name';\nimport { isAllowedFontUrl } from './url-allowlist';\n\n// `subset-font` carries a harfbuzz WASM payload and is Node-only. Lazy-load\n// so a browser bundler that chases the generic `sources/` tree doesn't pull\n// it in. Cached across calls so the WASM heap is created once per process.\nlet subsetFontPromise: Promise<typeof import('subset-font').default> | null =\n null;\nfunction loadSubsetFont(): Promise<typeof import('subset-font').default> {\n if (!subsetFontPromise) {\n subsetFontPromise = import('subset-font').then((m) => m.default);\n }\n return subsetFontPromise;\n}\n\nexport interface VariableFetchOptions {\n url: string;\n weight: number;\n italic: boolean;\n /** Extra axis pins merged on top of the derived `wght` pin (e.g. `ital`,\n * `opsz`, `slnt`). Rare — the `weight`/`italic` pair is usually enough. */\n axes?: Record<string, number>;\n /** Family label used in error messages and diagnostics. */\n familyLabel?: string;\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 rawCacheKey(url: string): string {\n return `varsrc|${url}`;\n}\n\nfunction instanceCacheKey(\n url: string,\n weight: number,\n italic: boolean,\n axes?: Record<string, number>\n): string {\n // Axes go into the key deterministically so different axis pins don't\n // collide. Sorted so `{a:1,b:2}` and `{b:2,a:1}` hash the same.\n const axisPart = axes\n ? '|' +\n Object.entries(axes)\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([k, v]) => `${k}=${v}`)\n .join(',')\n : '';\n // `variable2`: v2 stamps standard subfamily names into the instanced\n // output — bumped so persistent disk caches drop pre-stamp instances.\n return `variable2|${url}|${weight}|${italic ? 'i' : 'r'}${axisPart}`;\n}\n\n/**\n * String covering every assigned BMP codepoint (0x20-0xFFFF minus surrogate\n * range). Built lazily on first use — ~127 KiB of UTF-16 memory (0xFFFF\n * codepoints × 2 bytes per UTF-16 code unit, minus the surrogate range)\n * held for the lifetime of the process, which is negligible next to the\n * WASM heap harfbuzz already carries.\n */\nlet cachedBmpCharset: string | null = null;\nfunction bmpCharset(): string {\n if (cachedBmpCharset) return cachedBmpCharset;\n let s = '';\n for (let cp = 0x20; cp <= 0xffff; cp++) {\n // Surrogate range is structurally invalid as standalone codepoints —\n // harfbuzz rejects them. Skip.\n if (cp >= 0xd800 && cp <= 0xdfff) continue;\n s += String.fromCodePoint(cp);\n }\n cachedBmpCharset = s;\n return s;\n}\n\ntype FetchResult = { buf: Buffer } | { error: string };\n\nasync function fetchVariableSource(\n opts: VariableFetchOptions\n): Promise<FetchResult> {\n if (!isAllowedFontUrl(opts.url)) {\n return { error: 'host not in allowlist or non-HTTPS' };\n }\n const key = rawCacheKey(opts.url);\n const mem = opts.memoryCache?.get(key);\n if (mem) return { buf: mem };\n const disk = await opts.diskCache?.get(key);\n if (disk) {\n opts.memoryCache?.set(key, disk);\n return { buf: disk };\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' so the allowlist can't be bypassed via Location.\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) return { error: `${res.status} with no Location` };\n const resolved = new URL(next, opts.url).toString();\n if (!isAllowedFontUrl(resolved)) {\n return { error: `redirect to disallowed host: ${resolved}` };\n }\n if (++hops > 3) return { error: 'too many redirects' };\n res = await f(resolved, { signal: ctrl.signal, redirect: 'manual' });\n }\n if (!res.ok) return { error: `HTTP ${res.status} ${res.statusText}` };\n const ab = await res.arrayBuffer();\n const buf = Buffer.from(ab);\n // Sanity-check: reject sub-1KB or non-TTF responses up front. The\n // instancer would fail loudly on garbage, but a clear \"wrong URL\"\n // signal here shortens the debug cycle.\n if (buf.length < 1024)\n return { error: `response too small (${buf.length}B)` };\n const format = detectFontFormat(buf);\n // WOFF/WOFF2 sources are fine: subset-font funnels every input through\n // fontverter (sfnt/woff/woff2 → truetype) before harfbuzz sees it, and\n // the instanced output is always plain sfnt. Needed in practice —\n // rsms/inter publishes its italic variable master only as woff2.\n if (\n format !== 'ttf' &&\n format !== 'otf' &&\n format !== 'woff' &&\n format !== 'woff2'\n ) {\n return { error: `unexpected font format: ${format}` };\n }\n opts.memoryCache?.set(key, buf);\n await opts.diskCache?.set(key, buf);\n return { buf };\n } catch (err) {\n return { error: (err as Error).message };\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport async function fetchVariableFontSource(\n opts: VariableFetchOptions\n): Promise<{ source?: ResolvedFontSource; warnings?: string[] }> {\n const key = instanceCacheKey(opts.url, opts.weight, opts.italic, opts.axes);\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 fetched = await fetchVariableSource(opts);\n if ('error' in fetched) {\n return {\n warnings: [\n `Variable font fetch \"${opts.url}\" for \"${opts.familyLabel ?? opts.url}\" weight ${opts.weight}: ${fetched.error}; falling back to host defaults.`,\n ],\n };\n }\n const raw = fetched.buf;\n\n // Harfbuzz refuses to emit WOFF2 for subset-font's default SFNT target,\n // but we need plain SFNT anyway — Office embeds TTFs, not compressed\n // formats. Pin the weight (and any extra axes) and preserve the name\n // records that our downstream name rewrites (`rewriteFontFamilyName`,\n // `rewriteFontSubfamilyNames`) depend on.\n //\n // Note: italic is encoded by URL (separate italic master), not by axis pin.\n // The `ital` axis exists on some fonts but not others (Inter ships a\n // separate InterVariable-Italic.ttf instead). Callers that want to force\n // an axis pin can pass `axes: { ital: 1 }` explicitly.\n const variationAxes: Record<string, number> = {\n wght: opts.weight,\n ...(opts.axes ?? {}),\n };\n\n let instanced: Buffer;\n try {\n const subsetFont = await loadSubsetFont();\n instanced = await subsetFont(raw, bmpCharset(), {\n targetFormat: 'sfnt',\n variationAxes,\n // Keep every common name record. harfbuzz drops the ones not in\n // this list; our downstream rewrites need 1/2/4/6/16/17 intact.\n preserveNameIds: [\n 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,\n 20, 21, 22, 23, 24, 25,\n ],\n });\n } catch (err) {\n return {\n warnings: [\n `Variable font instancing for \"${opts.familyLabel ?? opts.url}\" weight ${opts.weight}: ${(err as Error).message}`,\n ],\n };\n }\n\n // harfbuzz preserves the source's name records verbatim, so the instanced\n // static still carries the variable font's default-instance subfamily\n // (typically \"Regular\") in nameID 2/17 — which would trip\n // validateFontMetadata's FONT_METADATA_DEFECT warning for every non-\n // Regular weight. Stamp the standard subfamily for the pinned pair.\n instanced = rewriteFontSubfamilyNames(instanced, opts.weight, opts.italic);\n\n opts.memoryCache?.set(key, instanced);\n await opts.diskCache?.set(key, instanced);\n return {\n source: {\n data: instanced,\n weight: opts.weight,\n italic: opts.italic,\n format: detectFontFormat(instanced),\n },\n warnings: [],\n };\n}\n","/**\n * `ResolvedFont[]` ⇄ `RasterizeFontFace[]` — the one encoder/decoder pair for\n * shipping font bytes to the pptx rasterizer.\n *\n * The docx side encodes (core-docx, from `resolveDocumentFonts`) and the\n * rasterizer side decodes (jto-cli, before handing the faces to a\n * `FontStager`). Keeping both halves here means the two cannot drift on\n * base64 handling or on the family-name convention.\n *\n * FAMILY NAMES STAY UNSYNTHESIZED. The wire carries the catalog family\n * (\"Inter\"); the stager applies `synthesizeFamilyName` +\n * `rewriteFontFamilyName` to produce the sub-family the presentation\n * actually references (\"Inter Light\"). Encoding a pre-synthesized name here\n * would make the stager apply the suffix twice.\n *\n * Buffer-dependent → Node-only. Exported from `@json-to-office/shared/fonts/node`.\n */\n\nimport type { ResolvedFont, ResolvedFontSource } from './types';\nimport type { RasterizeFontFace } from '../types/services';\nimport type { GenerationWarning } from '../types/warnings';\n\n/**\n * Formats the rasterizer's native stagers can actually register.\n *\n * All three stagers (fontconfig, macOS Core Text, Windows GDI) write every\n * staged source as a `.ttf` and register it as a raw sfnt, and they rename\n * the face through `rewriteFontFamilyName`, which returns the buffer\n * UNCHANGED for anything without an sfnt header. So a WOFF/WOFF2 (or EOT, or\n * PostScript) source is staged as bytes no font system parses, under the\n * catalog family rather than the synthesized sub-family the presentation\n * references — it renders as fallback text, silently.\n *\n * Shipping those bytes anyway costs wire size, disk writes, and a distinct\n * rasterizer cache key for a render that is identical to the fontless one.\n * An allowlist (rather than a WOFF denylist) keeps any format added to\n * `ResolvedFontSource['format']` later excluded until a stager can handle it.\n */\nconst STAGEABLE_FORMATS = new Set<ResolvedFontSource['format']>(['ttf', 'otf']);\n\n/**\n * Flatten resolved fonts into the serializable wire faces (one face per\n * source variant). Entries with no sources — safe-only fonts, which the\n * renderer resolves against system faces — carry no bytes and are skipped,\n * as are sources in a format no stager can register.\n *\n * @param warnings - sink for one warning per dropped source, shaped like every\n * other generation warning so a caller can hand in the same array it already\n * collects. Both docx entry paths do: a dropped face renders as a fallback,\n * which is precisely the silent substitution this pipeline exists to make\n * visible, so it must not be discoverable only by reading the code.\n */\nexport function toRasterizeFontFaces(\n fonts: readonly ResolvedFont[],\n warnings?: GenerationWarning[]\n): RasterizeFontFace[] {\n const faces: RasterizeFontFace[] = [];\n for (const font of fonts) {\n if (font.sources.length === 0) continue;\n for (const source of font.sources) {\n if (!STAGEABLE_FORMATS.has(source.format)) {\n warnings?.push({\n component: 'fontRegistry',\n severity: 'warning',\n context: { code: 'FONT_FORMAT_NOT_RASTERIZABLE' },\n message:\n `\"${font.family}\" weight ${source.weight}` +\n `${source.italic ? ' italic' : ''} is ${source.format}; the rasterizer's ` +\n `font stagers only register TTF/OTF, so this face is omitted and the ` +\n `visual renders with a fallback face.`,\n });\n continue;\n }\n faces.push({\n family: font.family,\n weight: source.weight,\n italic: source.italic,\n data: source.data.toString('base64'),\n format: source.format as RasterizeFontFace['format'],\n });\n }\n }\n return faces;\n}\n\n/**\n * Inverse of {@link toRasterizeFontFaces}: regroup wire faces back into\n * `ResolvedFont[]` so the existing `FontStager.stage(ResolvedFont[], …)`\n * signature needs no change. Grouping is by exact (case-sensitive) family,\n * matching how the registry keys resolved fonts.\n */\nexport function fromRasterizeFontFaces(\n faces: readonly RasterizeFontFace[]\n): ResolvedFont[] {\n const byFamily = new Map<string, ResolvedFont>();\n for (const face of faces) {\n let font = byFamily.get(face.family);\n if (!font) {\n font = { family: face.family, sources: [], warnings: [] };\n byFamily.set(face.family, font);\n }\n const source: ResolvedFontSource = {\n data: Buffer.from(face.data, 'base64'),\n weight: face.weight,\n italic: face.italic,\n format: face.format ?? 'ttf',\n };\n font.sources.push(source);\n }\n return [...byFamily.values()];\n}\n"],"mappings":"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","names":["readFile"]}
1
+ {"version":3,"sources":["../../src/fonts/sources/file-loader.ts","../../src/fonts/cache/disk-cache.ts","../../src/fonts/sources/variable-fetcher.ts","../../src/fonts/rasterize-faces.ts"],"sourcesContent":["/**\n * Load a .ttf/.otf file from disk.\n * Node-only — called from the render pipeline.\n */\n\nimport { readFile } from 'fs/promises';\nimport { isAbsolute, resolve as resolvePath } from 'path';\nimport type { ResolvedFontSource } from '../types';\nimport { detectFontFormat } from './format';\n\nexport interface FileSourceInput {\n path: string;\n weight?: number;\n italic?: boolean;\n baseDir?: string;\n}\n\n/** Read a font file and wrap as a ResolvedFontSource. */\nexport async function loadFileFontSource(\n input: FileSourceInput\n): Promise<ResolvedFontSource> {\n const fullPath = isAbsolute(input.path)\n ? input.path\n : resolvePath(input.baseDir ?? process.cwd(), input.path);\n const data = await readFile(fullPath);\n const format = detectFontFormat(data);\n if (format === 'unknown') {\n throw new Error(\n `Font file at \"${fullPath}\" is not a recognized font file (expected TTF/OTF/WOFF/WOFF2)`\n );\n }\n // No format rejection here: bytes flow to the LibreOffice preview\n // stager, which handles WOFF/WOFF2 natively via fontconfig on\n // Linux/macOS. Office output never embeds these bytes — substitute/\n // custom modes rely on recipient-side fonts.\n return {\n data,\n weight: input.weight ?? 400,\n italic: input.italic ?? false,\n format,\n };\n}\n","/**\n * On-disk cache for fetched Google Fonts TTFs.\n * Optional — only active when a cacheDir is provided. Node-only.\n */\n\nimport { createHash } from 'crypto';\nimport { mkdir, readFile, writeFile } from 'fs/promises';\nimport { join } from 'path';\n\nexport class FontDiskCache {\n private readonly dir: string;\n // In-flight promise dedupes the first-write mkdir across concurrent set()\n // calls. Without it, two simultaneous cold-cache writes could both see\n // `ensured=false`, both issue mkdir, and both flip the flag afterwards —\n // harmless today (recursive mkdir is idempotent) but the pattern is\n // right and leaves room to add per-directory locks if we ever need to.\n private ensurePromise: Promise<void> | null = null;\n\n constructor(dir: string) {\n this.dir = dir;\n }\n\n private ensureDir(): Promise<void> {\n if (!this.ensurePromise) {\n this.ensurePromise = mkdir(this.dir, { recursive: true }).then(\n () => undefined\n );\n }\n return this.ensurePromise;\n }\n\n private pathFor(key: string): string {\n const hash = createHash('sha256').update(key).digest('hex').slice(0, 24);\n return join(this.dir, `${hash}.bin`);\n }\n\n async get(key: string): Promise<Buffer | undefined> {\n try {\n return await readFile(this.pathFor(key));\n } catch {\n return undefined;\n }\n }\n\n async set(key: string, value: Buffer): Promise<void> {\n await this.ensureDir();\n await writeFile(this.pathFor(key), value);\n }\n}\n","/**\n * Variable-font instancer. Fetches a variable font once (disk-cached; TTF,\n * OTF, or WOFF/WOFF2 — fontverter converts compressed containers before\n * instancing), then pins its `wght` axis (plus any additional axes) to\n * produce a clean static TTF per requested weight. Uses harfbuzz via\n * `subset-font` — pure JS + WASM, no native toolchain.\n *\n * Why this exists. Google Fonts serves pre-instanced static TTFs for many\n * families, but the instancing step is lossy: Inter Thin (100) and\n * ExtraLight (200) both ship with `OS/2.usWeightClass=250` and near-\n * identical glyph outlines (xAvgCharWidth differs by 1.8%, glyf table\n * differs by 83 bytes out of 135 KB). Pinning the upstream variable TTF's\n * `wght` axis at exactly 100 vs 200 produces properly distinct instances.\n *\n * Cache strategy:\n * 1. Raw variable font cached at key `varsrc|<url>` — one download per URL\n * per process (+ optional disk layer).\n * 2. Instanced static TTF cached at `variable2|<url>|<weight>|<italic>` —\n * avoids re-running harfbuzz for weights we've already produced.\n *\n * Full-glyph retention. subset-font's `text` parameter drives which\n * codepoints' glyphs survive. We pass every BMP codepoint so the output\n * is effectively a full-glyph static (not a subset) for any Latin /\n * Cyrillic / Greek / Vietnamese-covering family — which includes every\n * entry in our POPULAR_GOOGLE_FONTS catalog. Supplementary-plane glyphs\n * (emoji) would be dropped, but those aren't in the variable families we\n * target. `preserveNameIds` keeps the human-readable name records our\n * downstream normalization expects.\n */\n\nimport type { ResolvedFontSource } from '../types';\nimport { detectFontFormat } from './format';\nimport { rewriteFontSubfamilyNames } from './ttf-name';\nimport { isAllowedFontUrl } from './url-allowlist';\n\n// `subset-font` carries a harfbuzz WASM payload and is Node-only. Lazy-load\n// so a browser bundler that chases the generic `sources/` tree doesn't pull\n// it in. Cached across calls so the WASM heap is created once per process.\nlet subsetFontPromise: Promise<typeof import('subset-font').default> | null =\n null;\nfunction loadSubsetFont(): Promise<typeof import('subset-font').default> {\n if (!subsetFontPromise) {\n subsetFontPromise = import('subset-font').then((m) => m.default);\n }\n return subsetFontPromise;\n}\n\nexport interface VariableFetchOptions {\n url: string;\n weight: number;\n italic: boolean;\n /** Extra axis pins merged on top of the derived `wght` pin (e.g. `ital`,\n * `opsz`, `slnt`). Rare — the `weight`/`italic` pair is usually enough. */\n axes?: Record<string, number>;\n /** Family label used in error messages and diagnostics. */\n familyLabel?: string;\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 rawCacheKey(url: string): string {\n return `varsrc|${url}`;\n}\n\nfunction instanceCacheKey(\n url: string,\n weight: number,\n italic: boolean,\n axes?: Record<string, number>\n): string {\n // Axes go into the key deterministically so different axis pins don't\n // collide. Sorted so `{a:1,b:2}` and `{b:2,a:1}` hash the same.\n const axisPart = axes\n ? '|' +\n Object.entries(axes)\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([k, v]) => `${k}=${v}`)\n .join(',')\n : '';\n // `variable2`: v2 stamps standard subfamily names into the instanced\n // output — bumped so persistent disk caches drop pre-stamp instances.\n return `variable2|${url}|${weight}|${italic ? 'i' : 'r'}${axisPart}`;\n}\n\n/**\n * String covering every assigned BMP codepoint (0x20-0xFFFF minus surrogate\n * range). Built lazily on first use — ~127 KiB of UTF-16 memory (0xFFFF\n * codepoints × 2 bytes per UTF-16 code unit, minus the surrogate range)\n * held for the lifetime of the process, which is negligible next to the\n * WASM heap harfbuzz already carries.\n */\nlet cachedBmpCharset: string | null = null;\nfunction bmpCharset(): string {\n if (cachedBmpCharset) return cachedBmpCharset;\n let s = '';\n for (let cp = 0x20; cp <= 0xffff; cp++) {\n // Surrogate range is structurally invalid as standalone codepoints —\n // harfbuzz rejects them. Skip.\n if (cp >= 0xd800 && cp <= 0xdfff) continue;\n s += String.fromCodePoint(cp);\n }\n cachedBmpCharset = s;\n return s;\n}\n\ntype FetchResult = { buf: Buffer } | { error: string };\n\nasync function fetchVariableSource(\n opts: VariableFetchOptions\n): Promise<FetchResult> {\n if (!isAllowedFontUrl(opts.url)) {\n return { error: 'host not in allowlist or non-HTTPS' };\n }\n const key = rawCacheKey(opts.url);\n const mem = opts.memoryCache?.get(key);\n if (mem) return { buf: mem };\n const disk = await opts.diskCache?.get(key);\n if (disk) {\n opts.memoryCache?.set(key, disk);\n return { buf: disk };\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' so the allowlist can't be bypassed via Location.\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) return { error: `${res.status} with no Location` };\n const resolved = new URL(next, opts.url).toString();\n if (!isAllowedFontUrl(resolved)) {\n return { error: `redirect to disallowed host: ${resolved}` };\n }\n if (++hops > 3) return { error: 'too many redirects' };\n res = await f(resolved, { signal: ctrl.signal, redirect: 'manual' });\n }\n if (!res.ok) return { error: `HTTP ${res.status} ${res.statusText}` };\n const ab = await res.arrayBuffer();\n const buf = Buffer.from(ab);\n // Sanity-check: reject sub-1KB or non-TTF responses up front. The\n // instancer would fail loudly on garbage, but a clear \"wrong URL\"\n // signal here shortens the debug cycle.\n if (buf.length < 1024)\n return { error: `response too small (${buf.length}B)` };\n const format = detectFontFormat(buf);\n // WOFF/WOFF2 sources are fine: subset-font funnels every input through\n // fontverter (sfnt/woff/woff2 → truetype) before harfbuzz sees it, and\n // the instanced output is always plain sfnt. Needed in practice —\n // rsms/inter publishes its italic variable master only as woff2.\n if (\n format !== 'ttf' &&\n format !== 'otf' &&\n format !== 'woff' &&\n format !== 'woff2'\n ) {\n return { error: `unexpected font format: ${format}` };\n }\n opts.memoryCache?.set(key, buf);\n await opts.diskCache?.set(key, buf);\n return { buf };\n } catch (err) {\n return { error: (err as Error).message };\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport async function fetchVariableFontSource(\n opts: VariableFetchOptions\n): Promise<{ source?: ResolvedFontSource; warnings?: string[] }> {\n const key = instanceCacheKey(opts.url, opts.weight, opts.italic, opts.axes);\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 fetched = await fetchVariableSource(opts);\n if ('error' in fetched) {\n return {\n warnings: [\n `Variable font fetch \"${opts.url}\" for \"${opts.familyLabel ?? opts.url}\" weight ${opts.weight}: ${fetched.error}; falling back to host defaults.`,\n ],\n };\n }\n const raw = fetched.buf;\n\n // Harfbuzz refuses to emit WOFF2 for subset-font's default SFNT target,\n // but we need plain SFNT anyway — Office embeds TTFs, not compressed\n // formats. Pin the weight (and any extra axes) and preserve the name\n // records that our downstream name rewrites (`rewriteFontFamilyName`,\n // `rewriteFontSubfamilyNames`) depend on.\n //\n // Note: italic is encoded by URL (separate italic master), not by axis pin.\n // The `ital` axis exists on some fonts but not others (Inter ships a\n // separate InterVariable-Italic.ttf instead). Callers that want to force\n // an axis pin can pass `axes: { ital: 1 }` explicitly.\n const variationAxes: Record<string, number> = {\n wght: opts.weight,\n ...(opts.axes ?? {}),\n };\n\n let instanced: Buffer;\n try {\n const subsetFont = await loadSubsetFont();\n instanced = await subsetFont(raw, bmpCharset(), {\n targetFormat: 'sfnt',\n variationAxes,\n // Keep every common name record. harfbuzz drops the ones not in\n // this list; our downstream rewrites need 1/2/4/6/16/17 intact.\n preserveNameIds: [\n 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,\n 20, 21, 22, 23, 24, 25,\n ],\n });\n } catch (err) {\n return {\n warnings: [\n `Variable font instancing for \"${opts.familyLabel ?? opts.url}\" weight ${opts.weight}: ${(err as Error).message}`,\n ],\n };\n }\n\n // harfbuzz preserves the source's name records verbatim, so the instanced\n // static still carries the variable font's default-instance subfamily\n // (typically \"Regular\") in nameID 2/17 — which would trip\n // validateFontMetadata's FONT_METADATA_DEFECT warning for every non-\n // Regular weight. Stamp the standard subfamily for the pinned pair.\n instanced = rewriteFontSubfamilyNames(instanced, opts.weight, opts.italic);\n\n opts.memoryCache?.set(key, instanced);\n await opts.diskCache?.set(key, instanced);\n return {\n source: {\n data: instanced,\n weight: opts.weight,\n italic: opts.italic,\n format: detectFontFormat(instanced),\n },\n warnings: [],\n };\n}\n","/**\n * `ResolvedFont[]` ⇄ `RasterizeFontFace[]` — the one encoder/decoder pair for\n * shipping font bytes to the pptx rasterizer.\n *\n * The docx side encodes (core-docx, from `resolveDocumentFonts`) and the\n * rasterizer side decodes (jto-cli, before handing the faces to a\n * `FontStager`). Keeping both halves here means the two cannot drift on\n * base64 handling or on the family-name convention.\n *\n * FAMILY NAMES STAY UNSYNTHESIZED. The wire carries the catalog family\n * (\"Inter\"); the stager applies `synthesizeFamilyName` +\n * `rewriteFontFamilyName` to produce the sub-family the presentation\n * actually references (\"Inter Light\"). Encoding a pre-synthesized name here\n * would make the stager apply the suffix twice.\n *\n * Buffer-dependent → Node-only. Exported from `@json-to-office/shared/fonts/node`.\n */\n\nimport type { ResolvedFont, ResolvedFontSource } from './types';\nimport type { RasterizeFontFace } from '../types/services';\nimport type { GenerationWarning } from '../types/warnings';\n\n/**\n * Formats the rasterizer's native stagers can actually register.\n *\n * All three stagers (fontconfig, macOS Core Text, Windows GDI) write every\n * staged source as a `.ttf` and register it as a raw sfnt, and they rename\n * the face through `rewriteFontFamilyName`, which returns the buffer\n * UNCHANGED for anything without an sfnt header. So a WOFF/WOFF2 (or EOT, or\n * PostScript) source is staged as bytes no font system parses, under the\n * catalog family rather than the synthesized sub-family the presentation\n * references — it renders as fallback text, silently.\n *\n * Shipping those bytes anyway costs wire size, disk writes, and a distinct\n * rasterizer cache key for a render that is identical to the fontless one.\n * An allowlist (rather than a WOFF denylist) keeps any format added to\n * `ResolvedFontSource['format']` later excluded until a stager can handle it.\n */\nconst STAGEABLE_FORMATS = new Set<ResolvedFontSource['format']>(['ttf', 'otf']);\n\n/**\n * Flatten resolved fonts into the serializable wire faces (one face per\n * source variant). Entries with no sources — safe-only fonts, which the\n * renderer resolves against system faces — carry no bytes and are skipped,\n * as are sources in a format no stager can register.\n *\n * @param warnings - sink for one warning per dropped source, shaped like every\n * other generation warning so a caller can hand in the same array it already\n * collects. Both docx entry paths do: a dropped face renders as a fallback,\n * which is precisely the silent substitution this pipeline exists to make\n * visible, so it must not be discoverable only by reading the code.\n */\nexport function toRasterizeFontFaces(\n fonts: readonly ResolvedFont[],\n warnings?: GenerationWarning[]\n): RasterizeFontFace[] {\n const faces: RasterizeFontFace[] = [];\n for (const font of fonts) {\n if (font.sources.length === 0) continue;\n for (const source of font.sources) {\n if (!STAGEABLE_FORMATS.has(source.format)) {\n warnings?.push({\n component: 'fontRegistry',\n severity: 'warning',\n context: { code: 'FONT_FORMAT_NOT_RASTERIZABLE' },\n message:\n `\"${font.family}\" weight ${source.weight}` +\n `${source.italic ? ' italic' : ''} is ${source.format}; the rasterizer's ` +\n `font stagers only register TTF/OTF, so this face is omitted and the ` +\n `visual renders with a fallback face.`,\n });\n continue;\n }\n faces.push({\n family: font.family,\n weight: source.weight,\n italic: source.italic,\n data: source.data.toString('base64'),\n format: source.format as RasterizeFontFace['format'],\n });\n }\n }\n return faces;\n}\n\n/**\n * Inverse of {@link toRasterizeFontFaces}: regroup wire faces back into\n * `ResolvedFont[]` so the existing `FontStager.stage(ResolvedFont[], …)`\n * signature needs no change. Grouping is by exact (case-sensitive) family,\n * matching how the registry keys resolved fonts.\n */\nexport function fromRasterizeFontFaces(\n faces: readonly RasterizeFontFace[]\n): ResolvedFont[] {\n const byFamily = new Map<string, ResolvedFont>();\n for (const face of faces) {\n let font = byFamily.get(face.family);\n if (!font) {\n font = { family: face.family, sources: [], warnings: [] };\n byFamily.set(face.family, font);\n }\n const source: ResolvedFontSource = {\n data: Buffer.from(face.data, 'base64'),\n weight: face.weight,\n italic: face.italic,\n format: face.format ?? 'ttf',\n };\n font.sources.push(source);\n }\n return [...byFamily.values()];\n}\n\n/** Formats a browser's `@font-face` can load; a chart is drawn by one. */\nconst BROWSER_FORMATS = new Set<ResolvedFontSource['format']>([\n 'ttf',\n 'otf',\n 'woff',\n 'woff2',\n]);\n\n/**\n * Flatten resolved fonts into the faces a Highcharts export server can be\n * handed as inline `@font-face` rules. Wider than `toRasterizeFontFaces`:\n * the chart is drawn by Chromium, which reads WOFF and WOFF2 as readily as\n * an sfnt, so only formats no browser loads are dropped. Safe-only fonts\n * carry no bytes and are skipped; the server's own host faces cover them.\n */\nexport function toChartFontFaces(\n fonts: readonly ResolvedFont[]\n): RasterizeFontFace[] {\n const faces: RasterizeFontFace[] = [];\n for (const font of fonts) {\n for (const source of font.sources) {\n if (!BROWSER_FORMATS.has(source.format)) continue;\n faces.push({\n family: font.family,\n weight: source.weight,\n italic: source.italic,\n data: source.data.toString('base64'),\n format: source.format as RasterizeFontFace['format'],\n });\n }\n }\n return faces;\n}\n"],"mappings":"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","names":["readFile"]}
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { C as ComponentDefinition, a as ComponentSchemaConfig, c as convertToJsonSchema, b as createComponentSchema, d as createComponentSchemaObject, e as exportSchemaToFile, f as fixSchemaReferences } from './schema-utils-C5Qdzsgy.js';
2
2
  export { AddWarningFunction, GenerationWarning } from './types/warnings.js';
3
- import { F as FontRegistryEntry, c as FontRuntimeOpts, R as ResolvedFontSource, b as ResolvedFont } from './types-kcQwhOlf.js';
4
- export { D as DEFAULT_VISUAL_DPI, d as FontFamilyNameSchema, e as FontRegistryDefinition, f as FontRegistryEntrySchema, g as FontRegistrySchema, h as FontSource, i as FontSourceSchema, H as HighchartsHeaders, j as HighchartsHeadersResolver, k as HighchartsServiceConfig, M as MAX_RASTERIZE_BATCH_SLIDES, l as MAX_RASTERIZE_FONTS, m as MAX_RASTERIZE_FONT_BYTES, n as MAX_VISUAL_DPI, o as MIN_VISUAL_DPI, P as PptxBatchRasterizer, p as PptxRasterizeBatchRequest, q as PptxRasterizeBatchResult, r as PptxRasterizeBatchSlide, s as PptxRasterizeBatchSlideResult, t as PptxRasterizeFailureStage, u as PptxRasterizeRequest, v as PptxRasterizeResult, w as PptxRasterizer, x as PptxServiceConfig, y as PptxServiceHeaders, z as PptxServiceHeadersResolver, a as RasterizeFontFace, S as SAFE_FONTS, A as SafeFontName, B as ServicesConfig, C as clampVisualDpi, E as isSafeFont } from './types-kcQwhOlf.js';
3
+ import { F as FontRegistryEntry, c as FontRuntimeOpts, R as ResolvedFontSource, b as ResolvedFont, a as RasterizeFontFace } from './types-kcQwhOlf.js';
4
+ export { D as DEFAULT_VISUAL_DPI, d as FontFamilyNameSchema, e as FontRegistryDefinition, f as FontRegistryEntrySchema, g as FontRegistrySchema, h as FontSource, i as FontSourceSchema, H as HighchartsHeaders, j as HighchartsHeadersResolver, k as HighchartsServiceConfig, M as MAX_RASTERIZE_BATCH_SLIDES, l as MAX_RASTERIZE_FONTS, m as MAX_RASTERIZE_FONT_BYTES, n as MAX_VISUAL_DPI, o as MIN_VISUAL_DPI, P as PptxBatchRasterizer, p as PptxRasterizeBatchRequest, q as PptxRasterizeBatchResult, r as PptxRasterizeBatchSlide, s as PptxRasterizeBatchSlideResult, t as PptxRasterizeFailureStage, u as PptxRasterizeRequest, v as PptxRasterizeResult, w as PptxRasterizer, x as PptxServiceConfig, y as PptxServiceHeaders, z as PptxServiceHeadersResolver, S as SAFE_FONTS, A as SafeFontName, B as ServicesConfig, C as clampVisualDpi, E as isSafeFont } from './types-kcQwhOlf.js';
5
5
  export { F as FeatureRequirement, a as FeatureRequirementCollector, O as OfficeFormat, b as OfficeRenderer, R as RENDERER_DEPENDENCY_MISSING, c as RenderOptions, d as RendererDiagnostic, e as RendererDiagnosticSeverity, f as RendererRegistry, g as RendererStatus, h as UnsupportedRendererFeatureError, i as UnsupportedRendererFeatureErrorInit, j as assertNever, k as assertRendererSupports, l as diagnoseUnsupportedFeatures, p as partitionDiagnostics, r as rendererError, m as rendererWarning } from './capabilities-DtPF3aBj.js';
6
6
  export { DEFAULT_ERROR_CONFIG, ERROR_EMOJIS, ErrorFormatterConfig, calculatePosition, clearComponentNamesCache, createErrorConfig, createJsonParseError, extractStandardComponentNames, formatErrorMessage, formatErrorSummary, getLiteralValue, getObjectSchemaPropertyNames, getSchemaMetadata, groupErrorsByPath, isLiteralSchema, isObjectSchema, isUnionSchema, transformValueError, transformValueErrors } from './validation/unified/index.js';
7
7
  export { T as TransformedError, V as ValidationError, a as ValidationResult } from './types-BWFZ7OaO.js';
@@ -1612,6 +1612,93 @@ declare function resolveDesignColor(value: string, colors: Record<string, string
1612
1612
  /** Reject dangling/cyclic new tokens before they can become invalid OOXML. */
1613
1613
  declare function validateDesignColors(system: DesignSystem, colors: Record<string, string | undefined>): void;
1614
1614
 
1615
+ /**
1616
+ * The document's typography, expressed as Highcharts options.
1617
+ *
1618
+ * A `highcharts` component is a PNG drawn by a browser that has never seen the
1619
+ * document, so nothing about the page's type reaches the chart on its own: the
1620
+ * axis labels, title and legend come out in the export server's default face
1621
+ * at Highcharts' own sizes, visibly foreign to the prose around them. The
1622
+ * palette already carries (see `chart-palette.ts`); this carries the type.
1623
+ *
1624
+ * Format-neutral on purpose. Each core reads its own theme shape into a
1625
+ * `ChartTypography` — family, colours and sizes in document points — and this
1626
+ * module turns that into the option paths Highcharts styles text through. An
1627
+ * explicit author value keeps winning, property by property, exactly as
1628
+ * `options.colors` does.
1629
+ *
1630
+ * Sizes are converted from points to chart pixels through the scale the chart
1631
+ * is placed at: a 900px chart set into a 450pt measure shrinks by half, so a
1632
+ * label that must read as 9pt on the page is drawn at 18px.
1633
+ */
1634
+
1635
+ interface ChartTypography {
1636
+ /** CSS `font-family` for everything not styled otherwise (see `cssFontFamily`). */
1637
+ bodyFamily: string;
1638
+ /** CSS `font-family` for the chart title. */
1639
+ headingFamily: string;
1640
+ /** `#RRGGBB` for the title, legend and data labels. */
1641
+ textColor: string;
1642
+ /** `#RRGGBB` for axis text, subtitle, caption and credits. */
1643
+ mutedColor: string;
1644
+ /** Axis labels, axis titles, legend, subtitle and data labels, in points. */
1645
+ labelPt: number;
1646
+ /** Weight for legend items and data labels; Highcharts' own default when unset. */
1647
+ labelWeight?: number;
1648
+ /** Chart title, in points. */
1649
+ titlePt: number;
1650
+ /** Chart title weight; Highcharts' own default when unset. */
1651
+ titleWeight?: number;
1652
+ /** Credits (the source line) and caption, in points. */
1653
+ sourcePt: number;
1654
+ }
1655
+ /** Points per CSS pixel at the 96 dpi both formats assume for an unplaced chart. */
1656
+ declare const POINTS_PER_PIXEL_96DPI = 0.75;
1657
+ /**
1658
+ * How many document points one chart pixel occupies once the image is placed.
1659
+ * Unknown or degenerate widths fall back to 96 dpi, the size an unscaled
1660
+ * chart has in both formats.
1661
+ */
1662
+ declare function chartPointsPerPixel(chartWidthPx: number, placedWidthPt: number | undefined): number;
1663
+ type FontCategory = NonNullable<FontRegistryEntry['category']>;
1664
+ /**
1665
+ * A CSS `font-family` list: the family, quoted, then the generic it belongs
1666
+ * to — from the registry category when the font is registered, from the
1667
+ * SAFE_FONTS list otherwise — so a face the export server lacks degrades to
1668
+ * the right shape rather than to the browser's default.
1669
+ */
1670
+ declare function cssFontFamily(family: string, category?: FontCategory): string;
1671
+ /**
1672
+ * `cssFontFamily` bound to a theme: a registered family answers with its
1673
+ * registry category, an unregistered one with what SAFE_FONTS says of it.
1674
+ */
1675
+ declare function chartFamilyResolver(theme: unknown): (family: string) => string;
1676
+ type Options = Record<string, unknown>;
1677
+ /**
1678
+ * The document's typography written into every Highcharts option path that
1679
+ * styles text, beneath whatever the author set. `ptPerPx` is the placement
1680
+ * scale from `chartPointsPerPixel`.
1681
+ */
1682
+ declare function withChartTypography<T extends Options>(options: T, typography: ChartTypography, ptPerPx: number): T;
1683
+ /**
1684
+ * `@font-face` rules for the faces of `families`, inlined as data URIs, so an
1685
+ * export server draws a registered font from the same bytes the document
1686
+ * stages rather than from whatever its host happens to have installed. The
1687
+ * bytes go only to the export server, which already receives every data
1688
+ * point of the chart. Empty when no face matches.
1689
+ */
1690
+ declare function chartFontFaceCss(faces: readonly RasterizeFontFace[], families: readonly string[]): string;
1691
+ /**
1692
+ * The `@font-face` rules for `families` written into a chart's `resources.css`
1693
+ * ahead of whatever the author supplied there. Nothing changes when no face
1694
+ * matches, so a chart set in safe fonts posts the same request it always did.
1695
+ */
1696
+ declare function withChartFontFaceCss<T extends {
1697
+ resources?: {
1698
+ css?: string;
1699
+ };
1700
+ }>(props: T, faces: readonly RasterizeFontFace[], families: readonly string[]): T;
1701
+
1615
1702
  /**
1616
1703
  * Deep Merge Utilities
1617
1704
  * Generic deep-merge helpers used by both docx and pptx
@@ -1625,4 +1712,4 @@ declare function validateDesignColors(system: DesignSystem, colors: Record<strin
1625
1712
  */
1626
1713
  declare function mergeWithDefaults<T>(userConfig: T, themeDefaults: Partial<T>): T;
1627
1714
 
1628
- export { CANVASES, ChromeSchema, DEFAULT_CHART_THEME_COLORS, type DesignCanvas, DesignSpacingSchema, type DesignSystem, DesignSystemProperties, DesignSystemSchema, FONT_URL_ALLOWLIST, type FontIssueCode, FontRegistry, FontRegistryEntry, type FontRegistryInput, type FontResolutionIssue, FontRuntimeOpts, type FontSubstitution, type FontValidationInput, type FontValidationResult, MotifSchema, POPULAR_GOOGLE_FONTS, PaletteSchema, type PopularGoogleFont, ROLE_SCALE_STEPS, ResolvedFont, ResolvedFontSource, type SynthesizedFamily, TYPE_ROLES, TextCaseSchema, type TypeRole, type TypeRoleName, TypeRoleNameSchema, TypeRoleSchema, TypeRolesSchema, TypographySchema, UPSTREAM_OVERRIDES, type UpstreamOverride, type UpstreamVariant, WEIGHT_LABELS, applyExportMode, applyFontSubstitution, buildDefaultSubstitutionMap, capsFormatting, collectFontNamesFromDocx, collectFontNamesFromPptx, defaultSubstituteFor, designCanvas, designColors, detectFontFormat, documentFontRegistry, fetchGoogleFontSources, getUpstreamOverride, isAllowedFontUrl, mergeFontRegistries, mergeWithDefaults, resolveDesignColor, resolveTypeRoles, restructureNameDiscriminatedUnions, rewriteFontFamilyName, synthesizeFamilyName, themeFontRegistry, unionBranches, validateDesignColors, validateFontReferences };
1715
+ export { CANVASES, type ChartTypography, ChromeSchema, DEFAULT_CHART_THEME_COLORS, type DesignCanvas, DesignSpacingSchema, type DesignSystem, DesignSystemProperties, DesignSystemSchema, FONT_URL_ALLOWLIST, type FontIssueCode, FontRegistry, FontRegistryEntry, type FontRegistryInput, type FontResolutionIssue, FontRuntimeOpts, type FontSubstitution, type FontValidationInput, type FontValidationResult, MotifSchema, POINTS_PER_PIXEL_96DPI, POPULAR_GOOGLE_FONTS, PaletteSchema, type PopularGoogleFont, ROLE_SCALE_STEPS, RasterizeFontFace, ResolvedFont, ResolvedFontSource, type SynthesizedFamily, TYPE_ROLES, TextCaseSchema, type TypeRole, type TypeRoleName, TypeRoleNameSchema, TypeRoleSchema, TypeRolesSchema, TypographySchema, UPSTREAM_OVERRIDES, type UpstreamOverride, type UpstreamVariant, WEIGHT_LABELS, applyExportMode, applyFontSubstitution, buildDefaultSubstitutionMap, capsFormatting, chartFamilyResolver, chartFontFaceCss, chartPointsPerPixel, collectFontNamesFromDocx, collectFontNamesFromPptx, cssFontFamily, defaultSubstituteFor, designCanvas, designColors, detectFontFormat, documentFontRegistry, fetchGoogleFontSources, getUpstreamOverride, isAllowedFontUrl, mergeFontRegistries, mergeWithDefaults, resolveDesignColor, resolveTypeRoles, restructureNameDiscriminatedUnions, rewriteFontFamilyName, synthesizeFamilyName, themeFontRegistry, unionBranches, validateDesignColors, validateFontReferences, withChartFontFaceCss, withChartTypography };
package/dist/index.js CHANGED
@@ -38,18 +38,6 @@ import {
38
38
  transformValueError,
39
39
  transformValueErrors
40
40
  } from "./chunk-LLBCT7WL.js";
41
- import {
42
- FeatureRequirementCollector,
43
- RENDERER_DEPENDENCY_MISSING,
44
- RendererRegistry,
45
- UnsupportedRendererFeatureError,
46
- assertNever,
47
- assertRendererSupports,
48
- diagnoseUnsupportedFeatures,
49
- partitionDiagnostics,
50
- rendererError,
51
- rendererWarning
52
- } from "./chunk-QLZNOXT5.js";
53
41
  import {
54
42
  convertToJsonSchema,
55
43
  createComponentSchema,
@@ -87,6 +75,18 @@ import {
87
75
  resolveTypeRoles,
88
76
  validateDesignColors
89
77
  } from "./chunk-4MJFAJFW.js";
78
+ import {
79
+ FeatureRequirementCollector,
80
+ RENDERER_DEPENDENCY_MISSING,
81
+ RendererRegistry,
82
+ UnsupportedRendererFeatureError,
83
+ assertNever,
84
+ assertRendererSupports,
85
+ diagnoseUnsupportedFeatures,
86
+ partitionDiagnostics,
87
+ rendererError,
88
+ rendererWarning
89
+ } from "./chunk-QLZNOXT5.js";
90
90
  import {
91
91
  compareSemver,
92
92
  isValidSemver,
@@ -240,21 +240,21 @@ var WEIGHT_LABELS = {
240
240
  800: "ExtraBold",
241
241
  900: "Black"
242
242
  };
243
- function synthesizeFamilyName(family, weight, italic) {
244
- if (weight == null) {
243
+ function synthesizeFamilyName(family, weight2, italic) {
244
+ if (weight2 == null) {
245
245
  return { family, bold: false, italic, nonCanonicalWeight: false };
246
246
  }
247
- if (weight === 400) {
247
+ if (weight2 === 400) {
248
248
  return { family, bold: false, italic, nonCanonicalWeight: false };
249
249
  }
250
- if (weight === 700) {
250
+ if (weight2 === 700) {
251
251
  return { family, bold: true, italic, nonCanonicalWeight: false };
252
252
  }
253
- const label = WEIGHT_LABELS[weight];
253
+ const label = WEIGHT_LABELS[weight2];
254
254
  if (!label) {
255
255
  return {
256
256
  family,
257
- bold: weight >= 600,
257
+ bold: weight2 >= 600,
258
258
  italic,
259
259
  nonCanonicalWeight: true
260
260
  };
@@ -353,8 +353,8 @@ function parseCssFaces(css) {
353
353
  }
354
354
  return out;
355
355
  }
356
- function cacheKey(family, weight, italic) {
357
- return `google|${family}|${weight}|${italic ? "i" : "r"}`;
356
+ function cacheKey(family, weight2, italic) {
357
+ return `google|${family}|${weight2}|${italic ? "i" : "r"}`;
358
358
  }
359
359
  async function fetchGoogleFontSources(opts) {
360
360
  const weights = opts.weights?.length ? opts.weights : [400, 700];
@@ -464,8 +464,8 @@ async function fetchGoogleFontSources(opts) {
464
464
  }
465
465
 
466
466
  // src/fonts/sources/url-fetcher.ts
467
- function cacheKey2(url, weight, italic) {
468
- return `url|${url}|${weight}|${italic ? "i" : "r"}`;
467
+ function cacheKey2(url, weight2, italic) {
468
+ return `url|${url}|${weight2}|${italic ? "i" : "r"}`;
469
469
  }
470
470
  async function fetchUrlFontSource(opts) {
471
471
  if (!isAllowedFontUrl(opts.url)) {
@@ -592,25 +592,25 @@ function readUsWeightClass(ttf) {
592
592
  if (os2.off + 6 > ttf.length) return null;
593
593
  return ttf.readUInt16BE(os2.off + 4);
594
594
  }
595
- function validateFontMetadata(ttf, weight, italic, familyLabel) {
595
+ function validateFontMetadata(ttf, weight2, italic, familyLabel) {
596
596
  const diags = [];
597
597
  const usWeight = readUsWeightClass(ttf);
598
- if (usWeight != null && usWeight !== weight) {
598
+ if (usWeight != null && usWeight !== weight2) {
599
599
  diags.push({
600
600
  code: "WEIGHT_CLASS_MISMATCH",
601
- message: `Font "${familyLabel}" weight ${weight}: OS/2.usWeightClass reports ${usWeight}. Likely a defective redistribution \u2014 consider adding an upstream override.`
601
+ message: `Font "${familyLabel}" weight ${weight2}: OS/2.usWeightClass reports ${usWeight}. Likely a defective redistribution \u2014 consider adding an upstream override.`
602
602
  });
603
603
  }
604
604
  const declaredFamilies = readFontFamilyNames(ttf);
605
605
  if (declaredFamilies.length > 0 && !declaredFamilies.includes(familyLabel.trim())) {
606
606
  diags.push({
607
607
  code: "FAMILY_MISMATCH",
608
- message: `Font "${familyLabel}" weight ${weight}${italic ? " italic" : ""}: name table declares ${declaredFamilies.map((f) => `"${f}"`).join(
608
+ message: `Font "${familyLabel}" weight ${weight2}${italic ? " italic" : ""}: name table declares ${declaredFamilies.map((f) => `"${f}"`).join(
609
609
  " / "
610
610
  )}, not "${familyLabel}". Referencing runs will not resolve this face.`
611
611
  });
612
612
  }
613
- const std = standardSubfamilyNames(weight, italic);
613
+ const std = standardSubfamilyNames(weight2, italic);
614
614
  if (!std) return diags;
615
615
  const expected17 = std.typographic;
616
616
  const expected2 = std.legacy;
@@ -619,13 +619,13 @@ function validateFontMetadata(ttf, weight, italic, familyLabel) {
619
619
  if (n.nameID === 17 && n.value !== expected17) {
620
620
  diags.push({
621
621
  code: "SUBFAMILY_MISMATCH",
622
- message: `Font "${familyLabel}" weight ${weight}${italic ? " italic" : ""}: name record (platform ${n.platformID}) nameID 17 = "${n.value}", expected "${expected17}".`
622
+ message: `Font "${familyLabel}" weight ${weight2}${italic ? " italic" : ""}: name record (platform ${n.platformID}) nameID 17 = "${n.value}", expected "${expected17}".`
623
623
  });
624
624
  }
625
625
  if (n.nameID === 2 && n.value !== expected2) {
626
626
  diags.push({
627
627
  code: "LEGACY_SUBFAMILY_MISMATCH",
628
- message: `Font "${familyLabel}" weight ${weight}${italic ? " italic" : ""}: name record (platform ${n.platformID}) nameID 2 = "${n.value}", expected "${expected2}".`
628
+ message: `Font "${familyLabel}" weight ${weight2}${italic ? " italic" : ""}: name record (platform ${n.platformID}) nameID 2 = "${n.value}", expected "${expected2}".`
629
629
  });
630
630
  }
631
631
  }
@@ -645,8 +645,8 @@ var SFNT_VERSIONS = /* @__PURE__ */ new Set([
645
645
  ]);
646
646
  var HEADER_SIZE2 = 12;
647
647
  var TABLE_RECORD_SIZE2 = 16;
648
- function validateFontStructure(ttf, weight, italic, familyLabel) {
649
- const face = `Font "${familyLabel}" weight ${weight}${italic ? " italic" : ""}`;
648
+ function validateFontStructure(ttf, weight2, italic, familyLabel) {
649
+ const face = `Font "${familyLabel}" weight ${weight2}${italic ? " italic" : ""}`;
650
650
  const unreadable = (reason) => ({
651
651
  code: "FONT_UNREADABLE",
652
652
  message: `${face}: ${reason} The face will not resolve; text referencing it renders in a fallback.`
@@ -1160,16 +1160,16 @@ var INTER_VARIABLE_URL = "https://cdn.jsdelivr.net/gh/rsms/inter@v4.1/docs/font-
1160
1160
  var INTER_VARIABLE_ITALIC_URL = "https://cdn.jsdelivr.net/gh/rsms/inter@v4.1/docs/font-files/InterVariable-Italic.woff2";
1161
1161
  function interVariants() {
1162
1162
  const weights = [100, 200, 300, 400, 500, 600, 700, 800, 900];
1163
- const upright = weights.map((weight) => ({
1163
+ const upright = weights.map((weight2) => ({
1164
1164
  kind: "variable",
1165
1165
  url: INTER_VARIABLE_URL,
1166
- weight,
1166
+ weight: weight2,
1167
1167
  italic: false
1168
1168
  }));
1169
- const italic = weights.map((weight) => ({
1169
+ const italic = weights.map((weight2) => ({
1170
1170
  kind: "variable",
1171
1171
  url: INTER_VARIABLE_ITALIC_URL,
1172
- weight,
1172
+ weight: weight2,
1173
1173
  italic: true
1174
1174
  }));
1175
1175
  return [...upright, ...italic];
@@ -1371,6 +1371,118 @@ var DEFAULT_CHART_THEME_COLORS = [
1371
1371
  "accent6"
1372
1372
  ];
1373
1373
 
1374
+ // src/theme/chart-typography.ts
1375
+ var POINTS_PER_PIXEL_96DPI = 0.75;
1376
+ function chartPointsPerPixel(chartWidthPx, placedWidthPt) {
1377
+ if (!Number.isFinite(chartWidthPx) || chartWidthPx <= 0 || placedWidthPt === void 0 || !Number.isFinite(placedWidthPt) || placedWidthPt <= 0) {
1378
+ return POINTS_PER_PIXEL_96DPI;
1379
+ }
1380
+ return placedWidthPt / chartWidthPx;
1381
+ }
1382
+ var SERIF_FAMILIES = /* @__PURE__ */ new Set(["georgia", "times new roman", "cambria"]);
1383
+ var MONO_FAMILIES = /* @__PURE__ */ new Set(["consolas", "courier new", "menlo", "monaco"]);
1384
+ function cssFontFamily(family, category) {
1385
+ const generic = category === "serif" ? "serif" : category === "mono" ? "monospace" : category === "handwriting" ? "cursive" : category === void 0 && SERIF_FAMILIES.has(family.toLowerCase()) ? "serif" : category === void 0 && MONO_FAMILIES.has(family.toLowerCase()) ? "monospace" : "sans-serif";
1386
+ return `"${family.replace(/["\\]/g, "\\$&")}", ${generic}`;
1387
+ }
1388
+ function chartFamilyResolver(theme) {
1389
+ const categories = new Map(
1390
+ themeFontRegistry(theme).map((entry) => [
1391
+ entry.family.toLowerCase(),
1392
+ entry.category
1393
+ ])
1394
+ );
1395
+ return (family) => cssFontFamily(family, categories.get(family.toLowerCase()));
1396
+ }
1397
+ function isPlainObject(value) {
1398
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1399
+ }
1400
+ function fill(authored, defaults) {
1401
+ if (authored !== void 0 && !isPlainObject(authored)) return authored;
1402
+ const base = isPlainObject(authored) ? { ...authored } : {};
1403
+ for (const [key, value] of Object.entries(defaults)) {
1404
+ if (value === void 0) continue;
1405
+ const current = base[key];
1406
+ if (current === void 0) {
1407
+ base[key] = isPlainObject(value) ? fill(void 0, value) : value;
1408
+ } else if (isPlainObject(current) && isPlainObject(value)) {
1409
+ base[key] = fill(current, value);
1410
+ }
1411
+ }
1412
+ return base;
1413
+ }
1414
+ function fillAxis(authored, defaults) {
1415
+ if (Array.isArray(authored)) {
1416
+ return authored.map((axis) => fill(axis, defaults));
1417
+ }
1418
+ return fill(authored, defaults);
1419
+ }
1420
+ function weight(value) {
1421
+ return value === void 0 ? void 0 : String(value);
1422
+ }
1423
+ function withChartTypography(options, typography, ptPerPx) {
1424
+ const px = (points) => `${Math.round(points / ptPerPx * 10) / 10}px`;
1425
+ const labelPx = px(typography.labelPt);
1426
+ const sourcePx = px(typography.sourcePt);
1427
+ const mutedText = { fontSize: labelPx, color: typography.mutedColor };
1428
+ const mutedSource = { fontSize: sourcePx, color: typography.mutedColor };
1429
+ const labelText = {
1430
+ fontSize: labelPx,
1431
+ color: typography.textColor,
1432
+ fontWeight: weight(typography.labelWeight)
1433
+ };
1434
+ const axis = { labels: { style: mutedText }, title: { style: mutedText } };
1435
+ return {
1436
+ ...options,
1437
+ chart: fill(options.chart, {
1438
+ style: { fontFamily: typography.bodyFamily }
1439
+ }),
1440
+ title: fill(options.title, {
1441
+ style: {
1442
+ fontFamily: typography.headingFamily,
1443
+ fontSize: px(typography.titlePt),
1444
+ fontWeight: weight(typography.titleWeight),
1445
+ color: typography.textColor
1446
+ }
1447
+ }),
1448
+ subtitle: fill(options.subtitle, { style: mutedText }),
1449
+ caption: fill(options.caption, { style: mutedSource }),
1450
+ xAxis: fillAxis(options.xAxis, axis),
1451
+ yAxis: fillAxis(options.yAxis, axis),
1452
+ legend: fill(options.legend, { itemStyle: labelText }),
1453
+ plotOptions: fill(options.plotOptions, {
1454
+ series: { dataLabels: { style: labelText } }
1455
+ }),
1456
+ credits: fill(options.credits, { style: mutedSource })
1457
+ };
1458
+ }
1459
+ var FONT_FORMATS = {
1460
+ ttf: { mime: "font/ttf", format: "truetype" },
1461
+ otf: { mime: "font/otf", format: "opentype" },
1462
+ woff: { mime: "font/woff", format: "woff" },
1463
+ woff2: { mime: "font/woff2", format: "woff2" }
1464
+ };
1465
+ function chartFontFaceCss(faces, families) {
1466
+ const wanted = new Set(families.map((family) => family.toLowerCase()));
1467
+ return faces.filter((face) => wanted.has(face.family.toLowerCase())).map((face) => {
1468
+ const { mime, format } = FONT_FORMATS[face.format ?? "ttf"];
1469
+ return `@font-face{font-family:"${face.family.replace(/["\\]/g, "\\$&")}";font-weight:${face.weight};font-style:${face.italic ? "italic" : "normal"};src:url(data:${mime};base64,${face.data}) format("${format}")}`;
1470
+ }).join("\n");
1471
+ }
1472
+ function withChartFontFaceCss(props, faces, families) {
1473
+ const css = chartFontFaceCss(faces, families);
1474
+ if (!css) return props;
1475
+ const authored = props.resources?.css;
1476
+ return {
1477
+ ...props,
1478
+ resources: {
1479
+ ...props.resources,
1480
+ css: authored ? `${css}
1481
+ ${authored}` : css
1482
+ }
1483
+ };
1484
+ }
1485
+
1374
1486
  // src/utils/deepMerge.ts
1375
1487
  function isObject(item) {
1376
1488
  return item !== null && typeof item === "object" && !Array.isArray(item);
@@ -1420,6 +1532,7 @@ export {
1420
1532
  MAX_VISUAL_DPI,
1421
1533
  MIN_VISUAL_DPI,
1422
1534
  MotifSchema,
1535
+ POINTS_PER_PIXEL_96DPI,
1423
1536
  POPULAR_GOOGLE_FONTS,
1424
1537
  PaletteSchema,
1425
1538
  RENDERER_DEPENDENCY_MISSING,
@@ -1443,6 +1556,9 @@ export {
1443
1556
  buildDefaultSubstitutionMap,
1444
1557
  calculatePosition,
1445
1558
  capsFormatting,
1559
+ chartFamilyResolver,
1560
+ chartFontFaceCss,
1561
+ chartPointsPerPixel,
1446
1562
  clampVisualDpi,
1447
1563
  clearComponentNamesCache,
1448
1564
  collectFontNamesFromDocx,
@@ -1455,6 +1571,7 @@ export {
1455
1571
  createErrorConfig,
1456
1572
  createJsonParseError,
1457
1573
  createVersion,
1574
+ cssFontFamily,
1458
1575
  defaultSubstituteFor,
1459
1576
  designCanvas,
1460
1577
  designColors,
@@ -1499,6 +1616,8 @@ export {
1499
1616
  unionBranches,
1500
1617
  validateCustomComponentProps,
1501
1618
  validateDesignColors,
1502
- validateFontReferences
1619
+ validateFontReferences,
1620
+ withChartFontFaceCss,
1621
+ withChartTypography
1503
1622
  };
1504
1623
  //# sourceMappingURL=index.js.map