@json-to-office/shared 0.33.0 → 1.0.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/{chunk-CP2I5NPP.js → chunk-FDSJYZ5W.js} +21 -20
- package/dist/chunk-FDSJYZ5W.js.map +1 -0
- package/dist/chunk-JM5KTMNL.js +240 -0
- package/dist/chunk-JM5KTMNL.js.map +1 -0
- package/dist/{chunk-KLWNDWC4.js → chunk-SJ2YYRCT.js} +10 -3
- package/dist/chunk-SJ2YYRCT.js.map +1 -0
- package/dist/fonts/node.d.ts +42 -2
- package/dist/fonts/node.js +50 -2
- package/dist/fonts/node.js.map +1 -1
- package/dist/index.d.ts +52 -148
- package/dist/index.js +118 -23
- package/dist/index.js.map +1 -1
- package/dist/rendering/index.d.ts +210 -0
- package/dist/rendering/index.js +25 -0
- package/dist/rendering/index.js.map +1 -0
- package/dist/schemas/schema-utils.js +1 -1
- package/dist/schemas/slide-content.d.ts +18 -0
- package/dist/schemas/slide-content.js +42 -0
- package/dist/schemas/slide-content.js.map +1 -1
- package/dist/{types-CL0Hbw6x.d.ts → types-kcQwhOlf.d.ts} +175 -1
- package/package.json +5 -5
- package/dist/cache/index.d.ts +0 -464
- package/dist/cache/index.js +0 -741
- package/dist/cache/index.js.map +0 -1
- package/dist/chunk-CP2I5NPP.js.map +0 -1
- package/dist/chunk-KLWNDWC4.js.map +0 -1
package/dist/fonts/node.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/fonts/sources/file-loader.ts","../../src/fonts/cache/disk-cache.ts","../../src/fonts/sources/variable-fetcher.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 TTF once (disk-cached), then\n * pins its `wght` axis (plus any additional axes) to produce a clean static\n * TTF per requested weight. Uses harfbuzz via `subset-font` — pure JS + WASM,\n * 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 TTF cached at key `varsrc|<url>` — one download per URL\n * per process (+ optional disk layer).\n * 2. Instanced static TTF cached at `variable|<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 { 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 return `variable|${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 if (format !== 'ttf' && format !== 'otf') {\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 `normalizeNameTable` depends 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 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"],"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 TTF once (disk-cached), then\n * pins its `wght` axis (plus any additional axes) to produce a clean static\n * TTF per requested weight. Uses harfbuzz via `subset-font` — pure JS + WASM,\n * 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 TTF cached at key `varsrc|<url>` — one download per URL\n * per process (+ optional disk layer).\n * 2. Instanced static TTF cached at `variable|<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 { 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 return `variable|${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 if (format !== 'ttf' && format !== 'otf') {\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 `normalizeNameTable` depends 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 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"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,148 +1,15 @@
|
|
|
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-CbCi6dEk.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';
|
|
5
|
+
export { FeatureRequirement, FeatureRequirementCollector, OfficeFormat, OfficeRenderer, RenderOptions, RendererDiagnostic, RendererDiagnosticSeverity, RendererRegistry, UnsupportedRendererFeatureError, UnsupportedRendererFeatureErrorInit, assertNever, assertRendererSupports, diagnoseUnsupportedFeatures, partitionDiagnostics, rendererError, rendererWarning } from './rendering/index.js';
|
|
3
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';
|
|
4
7
|
export { T as TransformedError, V as ValidationError, a as ValidationResult } from './types-BWFZ7OaO.js';
|
|
5
8
|
export { ComponentValidationError, ComponentValidationResult, ComponentVersion, ComponentVersionMap, CustomComponent, DuplicateComponentError, PluginValidationOptions, PluginValidationResult, RenderContext, RenderFunction, UnknownPreservedComponentError, createComponent, createVersion, getValidationSummary, isValidationSuccess, resolveComponentVersion, validateCustomComponentProps } from './plugin/index.js';
|
|
6
|
-
import { F as FontRegistryEntry, a as FontRuntimeOpts, R as ResolvedFontSource, b as ResolvedFont } from './types-CL0Hbw6x.js';
|
|
7
|
-
export { c as FontFamilyNameSchema, d as FontRegistryDefinition, e as FontRegistryEntrySchema, f as FontRegistrySchema, g as FontSource, h as FontSourceSchema, S as SAFE_FONTS, i as SafeFontName, j as isSafeFont } from './types-CL0Hbw6x.js';
|
|
8
9
|
export { ParsedSemver, compareSemver, isValidSemver, latestVersion, parseSemver } from './utils/semver.js';
|
|
9
10
|
import '@sinclair/typebox';
|
|
10
11
|
import '@sinclair/typebox/value';
|
|
11
12
|
|
|
12
|
-
/**
|
|
13
|
-
* Service configuration types for external integrations (e.g. Highcharts export server)
|
|
14
|
-
*/
|
|
15
|
-
/** Default raster resolution when a `visual` does not specify one. */
|
|
16
|
-
declare const DEFAULT_VISUAL_DPI = 200;
|
|
17
|
-
/** Minimum accepted raster resolution. */
|
|
18
|
-
declare const MIN_VISUAL_DPI = 36;
|
|
19
|
-
/** Maximum accepted raster resolution (bounds bitmap size / DoS surface). */
|
|
20
|
-
declare const MAX_VISUAL_DPI = 600;
|
|
21
|
-
/** Clamp an arbitrary dpi to [MIN_VISUAL_DPI, MAX_VISUAL_DPI]; non-finite → default. */
|
|
22
|
-
declare function clampVisualDpi(dpi: unknown): number;
|
|
23
|
-
type HighchartsHeaders = Record<string, string>;
|
|
24
|
-
type HighchartsHeadersResolver = (body: unknown) => HighchartsHeaders | Promise<HighchartsHeaders>;
|
|
25
|
-
interface HighchartsServiceConfig {
|
|
26
|
-
serverUrl?: string;
|
|
27
|
-
headers?: HighchartsHeaders | HighchartsHeadersResolver;
|
|
28
|
-
}
|
|
29
|
-
type PptxServiceHeaders = Record<string, string>;
|
|
30
|
-
type PptxServiceHeadersResolver = (body: unknown) => PptxServiceHeaders | Promise<PptxServiceHeaders>;
|
|
31
|
-
/**
|
|
32
|
-
* Request handed to a pptx rasterizer: a single-slide pptx presentation
|
|
33
|
-
* component definition plus the target resolution.
|
|
34
|
-
*/
|
|
35
|
-
interface PptxRasterizeRequest {
|
|
36
|
-
/** A pptx presentation component definition ({ name: 'pptx', ... }) with one slide */
|
|
37
|
-
presentation: unknown;
|
|
38
|
-
/** Target raster resolution in dots-per-inch */
|
|
39
|
-
dpi: number;
|
|
40
|
-
/**
|
|
41
|
-
* Directory that relative asset paths inside the presentation resolve
|
|
42
|
-
* against — the originating document's own directory. Absent → the
|
|
43
|
-
* rasterizer's cwd, the legacy behavior (#142).
|
|
44
|
-
*/
|
|
45
|
-
baseDir?: string;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Result returned by a pptx rasterizer.
|
|
49
|
-
*/
|
|
50
|
-
interface PptxRasterizeResult {
|
|
51
|
-
/** Rendered PNG as a base64 data URI (data:image/png;base64,...) */
|
|
52
|
-
base64DataUri: string;
|
|
53
|
-
/** Natural pixel width of the rendered image */
|
|
54
|
-
width: number;
|
|
55
|
-
/** Natural pixel height of the rendered image */
|
|
56
|
-
height: number;
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* In-process rasterizer callback. Implementations build the .pptx from the
|
|
60
|
-
* presentation JSON and rasterize it to a PNG (e.g. via LibreOffice + poppler).
|
|
61
|
-
*/
|
|
62
|
-
type PptxRasterizer = (request: PptxRasterizeRequest) => Promise<PptxRasterizeResult>;
|
|
63
|
-
/**
|
|
64
|
-
* Maximum slides accepted in one batch request. Shared by the HTTP surface
|
|
65
|
-
* (request validation) and clients (chunk size) so the two cannot drift.
|
|
66
|
-
* Bounds per-request work and response size the same way rate limits bound
|
|
67
|
-
* request counts.
|
|
68
|
-
*/
|
|
69
|
-
declare const MAX_RASTERIZE_BATCH_SLIDES = 32;
|
|
70
|
-
/** One slide in a batch: a single-slide presentation plus its resolution. */
|
|
71
|
-
interface PptxRasterizeBatchSlide {
|
|
72
|
-
/** A pptx presentation component definition ({ name: 'pptx', ... }) with one slide */
|
|
73
|
-
presentation: unknown;
|
|
74
|
-
/** Target raster resolution in dots-per-inch (absent → service default) */
|
|
75
|
-
dpi?: number;
|
|
76
|
-
}
|
|
77
|
-
/** Request handed to a batch pptx rasterizer. */
|
|
78
|
-
interface PptxRasterizeBatchRequest {
|
|
79
|
-
/** Slides to rasterize; results come back index-aligned with this array. */
|
|
80
|
-
slides: PptxRasterizeBatchSlide[];
|
|
81
|
-
/** Base directory for relative asset paths, shared by every slide (#142). */
|
|
82
|
-
baseDir?: string;
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Pipeline stage a slide failed in. `build` failures are caused by the
|
|
86
|
-
* slide's own JSON (safe to surface verbatim to callers); `convert` and
|
|
87
|
-
* `rasterize` failures are environment/tooling errors whose raw messages may
|
|
88
|
-
* carry host paths — HTTP surfaces sanitize those.
|
|
89
|
-
*/
|
|
90
|
-
type PptxRasterizeFailureStage = 'build' | 'convert' | 'rasterize';
|
|
91
|
-
/**
|
|
92
|
-
* Per-slide outcome. A batch response is 200-with-item-errors rather than
|
|
93
|
-
* all-or-nothing: one bad visual must not discard its siblings' pixels.
|
|
94
|
-
*/
|
|
95
|
-
type PptxRasterizeBatchSlideResult = ({
|
|
96
|
-
ok: true;
|
|
97
|
-
} & PptxRasterizeResult) | {
|
|
98
|
-
ok: false;
|
|
99
|
-
error: string;
|
|
100
|
-
stage?: PptxRasterizeFailureStage;
|
|
101
|
-
};
|
|
102
|
-
/** Result returned by a batch pptx rasterizer. */
|
|
103
|
-
interface PptxRasterizeBatchResult {
|
|
104
|
-
/** Index-aligned with the request's `slides` (same length, same order). */
|
|
105
|
-
results: PptxRasterizeBatchSlideResult[];
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* In-process batch rasterizer callback. Batch-level failures (missing
|
|
109
|
-
* binaries, bad request) throw; per-slide failures land in `results`.
|
|
110
|
-
*/
|
|
111
|
-
type PptxBatchRasterizer = (request: PptxRasterizeBatchRequest) => Promise<PptxRasterizeBatchResult>;
|
|
112
|
-
/**
|
|
113
|
-
* Configuration for the pptx rasterization service backing `visual` components.
|
|
114
|
-
*
|
|
115
|
-
* Mirrors {@link HighchartsServiceConfig}: the published packages depend on this
|
|
116
|
-
* interface, never on a binary. A host injects either an in-process `render`
|
|
117
|
-
* callback or an HTTP `serverUrl`.
|
|
118
|
-
*/
|
|
119
|
-
interface PptxServiceConfig {
|
|
120
|
-
/**
|
|
121
|
-
* In-process rasterizer. Takes precedence over `serverUrl` when provided.
|
|
122
|
-
* Ideal for tests (no binaries) and single-process hosts.
|
|
123
|
-
*/
|
|
124
|
-
render?: PptxRasterizer;
|
|
125
|
-
/**
|
|
126
|
-
* In-process batch rasterizer. When provided, the docx renderer coalesces a
|
|
127
|
-
* document's visuals into batch calls (#153) instead of one `render` call
|
|
128
|
-
* per visual. Like `render`, takes precedence over `serverUrl`.
|
|
129
|
-
*/
|
|
130
|
-
renderBatch?: PptxBatchRasterizer;
|
|
131
|
-
/**
|
|
132
|
-
* HTTP rasterization service URL. The service receives
|
|
133
|
-
* `{ presentation, dpi }` and returns a {@link PptxRasterizeResult}.
|
|
134
|
-
*/
|
|
135
|
-
serverUrl?: string;
|
|
136
|
-
/** Optional headers (or async resolver) for the HTTP service. */
|
|
137
|
-
headers?: PptxServiceHeaders | PptxServiceHeadersResolver;
|
|
138
|
-
/** Default DPI applied when a `visual` does not specify one. */
|
|
139
|
-
dpi?: number;
|
|
140
|
-
}
|
|
141
|
-
interface ServicesConfig {
|
|
142
|
-
highcharts?: HighchartsServiceConfig;
|
|
143
|
-
pptx?: PptxServiceConfig;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
13
|
/**
|
|
147
14
|
* Canonical `if/then` restructuring for name-discriminated component unions.
|
|
148
15
|
*
|
|
@@ -216,6 +83,34 @@ declare const collectFontNamesFromDocx: typeof collectFontNames;
|
|
|
216
83
|
/** Scan a PPTX presentation tree for every font family name referenced. */
|
|
217
84
|
declare const collectFontNamesFromPptx: typeof collectFontNames;
|
|
218
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Read the document-scoped and theme-scoped font registries and merge them
|
|
88
|
+
* with runtime `fonts.extraEntries`.
|
|
89
|
+
*
|
|
90
|
+
* Precedence (last wins, matching registry.ts's documented resolution rules
|
|
91
|
+
* and FontRuntimeOpts.extraEntries's "merged over the document's
|
|
92
|
+
* fontRegistry"):
|
|
93
|
+
*
|
|
94
|
+
* theme.fontRegistry < document.props.fontRegistry < fonts.extraEntries
|
|
95
|
+
*
|
|
96
|
+
* Merging happens here rather than inside FontRegistry because
|
|
97
|
+
* `validateFontReferences` needs the same merged list, and two merge sites
|
|
98
|
+
* would eventually disagree — which would show up as a font that validates
|
|
99
|
+
* but never renders, or vice versa.
|
|
100
|
+
*/
|
|
101
|
+
|
|
102
|
+
/** `document.props.fontRegistry`, defensively (props may be absent/null). */
|
|
103
|
+
declare function documentFontRegistry(document: unknown): FontRegistryEntry[];
|
|
104
|
+
/** `theme.fontRegistry`, defensively. */
|
|
105
|
+
declare function themeFontRegistry(theme: unknown): FontRegistryEntry[];
|
|
106
|
+
/**
|
|
107
|
+
* Merge entry groups in precedence order — later groups win on a collision of
|
|
108
|
+
* `family` OR `id`, case-insensitively, which are the same two keys
|
|
109
|
+
* `FontRegistry.addEntry` indexes on. Returns a flat list safe to hand to both
|
|
110
|
+
* `validateFontReferences` and `new FontRegistry({ opts: { extraEntries } })`.
|
|
111
|
+
*/
|
|
112
|
+
declare function mergeFontRegistries(...groups: (FontRegistryEntry[] | undefined)[]): FontRegistryEntry[];
|
|
113
|
+
|
|
219
114
|
/**
|
|
220
115
|
* Validate that every font name referenced in a document is either
|
|
221
116
|
* in SAFE_FONTS or present in the document's fontRegistry / runtime overrides.
|
|
@@ -329,6 +224,21 @@ declare function synthesizeFamilyName(family: string, weight: number | undefined
|
|
|
329
224
|
*/
|
|
330
225
|
declare function rewriteFontFamilyName(input: Buffer, newFamily: string): Buffer;
|
|
331
226
|
|
|
227
|
+
/**
|
|
228
|
+
* Hostname allowlist for font fetchers.
|
|
229
|
+
*
|
|
230
|
+
* `url-fetcher` and `variable-fetcher` can be handed arbitrary URLs via
|
|
231
|
+
* `FontRegistryEntry.sources`, which may originate from document JSON. Without
|
|
232
|
+
* a guard, a malicious doc could point fetchers at internal hosts (SSRF), the
|
|
233
|
+
* filesystem (`file://`), or the IMDS endpoint. Limit downloads to the hosts
|
|
234
|
+
* our catalog + UPSTREAM_OVERRIDES actually target.
|
|
235
|
+
*
|
|
236
|
+
* Keep the list small and HTTPS-only. Expansions should be deliberate code
|
|
237
|
+
* reviews, not config-driven — the cost of a new domain is the code change.
|
|
238
|
+
*/
|
|
239
|
+
declare const FONT_URL_ALLOWLIST: readonly string[];
|
|
240
|
+
declare function isAllowedFontUrl(url: string): boolean;
|
|
241
|
+
|
|
332
242
|
/**
|
|
333
243
|
* FontRegistry — merges catalog + document registry + runtime entries
|
|
334
244
|
* and materializes referenced fonts into ResolvedFont records.
|
|
@@ -433,7 +343,7 @@ declare function detectFontFormat(buf: Buffer): ResolvedFontSource['format'];
|
|
|
433
343
|
* Curated list of popular Google Fonts for picker autocomplete.
|
|
434
344
|
*
|
|
435
345
|
* Not exhaustive — the full Google Fonts library has ~1500 families.
|
|
436
|
-
* This is ~
|
|
346
|
+
* This is ~37 names known to cover most real-world use cases.
|
|
437
347
|
*/
|
|
438
348
|
interface PopularGoogleFont {
|
|
439
349
|
family: string;
|
|
@@ -578,6 +488,10 @@ interface ApplyFontSubstitutionResult<T> {
|
|
|
578
488
|
* Returns a new tree (structural clone) plus the list of `(from, to)`
|
|
579
489
|
* swaps made, deduped by source name.
|
|
580
490
|
*
|
|
491
|
+
* One deliberate exception to the clone: `fontRegistry` subtrees are carried
|
|
492
|
+
* through by reference, since they declare fonts rather than reference them
|
|
493
|
+
* and nothing downstream mutates them.
|
|
494
|
+
*
|
|
581
495
|
* Families already in SAFE_FONTS are never rewritten (even if a mapping
|
|
582
496
|
* entry targets them as a key — safe fonts don't need substitution).
|
|
583
497
|
* Families with no mapping entry are left untouched — callers should
|
|
@@ -603,16 +517,6 @@ declare function defaultSubstituteFor(family: string): string;
|
|
|
603
517
|
*/
|
|
604
518
|
declare function buildDefaultSubstitutionMap(referencedNames: Iterable<string>): Record<string, string>;
|
|
605
519
|
|
|
606
|
-
/**
|
|
607
|
-
* Cache-key suffix used to scope generator outputs by export mode. When
|
|
608
|
-
* `fonts.mode === 'substitute'` the doc tree is rewritten pre-render, so
|
|
609
|
-
* a substitute-mode buffer and a custom-mode buffer for the same base
|
|
610
|
-
* theme must not collide in the byte cache. Keep this as a single
|
|
611
|
-
* helper so a typo in one caller can't silently alias one mode onto the
|
|
612
|
-
* other's cache slot.
|
|
613
|
-
*/
|
|
614
|
-
declare function scopedThemeName(baseThemeName: string, fontMode: string | undefined): string;
|
|
615
|
-
|
|
616
520
|
interface ApplyExportModeInput<D, T> {
|
|
617
521
|
doc: D;
|
|
618
522
|
theme: T;
|
|
@@ -694,4 +598,4 @@ declare const DEFAULT_CHART_THEME_COLORS: string[];
|
|
|
694
598
|
*/
|
|
695
599
|
declare function mergeWithDefaults<T>(userConfig: T, themeDefaults: Partial<T>): T;
|
|
696
600
|
|
|
697
|
-
export { DEFAULT_CHART_THEME_COLORS,
|
|
601
|
+
export { DEFAULT_CHART_THEME_COLORS, FONT_URL_ALLOWLIST, type FontIssueCode, FontRegistry, FontRegistryEntry, type FontRegistryInput, type FontResolutionIssue, FontRuntimeOpts, type FontSubstitution, type FontValidationInput, type FontValidationResult, POPULAR_GOOGLE_FONTS, type PopularGoogleFont, ResolvedFont, ResolvedFontSource, type SynthesizedFamily, UPSTREAM_OVERRIDES, type UpstreamOverride, type UpstreamVariant, WEIGHT_LABELS, applyExportMode, applyFontSubstitution, buildDefaultSubstitutionMap, collectFontNamesFromDocx, collectFontNamesFromPptx, defaultSubstituteFor, detectFontFormat, documentFontRegistry, fetchGoogleFontSources, getUpstreamOverride, isAllowedFontUrl, mergeFontRegistries, mergeWithDefaults, restructureNameDiscriminatedUnions, rewriteFontFamilyName, synthesizeFamilyName, themeFontRegistry, unionBranches, validateFontReferences };
|
package/dist/index.js
CHANGED
|
@@ -1,24 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
+
FONT_URL_ALLOWLIST,
|
|
2
3
|
detectFontFormat,
|
|
3
4
|
isAllowedFontUrl
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import {
|
|
6
|
-
convertToJsonSchema,
|
|
7
|
-
createComponentSchema,
|
|
8
|
-
createComponentSchemaObject,
|
|
9
|
-
exportSchemaToFile,
|
|
10
|
-
fixSchemaReferences,
|
|
11
|
-
restructureNameDiscriminatedUnions,
|
|
12
|
-
unionBranches
|
|
13
|
-
} from "./chunk-KLWNDWC4.js";
|
|
14
|
-
import {
|
|
15
|
-
FontFamilyNameSchema,
|
|
16
|
-
FontRegistryEntrySchema,
|
|
17
|
-
FontRegistrySchema,
|
|
18
|
-
FontSourceSchema,
|
|
19
|
-
SAFE_FONTS,
|
|
20
|
-
isSafeFont
|
|
21
|
-
} from "./chunk-6KUQYVPT.js";
|
|
5
|
+
} from "./chunk-FDSJYZ5W.js";
|
|
22
6
|
import {
|
|
23
7
|
ComponentValidationError,
|
|
24
8
|
DuplicateComponentError,
|
|
@@ -50,6 +34,34 @@ import {
|
|
|
50
34
|
transformValueError,
|
|
51
35
|
transformValueErrors
|
|
52
36
|
} from "./chunk-ZKD5BAMU.js";
|
|
37
|
+
import {
|
|
38
|
+
FeatureRequirementCollector,
|
|
39
|
+
RendererRegistry,
|
|
40
|
+
UnsupportedRendererFeatureError,
|
|
41
|
+
assertNever,
|
|
42
|
+
assertRendererSupports,
|
|
43
|
+
diagnoseUnsupportedFeatures,
|
|
44
|
+
partitionDiagnostics,
|
|
45
|
+
rendererError,
|
|
46
|
+
rendererWarning
|
|
47
|
+
} from "./chunk-JM5KTMNL.js";
|
|
48
|
+
import {
|
|
49
|
+
convertToJsonSchema,
|
|
50
|
+
createComponentSchema,
|
|
51
|
+
createComponentSchemaObject,
|
|
52
|
+
exportSchemaToFile,
|
|
53
|
+
fixSchemaReferences,
|
|
54
|
+
restructureNameDiscriminatedUnions,
|
|
55
|
+
unionBranches
|
|
56
|
+
} from "./chunk-SJ2YYRCT.js";
|
|
57
|
+
import {
|
|
58
|
+
FontFamilyNameSchema,
|
|
59
|
+
FontRegistryEntrySchema,
|
|
60
|
+
FontRegistrySchema,
|
|
61
|
+
FontSourceSchema,
|
|
62
|
+
SAFE_FONTS,
|
|
63
|
+
isSafeFont
|
|
64
|
+
} from "./chunk-6KUQYVPT.js";
|
|
53
65
|
import {
|
|
54
66
|
compareSemver,
|
|
55
67
|
isValidSemver,
|
|
@@ -66,6 +78,8 @@ function clampVisualDpi(dpi) {
|
|
|
66
78
|
return DEFAULT_VISUAL_DPI;
|
|
67
79
|
return Math.min(MAX_VISUAL_DPI, Math.max(MIN_VISUAL_DPI, Math.round(dpi)));
|
|
68
80
|
}
|
|
81
|
+
var MAX_RASTERIZE_FONTS = 32;
|
|
82
|
+
var MAX_RASTERIZE_FONT_BYTES = 8 * 1024 * 1024;
|
|
69
83
|
var MAX_RASTERIZE_BATCH_SLIDES = 32;
|
|
70
84
|
|
|
71
85
|
// src/fonts/collect.ts
|
|
@@ -79,6 +93,7 @@ var FONT_NAME_KEYS = /* @__PURE__ */ new Set([
|
|
|
79
93
|
"valAxisLabelFontFace"
|
|
80
94
|
]);
|
|
81
95
|
var THEME_FONT_KEYS = /* @__PURE__ */ new Set(["heading", "body", "mono", "light"]);
|
|
96
|
+
var FONT_DECLARATION_KEYS = /* @__PURE__ */ new Set(["fontRegistry"]);
|
|
82
97
|
function collect(node, out, parentKey) {
|
|
83
98
|
if (node == null) return;
|
|
84
99
|
if (typeof node === "string") {
|
|
@@ -111,6 +126,7 @@ function collect(node, out, parentKey) {
|
|
|
111
126
|
}
|
|
112
127
|
}
|
|
113
128
|
for (const [k, v] of Object.entries(node)) {
|
|
129
|
+
if (FONT_DECLARATION_KEYS.has(k)) continue;
|
|
114
130
|
collect(v, out, k);
|
|
115
131
|
}
|
|
116
132
|
}
|
|
@@ -123,6 +139,41 @@ function collectFontNames(doc) {
|
|
|
123
139
|
var collectFontNamesFromDocx = collectFontNames;
|
|
124
140
|
var collectFontNamesFromPptx = collectFontNames;
|
|
125
141
|
|
|
142
|
+
// src/fonts/document-registry.ts
|
|
143
|
+
function isEntry(v) {
|
|
144
|
+
if (!v || typeof v !== "object") return false;
|
|
145
|
+
const e = v;
|
|
146
|
+
return typeof e.id === "string" && typeof e.family === "string" && Array.isArray(e.sources);
|
|
147
|
+
}
|
|
148
|
+
function readAt(node, key) {
|
|
149
|
+
if (!node || typeof node !== "object") return [];
|
|
150
|
+
const raw = node[key];
|
|
151
|
+
return Array.isArray(raw) ? raw.filter(isEntry) : [];
|
|
152
|
+
}
|
|
153
|
+
function documentFontRegistry(document) {
|
|
154
|
+
if (!document || typeof document !== "object") return [];
|
|
155
|
+
return readAt(document.props, "fontRegistry");
|
|
156
|
+
}
|
|
157
|
+
function themeFontRegistry(theme) {
|
|
158
|
+
return readAt(theme, "fontRegistry");
|
|
159
|
+
}
|
|
160
|
+
function mergeFontRegistries(...groups) {
|
|
161
|
+
const out = [];
|
|
162
|
+
for (const group of groups) {
|
|
163
|
+
for (const entry of group ?? []) {
|
|
164
|
+
const family = entry.family.toLowerCase();
|
|
165
|
+
const id = entry.id.toLowerCase();
|
|
166
|
+
for (let i = out.length - 1; i >= 0; i--) {
|
|
167
|
+
if (out[i].family.toLowerCase() === family && out[i].id.toLowerCase() === id) {
|
|
168
|
+
out.splice(i, 1);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
out.push(entry);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
|
|
126
177
|
// src/fonts/validator.ts
|
|
127
178
|
function buildRegistryIndex(registeredEntries) {
|
|
128
179
|
const idx = /* @__PURE__ */ new Set();
|
|
@@ -1044,6 +1095,24 @@ var POPULAR_GOOGLE_FONTS = [
|
|
|
1044
1095
|
weights: [100, 200, 300, 400, 500, 600, 700],
|
|
1045
1096
|
hasItalic: true
|
|
1046
1097
|
},
|
|
1098
|
+
{
|
|
1099
|
+
family: "Archivo",
|
|
1100
|
+
category: "sans",
|
|
1101
|
+
weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],
|
|
1102
|
+
hasItalic: true
|
|
1103
|
+
},
|
|
1104
|
+
{
|
|
1105
|
+
family: "Geist",
|
|
1106
|
+
category: "sans",
|
|
1107
|
+
weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],
|
|
1108
|
+
hasItalic: true
|
|
1109
|
+
},
|
|
1110
|
+
{
|
|
1111
|
+
family: "Space Grotesk",
|
|
1112
|
+
category: "sans",
|
|
1113
|
+
weights: [300, 400, 500, 600, 700],
|
|
1114
|
+
hasItalic: false
|
|
1115
|
+
},
|
|
1047
1116
|
// Serif
|
|
1048
1117
|
{
|
|
1049
1118
|
family: "Playfair Display",
|
|
@@ -1118,6 +1187,12 @@ var POPULAR_GOOGLE_FONTS = [
|
|
|
1118
1187
|
weights: [400, 700],
|
|
1119
1188
|
hasItalic: true
|
|
1120
1189
|
},
|
|
1190
|
+
{
|
|
1191
|
+
family: "Geist Mono",
|
|
1192
|
+
category: "mono",
|
|
1193
|
+
weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],
|
|
1194
|
+
hasItalic: true
|
|
1195
|
+
},
|
|
1121
1196
|
// Display
|
|
1122
1197
|
{
|
|
1123
1198
|
family: "Bebas Neue",
|
|
@@ -1209,6 +1284,10 @@ function rewrite(node, mapping, seen, parentKey) {
|
|
|
1209
1284
|
if (typeof node === "object") {
|
|
1210
1285
|
const out = {};
|
|
1211
1286
|
for (const [k, v] of Object.entries(node)) {
|
|
1287
|
+
if (FONT_DECLARATION_KEYS.has(k)) {
|
|
1288
|
+
out[k] = v;
|
|
1289
|
+
continue;
|
|
1290
|
+
}
|
|
1212
1291
|
if (parentKey === "theme" && k === "fonts" && v && typeof v === "object") {
|
|
1213
1292
|
const nextFonts = {};
|
|
1214
1293
|
for (const [fk, fv] of Object.entries(v)) {
|
|
@@ -1251,6 +1330,9 @@ var EXPLICIT_OVERRIDES = {
|
|
|
1251
1330
|
"Source Sans 3": "Calibri",
|
|
1252
1331
|
"Source Sans Pro": "Calibri",
|
|
1253
1332
|
"IBM Plex Sans": "Calibri",
|
|
1333
|
+
Archivo: "Calibri",
|
|
1334
|
+
Geist: "Calibri",
|
|
1335
|
+
"Space Grotesk": "Calibri",
|
|
1254
1336
|
"Work Sans": "Calibri",
|
|
1255
1337
|
Manrope: "Calibri",
|
|
1256
1338
|
Nunito: "Calibri",
|
|
@@ -1270,7 +1352,8 @@ var EXPLICIT_OVERRIDES = {
|
|
|
1270
1352
|
"Fira Code": "Consolas",
|
|
1271
1353
|
"IBM Plex Mono": "Consolas",
|
|
1272
1354
|
"Source Code Pro": "Consolas",
|
|
1273
|
-
"Roboto Mono": "Consolas"
|
|
1355
|
+
"Roboto Mono": "Consolas",
|
|
1356
|
+
"Geist Mono": "Consolas"
|
|
1274
1357
|
};
|
|
1275
1358
|
var CATEGORY_FALLBACK = {
|
|
1276
1359
|
sans: "Calibri",
|
|
@@ -1304,9 +1387,6 @@ function buildDefaultSubstitutionMap(referencedNames) {
|
|
|
1304
1387
|
}
|
|
1305
1388
|
return out;
|
|
1306
1389
|
}
|
|
1307
|
-
function scopedThemeName(baseThemeName, fontMode) {
|
|
1308
|
-
return fontMode === "substitute" ? `${baseThemeName}#substitute` : baseThemeName;
|
|
1309
|
-
}
|
|
1310
1390
|
function applyExportMode(input) {
|
|
1311
1391
|
const mode = input.fonts?.mode ?? "custom";
|
|
1312
1392
|
if (mode === "custom") {
|
|
@@ -1400,21 +1480,29 @@ export {
|
|
|
1400
1480
|
DEFAULT_VISUAL_DPI,
|
|
1401
1481
|
DuplicateComponentError,
|
|
1402
1482
|
ERROR_EMOJIS,
|
|
1483
|
+
FONT_URL_ALLOWLIST,
|
|
1484
|
+
FeatureRequirementCollector,
|
|
1403
1485
|
FontFamilyNameSchema,
|
|
1404
1486
|
FontRegistry,
|
|
1405
1487
|
FontRegistryEntrySchema,
|
|
1406
1488
|
FontRegistrySchema,
|
|
1407
1489
|
FontSourceSchema,
|
|
1408
1490
|
MAX_RASTERIZE_BATCH_SLIDES,
|
|
1491
|
+
MAX_RASTERIZE_FONTS,
|
|
1492
|
+
MAX_RASTERIZE_FONT_BYTES,
|
|
1409
1493
|
MAX_VISUAL_DPI,
|
|
1410
1494
|
MIN_VISUAL_DPI,
|
|
1411
1495
|
POPULAR_GOOGLE_FONTS,
|
|
1496
|
+
RendererRegistry,
|
|
1412
1497
|
SAFE_FONTS,
|
|
1413
1498
|
UPSTREAM_OVERRIDES,
|
|
1414
1499
|
UnknownPreservedComponentError,
|
|
1500
|
+
UnsupportedRendererFeatureError,
|
|
1415
1501
|
WEIGHT_LABELS,
|
|
1416
1502
|
applyExportMode,
|
|
1417
1503
|
applyFontSubstitution,
|
|
1504
|
+
assertNever,
|
|
1505
|
+
assertRendererSupports,
|
|
1418
1506
|
buildDefaultSubstitutionMap,
|
|
1419
1507
|
calculatePosition,
|
|
1420
1508
|
clampVisualDpi,
|
|
@@ -1431,6 +1519,8 @@ export {
|
|
|
1431
1519
|
createVersion,
|
|
1432
1520
|
defaultSubstituteFor,
|
|
1433
1521
|
detectFontFormat,
|
|
1522
|
+
diagnoseUnsupportedFeatures,
|
|
1523
|
+
documentFontRegistry,
|
|
1434
1524
|
exportSchemaToFile,
|
|
1435
1525
|
extractStandardComponentNames,
|
|
1436
1526
|
fetchGoogleFontSources,
|
|
@@ -1443,6 +1533,7 @@ export {
|
|
|
1443
1533
|
getUpstreamOverride,
|
|
1444
1534
|
getValidationSummary,
|
|
1445
1535
|
groupErrorsByPath,
|
|
1536
|
+
isAllowedFontUrl,
|
|
1446
1537
|
isLiteralSchema,
|
|
1447
1538
|
isObjectSchema,
|
|
1448
1539
|
isSafeFont,
|
|
@@ -1450,13 +1541,17 @@ export {
|
|
|
1450
1541
|
isValidSemver,
|
|
1451
1542
|
isValidationSuccess,
|
|
1452
1543
|
latestVersion,
|
|
1544
|
+
mergeFontRegistries,
|
|
1453
1545
|
mergeWithDefaults,
|
|
1454
1546
|
parseSemver,
|
|
1547
|
+
partitionDiagnostics,
|
|
1548
|
+
rendererError,
|
|
1549
|
+
rendererWarning,
|
|
1455
1550
|
resolveComponentVersion,
|
|
1456
1551
|
restructureNameDiscriminatedUnions,
|
|
1457
1552
|
rewriteFontFamilyName,
|
|
1458
|
-
scopedThemeName,
|
|
1459
1553
|
synthesizeFamilyName,
|
|
1554
|
+
themeFontRegistry,
|
|
1460
1555
|
transformValueError,
|
|
1461
1556
|
transformValueErrors,
|
|
1462
1557
|
unionBranches,
|