@json-to-office/jto-ops 6.8.0 → 7.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/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/format-adapter.ts","../src/pptx-rasterizer.ts","../src/font-staging/noop-stager.ts","../src/font-staging/fontconfig-stager.ts","../src/font-staging/types.ts","../src/font-staging/windows-stager.ts","../src/font-staging/macos-stager.ts","../src/diagnostics.ts","../src/font-staging/index.ts","../src/pdf-text-geometry.ts","../src/pdf-page-ink.ts","../src/pdf-fonts.ts","../src/rendered-analysis.ts","../src/rendered-rules.ts","../src/rendered-text-match.ts","../src/rendered-inventory.ts","../src/block-matrix.ts"],"sourcesContent":["import * as path from 'path';\nimport * as fs from 'fs';\n\nimport type {\n ServicesConfig,\n FontRuntimeOpts,\n PptxRasterizer,\n PptxBatchRasterizer,\n GenerationWarning,\n RendererStatus,\n} from '@json-to-office/shared';\nimport type {\n PreparedDocument,\n QualityAnalysis,\n QualityPolicy,\n QualityProfile,\n} from '@json-to-office/quality';\nimport { validatePresentationDocument } from '@json-to-office/shared-pptx';\nimport { validate as validateDocx } from '@json-to-office/shared-docx';\nimport {\n createLibreOfficePptxRasterizer,\n createLibreOfficePptxBatchRasterizer,\n} from './pptx-rasterizer.js';\nimport { emitDiagnostic } from './diagnostics.js';\n\n/** Forward structured warnings collected during generation to the host's sink. */\nfunction emitGenerationWarnings(warnings: GenerationWarning[]): void {\n for (const warning of warnings) {\n emitDiagnostic(\n `${warning.component}: ${warning.message}`,\n warning.severity === 'info' ? 'info' : 'warning'\n );\n }\n}\n\n/**\n * Normalize a core's warning array into the single client/CLI-facing shape.\n *\n * DOCX cores already emit `GenerationWarning`; PPTX cores emit\n * `PipelineWarning = {code, message, component?, slide?}` — no `severity` and\n * an optional `component`. Left raw, a PipelineWarning renders as an empty\n * component chip in the playground's WarningsPanel, so both shapes funnel\n * through here and come out self-describing.\n */\nfunction toGenerationWarnings(\n raw: readonly any[] | null | undefined\n): GenerationWarning[] {\n return (raw ?? []).map((w) => ({\n component: w?.component ?? 'pptx',\n message: String(w?.message ?? ''),\n severity: (w?.severity === 'info' ? 'info' : 'warning') as\n | 'warning'\n | 'info',\n context: {\n ...(w?.context && typeof w.context === 'object' ? w.context : {}),\n ...(w?.code !== undefined && { code: w.code }),\n ...(w?.slide !== undefined && { slide: w.slide }),\n },\n }));\n}\n\nfunction preparedThemeLabel(prepared: PreparedDocument): string | undefined {\n const label = prepared.metadata?.themeLabel;\n return typeof label === 'string' ? label : undefined;\n}\n\n/**\n * The document a prepared model was built from, and the warnings its\n * preparation already reported.\n *\n * Symbols, so neither crosses a serialization boundary: `PreparedDocument` is\n * a serializable contract, and a model that arrives without its source is\n * re-prepared rather than rendered in place of the document it was handed.\n */\nconst PREPARED_SOURCE = Symbol('jto.prepared.source');\nconst PREPARED_WARNINGS = Symbol('jto.prepared.warnings');\n\ninterface PreparedInternals {\n [PREPARED_SOURCE]?: unknown;\n [PREPARED_WARNINGS]?: readonly GenerationWarning[];\n}\n\n/** Bind a prepared model to the document and the warnings it came from. */\nfunction stampPrepared<T extends PreparedDocument>(\n prepared: T,\n source: unknown,\n warnings: readonly GenerationWarning[]\n): T {\n return Object.assign(prepared, {\n [PREPARED_SOURCE]: source,\n [PREPARED_WARNINGS]: warnings,\n });\n}\n\n/**\n * A prepared model, but only for the document it was prepared from.\n *\n * `generateBuffer(document)` is a per-document API while `prepared` is fixed\n * when the generator is built, so any other document would be validated as\n * itself and then rendered as the prepared one.\n */\nfunction preparedFor<T extends PreparedDocument>(\n prepared: T | undefined,\n document: unknown\n): T | undefined {\n if (!prepared) return undefined;\n const source = (prepared as PreparedInternals)[PREPARED_SOURCE];\n return source !== undefined && source === document ? prepared : undefined;\n}\n\n/** Warning identity, for the prepare/render overlap only. */\nfunction warningKey(warning: GenerationWarning): string {\n return `${warning.component}|${warning.context?.code ?? ''}|${warning.message}`;\n}\n\n/**\n * Drop render warnings this model's preparation already reported.\n *\n * Preparation repeats work the render does again — pptx block, layout and grid\n * resolution above all — so reusing a prepared model would report those\n * warnings twice. Deliberately narrow: nothing is deduplicated except against\n * what preparing this very model emitted.\n */\nfunction withoutPreparedWarnings(\n warnings: GenerationWarning[],\n prepared: PreparedDocument | undefined\n): GenerationWarning[] {\n const emitted = (prepared as PreparedInternals | undefined)?.[\n PREPARED_WARNINGS\n ];\n if (!emitted || emitted.length === 0) return warnings;\n const seen = new Set(emitted.map(warningKey));\n return warnings.filter((warning) => !seen.has(warningKey(warning)));\n}\n\n/**\n * The renderer the document itself names. Core generation resolves\n * `options.renderer ?? document.renderer`, so preparation that reads only the\n * option stamps the default onto the model and misjudges a profile targeted\n * at the backend the document actually renders with.\n */\nfunction documentRenderer(document: unknown): string | undefined {\n const renderer = (document as { renderer?: unknown } | null | undefined)\n ?.renderer;\n return typeof renderer === 'string' ? renderer : undefined;\n}\n\nconst UNSAFE_KEYS = new Set(['__proto__', 'constructor', 'prototype']);\nfunction safeThemeKey(name: string | undefined): string {\n return name && !UNSAFE_KEYS.has(name) ? name : 'custom';\n}\n\n/** Key the theme named by `--theme`/`--theme-path` is registered under. */\nconst CLI_THEME_KEY = 'jto-cli-theme';\n\n/**\n * Point a document at the explicitly requested theme. The JSON path selects a\n * theme by name off `props.theme`, so `--theme`/`--theme-path` is applied by\n * registering the resolved theme under a reserved key and rewriting the\n * reference — an explicit theme wins over the document's own `props.theme`.\n * With no theme requested the document is passed through untouched.\n */\nfunction withRequestedTheme(\n document: any,\n theme: any | undefined,\n customThemes: Record<string, any> | undefined\n): { document: any; customThemes: Record<string, any> | undefined } {\n if (!theme || typeof document !== 'object' || document === null) {\n return { document, customThemes };\n }\n return {\n document: {\n ...document,\n props: { ...document.props, theme: CLI_THEME_KEY },\n },\n customThemes: { ...customThemes, [CLI_THEME_KEY]: theme },\n };\n}\n\nexport type FormatName = 'docx' | 'pptx';\n\nfunction buildServicesFromEnv(): ServicesConfig | undefined {\n const serverUrl = process.env.HIGHCHARTS_SERVER_URL;\n const apiKey = process.env.HIGHCHARTS_API_KEY;\n const apiKeyHeader = process.env.HIGHCHARTS_API_KEY_HEADER ?? 'x-api-key';\n // A public export server receives every chart's data; the switch that\n // permits it is deliberate and separate from naming the URL.\n const allowRemote = /^(1|true|yes)$/i.test(\n process.env.HIGHCHARTS_ALLOW_REMOTE ?? ''\n );\n\n if (!serverUrl && !apiKey && !allowRemote) return undefined;\n\n return {\n highcharts: {\n serverUrl,\n ...(apiKey && { headers: { [apiKeyHeader]: apiKey } }),\n ...(allowRemote && { allowRemote }),\n },\n };\n}\n\n// Lazily-constructed LibreOffice rasterizers, shared across docx generations.\n// Constructing them is cheap (no binaries touched); they only spawn\n// LibreOffice when a document actually contains a `visual` component. Single\n// and batch share the same content-addressed disk cache.\nlet cachedRasterizer: PptxRasterizer | undefined;\nfunction getPptxRasterizer(): PptxRasterizer {\n if (!cachedRasterizer) {\n cachedRasterizer = createLibreOfficePptxRasterizer();\n }\n return cachedRasterizer;\n}\nlet cachedBatchRasterizer: PptxBatchRasterizer | undefined;\nfunction getPptxBatchRasterizer(): PptxBatchRasterizer {\n if (!cachedBatchRasterizer) {\n cachedBatchRasterizer = createLibreOfficePptxBatchRasterizer();\n }\n return cachedBatchRasterizer;\n}\n\n/**\n * Services for docx generation: highcharts (from env) plus the pptx rasterizer\n * that backs `visual` components. An explicit HIGHCHARTS-style override is not\n * needed for pptx — a running rasterization server can be pointed at via\n * `services.pptx.serverUrl`, but the default is the in-process LibreOffice\n * renderer.\n */\n/**\n * Services a caller asked for win over the environment, service by service:\n * a test that names an unreachable export server must reach it, whatever\n * `HIGHCHARTS_SERVER_URL` says.\n */\nfunction withRequestedServices(\n base: ServicesConfig | undefined,\n requested: ServicesConfig | undefined\n): ServicesConfig | undefined {\n if (!requested) return base;\n return {\n ...base,\n ...requested,\n ...(base?.highcharts || requested.highcharts\n ? { highcharts: { ...base?.highcharts, ...requested.highcharts } }\n : {}),\n };\n}\n\nfunction buildDocxServices(): ServicesConfig {\n const base = buildServicesFromEnv() ?? {};\n const serverUrl = process.env.JTO_PPTX_RASTERIZER_URL?.trim();\n const apiKey =\n process.env.JTO_PPTX_RASTERIZER_API_KEY || process.env.HIGHCHARTS_API_KEY;\n const apiKeyHeader =\n process.env.JTO_PPTX_RASTERIZER_API_KEY_HEADER ||\n process.env.HIGHCHARTS_API_KEY_HEADER ||\n 'x-api-key';\n return {\n ...base,\n pptx: serverUrl\n ? {\n serverUrl,\n ...(apiKey && { headers: { [apiKeyHeader]: apiKey } }),\n }\n : {\n render: getPptxRasterizer(),\n renderBatch: getPptxBatchRasterizer(),\n },\n };\n}\n\n/** Minimal builder shape shared by DOCX and PPTX generators */\ninterface GeneratorBuilder {\n addComponent(component: any): GeneratorBuilder;\n validate(document: any): {\n valid: boolean;\n errors?: { path: string; message: string }[];\n };\n generateBuffer(\n document: any,\n options?: {\n deterministic?: boolean;\n generatedAt?: string | Date;\n validation?: { allowUnknownFields?: boolean };\n baseDir?: string;\n renderer?: string;\n /** DOCX only; PPTX generators ignore it. */\n svgRasterFallback?: boolean;\n }\n ): Promise<{ buffer: Buffer; warnings: any }>;\n /**\n * Cheap standard-definition path: expansion + normalization only, no\n * rendering (DOCX generators expose it; PPTX ones may not).\n */\n expandStandardDefinition?: (\n document: any,\n options?: { validation?: { allowUnknownFields?: boolean } }\n ) => Promise<{ standardDefinition: any; warnings: any }>;\n}\n\n/**\n * Each format's renderer-id union, read from its core without importing it.\n *\n * `typeof import(...)` is a type query — erased at compile time — so this keeps\n * the ids honest while the cores stay dynamically loaded. `GeneratorOptions`\n * carries a bare string because one options bag serves both formats; these are\n * what it narrows to at each call site, and an id outside the union is rejected\n * by the core's registry with the list of valid ones.\n */\ntype DocxRendererId =\n (typeof import('@json-to-office/core-docx'))['DEFAULT_DOCX_RENDERER_ID'];\ntype PptxRendererId =\n (typeof import('@json-to-office/core-pptx'))['DEFAULT_PPTX_RENDERER_ID'];\n\nexport interface GeneratorOptions {\n theme?: string | any;\n themePath?: string;\n customThemes?: Record<string, any>;\n validation?: {\n strict?: boolean;\n allowUnknownFields?: boolean;\n };\n fonts?: FontRuntimeOpts;\n /**\n * External services for this generation: an export server for\n * `highcharts`, a rasterizer for `visual`. Wins over the environment,\n * service by service.\n */\n services?: ServicesConfig;\n deterministic?: boolean;\n generatedAt?: string | Date;\n /**\n * Directory that relative asset paths in the document resolve against —\n * normally the input document's own directory (#142).\n */\n baseDir?: string;\n /**\n * Backend that turns the compiled document into bytes.\n *\n * Format-specific and validated by the core's renderer registry, which is\n * why this is a bare string here: naming the id union would make this\n * package import both cores statically, and they are loaded on demand.\n * Undefined means the format's default (`docxjs` / `pptxgenjs`).\n */\n renderer?: string;\n /**\n * Rasterize a PNG fallback for each inline SVG. Defaults to true.\n *\n * DOCX only — pptx embeds SVG without a raster twin. Only readers older than\n * Word 2016 draw it, and producing it dominates the render of a document\n * whose artwork is many small SVGs.\n */\n svgRasterFallback?: boolean;\n /**\n * Optional sink for structured generation warnings (FONT_UNRESOLVED and\n * friends). Mirrors core-docx's `JsonGenerationOptions.warnings`, and is the\n * only delivery mechanism that works off the CLI: `emitGenerationWarnings`\n * routes through an AsyncLocalStorage sink that is a no-op on the server.\n *\n * Adapters PUSH into it; they never replace it. Warnings therefore\n * ACCUMULATE across repeated `generateBuffer` calls on one\n * `GeneratorResult` — allocate one array per logical request.\n */\n warnings?: GenerationWarning[];\n /** Design profile and invocation-specific enforcement. */\n quality?: {\n profile?: QualityProfile;\n policy?: QualityPolicy;\n };\n /** Opaque canonical prologue output shared by analysis and rendering. */\n prepared?: PreparedDocument;\n}\n\nexport interface GeneratorResult {\n generateBuffer: (document: any) => Promise<Buffer>;\n /**\n * Post-expansion standard JSON tree without any rendering work — no fonts,\n * no layout, no visual rasterization. Present when the underlying generator\n * supports it (plugin-aware DOCX generation does).\n */\n getStandardDefinition?: (config: any) => Promise<any>;\n hasPlugins: boolean;\n pluginNames: string[];\n /**\n * Identity of the theme this generator forces on every document, or\n * undefined when nothing was requested and each document's own `props.theme`\n * decides. Reported by the CLI so the summary names what actually rendered.\n */\n themeLabel?: string;\n}\n\n/** One resolution of `theme`/`themePath`, shared by every consumer of a run. */\ninterface ResolvedThemes {\n /**\n * The theme named by `theme`/`themePath`, or undefined when neither is set\n * or resolves — callers that must not override the document's own\n * `props.theme` depend on that distinction.\n */\n requested: any | undefined;\n /** Themes registered by name for `props.theme` lookups. */\n customThemes: Record<string, any> | undefined;\n /** What to call `requested`; undefined when the document decides. */\n label: string | undefined;\n}\n\nexport interface FormatAdapter {\n name: FormatName;\n extension: string;\n label: string;\n defaultPort: number;\n\n generateBuffer(json: unknown, options: GeneratorOptions): Promise<Buffer>;\n\n createGenerator(\n plugins: any[],\n options: GeneratorOptions\n ): Promise<GeneratorResult>;\n\n parseJson(input: string | object): unknown;\n validateDocument(doc: unknown): { valid: boolean; errors?: any[] };\n\n /**\n * Validate a document that names plugin components.\n *\n * `validateDocument` above knows the standard components and nothing else,\n * so a registered plugin reads to it as `Unknown component \"weather\"` —\n * the same name the schema route offers for completion and the generator\n * expands. The core validators take the registered components, defer those\n * nodes from the standard walk, and check each one's props against the\n * version it resolves to; this is the seam that reaches them.\n *\n * Async because the core that owns them is imported on demand, as\n * `analyzeQuality` does. Callers with no plugins registered should keep\n * using the sync entry point.\n */\n validateDocumentWithPlugins?(\n doc: unknown,\n plugins: any[]\n ): Promise<{ valid: boolean; errors?: any[] }>;\n\n /** Analyze format-specific design quality with profiles, policy, and gate. */\n analyzeQuality?(\n doc: unknown,\n options?: GeneratorOptions\n ): Promise<QualityAnalysis>;\n\n /** Prepare effective values and provenance once for official pipelines. */\n prepareDocument?(\n doc: unknown,\n options?: GeneratorOptions\n ): Promise<PreparedDocument>;\n\n generateSchema(options?: any): any;\n\n getBuiltinThemes(): Record<string, any>;\n /** Full values for ESM hosts; falls back to `getBuiltinThemes` for plugins. */\n getBuiltinThemeValues?(): Promise<Record<string, any>>;\n resolveTheme(options: GeneratorOptions): Promise<any>;\n loadCustomThemes(\n options: GeneratorOptions\n ): Promise<Record<string, any> | undefined>;\n\n /**\n * Renderer ids this format registers, defaults first.\n *\n * Async because the core that owns the registry is imported on demand — the\n * list is read from it rather than repeated here, so the two cannot drift.\n */\n rendererIds(): Promise<readonly string[]>;\n\n /**\n * The same renderers, each with whether its backend loads on this host.\n *\n * `rendererIds` answers \"what is registered\", which is not the same question:\n * a factory only runs when its renderer is selected, so an id says nothing\n * about whether the render behind it will work. Anything that *advertises*\n * renderers should report this instead — otherwise a caller picks one, gets\n * a green light from validation, and fails a call later.\n */\n rendererStatuses(): Promise<readonly RendererStatus[]>;\n\n /** Cumulative visual pre-pass dedupe counters (DOCX only) (#156). */\n getVisualPrepassStats?(): Promise<any>;\n /** Reset per-format cache observability counters (DOCX only). */\n resetCacheStats?(): Promise<void>;\n}\n\nexport class DocxFormatAdapter implements FormatAdapter {\n name: FormatName = 'docx';\n extension = '.docx';\n label = 'document';\n defaultPort = 3003;\n\n async rendererIds(): Promise<readonly string[]> {\n const core = await import('@json-to-office/core-docx');\n return core.docxRendererIds();\n }\n\n async rendererStatuses(): Promise<readonly RendererStatus[]> {\n const core = await import('@json-to-office/core-docx');\n return core.docxRendererStatuses();\n }\n\n async generateBuffer(\n json: unknown,\n options: GeneratorOptions\n ): Promise<Buffer> {\n const core = await import('@json-to-office/core-docx');\n const parsed = typeof json === 'string' ? JSON.parse(json as string) : json;\n const prepared = preparedFor(\n options.prepared?.format === 'docx'\n ? (options.prepared as ReturnType<\n typeof core.prepareDocxQualityDocument\n >)\n : undefined,\n parsed\n );\n let docDefinition: unknown;\n let customThemes: Record<string, any> | undefined;\n if (prepared) {\n docDefinition = prepared.model.authored;\n } else {\n const resolved = await this.resolveThemes(options);\n const normalized = withRequestedTheme(\n parsed,\n resolved.requested,\n resolved.customThemes\n );\n docDefinition = normalized.document;\n customThemes = normalized.customThemes;\n }\n const services = withRequestedServices(\n buildDocxServices(),\n options.services\n );\n // Collect rather than swallow: without a sink, core warnings (an\n // unresolvable `props.theme` among them) never reach the terminal and the\n // render just comes back looking subtly wrong.\n const warnings: GenerationWarning[] = [];\n const buffer = await core.generateBufferFromJson(docDefinition as any, {\n customThemes,\n services,\n fonts: options.fonts,\n validation: {\n allowUnknownFields: options.validation?.allowUnknownFields,\n },\n deterministic: options.deterministic,\n generatedAt: options.generatedAt,\n baseDir: options.baseDir,\n renderer: options.renderer as DocxRendererId | undefined,\n svgRasterFallback: options.svgRasterFallback,\n prepared,\n warnings,\n });\n const emitted = withoutPreparedWarnings(warnings, prepared);\n emitGenerationWarnings(emitted);\n options.warnings?.push(...toGenerationWarnings(emitted));\n return buffer;\n }\n\n async createGenerator(\n plugins: any[],\n options: GeneratorOptions\n ): Promise<GeneratorResult> {\n const core = await import('@json-to-office/core-docx');\n const hasPlugins = plugins.length > 0;\n const pluginNames = plugins.map((p) => p.name);\n const services = withRequestedServices(\n buildDocxServices(),\n options.services\n );\n\n // Resolve once unless the canonical model already carries the result.\n const prepared =\n !hasPlugins && options.prepared?.format === 'docx'\n ? (options.prepared as ReturnType<\n typeof core.prepareDocxQualityDocument\n >)\n : undefined;\n const resolved = prepared ? undefined : await this.resolveThemes(options);\n const requestedTheme = resolved?.requested;\n const customThemes = resolved?.customThemes;\n const themeLabel = prepared\n ? preparedThemeLabel(prepared)\n : resolved?.label;\n\n if (!hasPlugins) {\n // A prepared model renders only its own document; any other one falls\n // back to resolving themes here, memoized so a bad `--theme` still\n // warns once per generator.\n let fallbackThemes: Promise<ResolvedThemes> | undefined;\n const themesFor = (): Promise<ResolvedThemes> =>\n (fallbackThemes ??= resolved\n ? Promise.resolve(resolved)\n : this.resolveThemes(options));\n return {\n generateBuffer: async (document: any) => {\n const parsed =\n typeof document === 'string' ? JSON.parse(document) : document;\n const usable = preparedFor(prepared, parsed);\n const themes = usable ? undefined : await themesFor();\n const normalized = usable\n ? { document: usable.model.authored, customThemes: undefined }\n : withRequestedTheme(\n parsed,\n themes?.requested,\n themes?.customThemes\n );\n const warnings: GenerationWarning[] = [];\n const buffer = await core.generateBufferFromJson(\n normalized.document,\n {\n customThemes: normalized.customThemes,\n services,\n fonts: options.fonts,\n validation: {\n allowUnknownFields: options.validation?.allowUnknownFields,\n },\n deterministic: options.deterministic,\n generatedAt: options.generatedAt,\n baseDir: options.baseDir,\n renderer: options.renderer as DocxRendererId | undefined,\n svgRasterFallback: options.svgRasterFallback,\n prepared: usable,\n warnings,\n }\n );\n const emitted = withoutPreparedWarnings(warnings, usable);\n emitGenerationWarnings(emitted);\n options.warnings?.push(...toGenerationWarnings(emitted));\n return buffer;\n },\n hasPlugins: false,\n pluginNames: [],\n themeLabel,\n };\n }\n\n let generator: GeneratorBuilder = core.createDocumentGenerator({\n // Undefined when nothing was requested: a constructor theme beats the\n // generator's own `props.theme` lookup, so forcing one here would render\n // every document in it.\n theme: requestedTheme,\n customThemes: requestedTheme\n ? { ...customThemes, [CLI_THEME_KEY]: requestedTheme }\n : customThemes,\n debug: process.env.DEBUG === 'true',\n services,\n fonts: options.fonts,\n validation: {\n allowUnknownFields: options.validation?.allowUnknownFields,\n },\n deterministic: options.deterministic,\n generatedAt: options.generatedAt,\n baseDir: options.baseDir,\n renderer: options.renderer as DocxRendererId | undefined,\n svgRasterFallback: options.svgRasterFallback,\n });\n\n for (const plugin of plugins) {\n generator = generator.addComponent(plugin);\n }\n\n return {\n generateBuffer: async (document: any) => {\n const parsed =\n typeof document === 'string' ? JSON.parse(document) : document;\n const { document: docDefinition } = withRequestedTheme(\n parsed,\n requestedTheme,\n customThemes\n );\n const result = await generator.generateBuffer(docDefinition, {\n validation: {\n allowUnknownFields: options.validation?.allowUnknownFields,\n },\n deterministic: options.deterministic,\n generatedAt: options.generatedAt,\n baseDir: options.baseDir,\n renderer: options.renderer as DocxRendererId | undefined,\n svgRasterFallback: options.svgRasterFallback,\n });\n emitGenerationWarnings(result.warnings ?? []);\n options.warnings?.push(...toGenerationWarnings(result.warnings));\n return result.buffer;\n },\n getStandardDefinition: generator.expandStandardDefinition\n ? async (config: any) => {\n const parsed =\n typeof config === 'string' ? JSON.parse(config) : config;\n const { document: docDefinition } = withRequestedTheme(\n parsed,\n requestedTheme,\n customThemes\n );\n const result = await generator.expandStandardDefinition!(\n docDefinition,\n {\n validation: {\n allowUnknownFields: options.validation?.allowUnknownFields,\n },\n }\n );\n emitGenerationWarnings(result.warnings ?? []);\n return result.standardDefinition;\n }\n : undefined,\n hasPlugins: true,\n pluginNames,\n themeLabel,\n };\n }\n\n parseJson(input: string | object): unknown {\n return typeof input === 'string' ? JSON.parse(input) : input;\n }\n\n validateDocument(doc: unknown): { valid: boolean; errors?: any[] } {\n const result = validateDocx.jsonDocument(doc as object);\n return {\n valid: result.valid,\n ...(result.errors.length > 0 && { errors: result.errors }),\n };\n }\n\n async validateDocumentWithPlugins(\n doc: unknown,\n plugins: any[]\n ): Promise<{ valid: boolean; errors?: any[] }> {\n const core = await import('@json-to-office/core-docx');\n const parsed = typeof doc === 'string' ? JSON.parse(doc) : doc;\n const result = core.validateDocument(parsed as any, plugins);\n const errors = result.errors ?? [];\n return {\n valid: result.valid,\n ...(errors.length > 0 && { errors }),\n };\n }\n\n async analyzeQuality(\n doc: unknown,\n options: GeneratorOptions = {}\n ): Promise<QualityAnalysis> {\n const core = await import('@json-to-office/core-docx');\n const parsed = typeof doc === 'string' ? JSON.parse(doc) : doc;\n let prepared = preparedFor(\n options.prepared?.format === 'docx'\n ? (options.prepared as ReturnType<\n typeof core.prepareDocxQualityDocument\n >)\n : undefined,\n parsed\n );\n if (!prepared) {\n try {\n prepared = (await this.prepareModel(parsed, options, [])) as ReturnType<\n typeof core.prepareDocxQualityDocument\n >;\n } catch {\n // Structural validation owns malformed trees. The core guards its own\n // preparation, so handing it the document instead of a model turns a\n // throw into an analysis that reports the failure.\n }\n }\n return core.analyzeDocxQuality(prepared?.model.authored ?? parsed, {\n ...(prepared && { prepared }),\n renderer: options.renderer ?? documentRenderer(parsed),\n profile: options.quality?.profile,\n policy: options.quality?.policy,\n });\n }\n\n async prepareDocument(\n doc: unknown,\n options: GeneratorOptions = {}\n ): Promise<PreparedDocument> {\n // Preparation resolves the theme context the render then skips, so these\n // warnings reach the host from here or not at all.\n const warnings: GenerationWarning[] = [];\n const prepared = await this.prepareModel(doc, options, warnings);\n emitGenerationWarnings(warnings);\n options.warnings?.push(...toGenerationWarnings(warnings));\n return prepared;\n }\n\n /** Prepare into a caller-owned sink; `prepareDocument` owns the reporting. */\n private async prepareModel(\n doc: unknown,\n options: GeneratorOptions,\n warnings: GenerationWarning[]\n ): Promise<PreparedDocument> {\n const core = await import('@json-to-office/core-docx');\n const parsed = typeof doc === 'string' ? JSON.parse(doc) : doc;\n const resolved = await this.resolveThemes(options);\n const normalized = withRequestedTheme(\n parsed,\n resolved.requested,\n resolved.customThemes\n );\n const prepared = core.prepareDocxQualityDocument(\n normalized.document as any,\n {\n customThemes: normalized.customThemes,\n fonts: options.fonts,\n renderer: options.renderer ?? documentRenderer(parsed),\n warnings,\n }\n );\n return stampPrepared(\n {\n ...prepared,\n metadata: {\n ...prepared.metadata,\n ...(resolved.label && { themeLabel: resolved.label }),\n },\n },\n parsed,\n toGenerationWarnings(warnings)\n );\n }\n\n generateSchema(_options?: any): any {\n // Delegate to shared-docx\n return null;\n }\n\n getBuiltinThemes(): Record<string, any> {\n try {\n const core = require('@json-to-office/core-docx');\n return core.themes || {};\n } catch {\n return {};\n }\n }\n\n async getBuiltinThemeValues(): Promise<Record<string, any>> {\n const core = await import('@json-to-office/core-docx');\n return core.themes || {};\n }\n\n async resolveTheme(options: GeneratorOptions): Promise<any> {\n const core = await import('@json-to-office/core-docx');\n const { requested } = await this.resolveThemes(options);\n return requested ?? (core.themes as any)?.minimal ?? {};\n }\n\n /**\n * Resolve `theme`/`themePath` once for a whole run: `themePath` is read a\n * single time and feeds both the requested theme and the custom-theme\n * registry, so a bad path warns once instead of once per consumer.\n */\n private async resolveThemes(\n options: GeneratorOptions\n ): Promise<ResolvedThemes> {\n const core = await import('@json-to-office/core-docx');\n // Themes passed directly from the client (playground UI) come first.\n const registry: Record<string, any> = { ...options.customThemes };\n\n if (typeof options.theme === 'object' && options.theme !== null) {\n registry[safeThemeKey(options.theme.name)] = options.theme;\n }\n\n let fileTheme: any | undefined;\n if (options.themePath) {\n try {\n if (options.themePath.endsWith('.json')) {\n fileTheme = await core.loadThemeFromFile(options.themePath);\n } else {\n const themePath = path.resolve(process.cwd(), options.themePath);\n const themeModule = await import(themePath);\n fileTheme = themeModule.default || themeModule.theme;\n }\n } catch (error: any) {\n emitDiagnostic(\n `Failed to load theme from ${options.themePath}: ${error.message}`,\n 'warning'\n );\n }\n if (fileTheme) {\n registry[safeThemeKey(fileTheme.name)] = fileTheme;\n }\n }\n\n const customThemes =\n Object.keys(registry).length > 0 ? registry : undefined;\n\n if (fileTheme) {\n return { requested: fileTheme, customThemes, label: options.themePath };\n }\n\n if (typeof options.theme === 'string') {\n const named =\n options.customThemes?.[options.theme] ??\n (core.themes as Record<string, any>)?.[options.theme];\n if (named)\n return { requested: named, customThemes, label: options.theme };\n\n if (options.theme.endsWith('.json') && fs.existsSync(options.theme)) {\n try {\n return {\n requested: await core.loadThemeFromFile(options.theme),\n customThemes,\n label: options.theme,\n };\n } catch {}\n }\n\n try {\n const inline = await core.loadThemeFromJson(options.theme);\n return {\n requested: inline,\n customThemes,\n label: (inline as any)?.name || options.theme,\n };\n } catch {}\n\n emitDiagnostic(\n `Unknown theme \"${options.theme}\"; keeping the document's own theme`,\n 'warning'\n );\n }\n\n if (typeof options.theme === 'object' && options.theme !== null) {\n return {\n requested: options.theme,\n customThemes,\n label: safeThemeKey(options.theme.name),\n };\n }\n\n return { requested: undefined, customThemes, label: undefined };\n }\n\n async loadCustomThemes(\n options: GeneratorOptions\n ): Promise<Record<string, any> | undefined> {\n return (await this.resolveThemes(options)).customThemes;\n }\n\n async getVisualPrepassStats(): Promise<any> {\n try {\n const core = await import('@json-to-office/core-docx');\n return core.getVisualPrepassStats?.() ?? null;\n } catch {\n return null;\n }\n }\n\n async resetCacheStats(): Promise<void> {\n try {\n const core = await import('@json-to-office/core-docx');\n core.resetVisualPrepassStats?.();\n } catch {\n // Resetting observability is best-effort.\n }\n }\n}\n\nexport class PptxFormatAdapter implements FormatAdapter {\n name: FormatName = 'pptx';\n extension = '.pptx';\n label = 'presentation';\n defaultPort = 3004;\n\n async rendererIds(): Promise<readonly string[]> {\n const core = await import('@json-to-office/core-pptx');\n return core.pptxRendererIds();\n }\n\n async rendererStatuses(): Promise<readonly RendererStatus[]> {\n const core = await import('@json-to-office/core-pptx');\n return core.pptxRendererStatuses();\n }\n\n async generateBuffer(\n json: unknown,\n options: GeneratorOptions\n ): Promise<Buffer> {\n const core = await import('@json-to-office/core-pptx');\n const parsed = typeof json === 'string' ? JSON.parse(json as string) : json;\n const prepared = preparedFor(\n options.prepared?.format === 'pptx'\n ? (options.prepared as ReturnType<\n typeof core.preparePptxQualityDocument\n >)\n : undefined,\n parsed\n );\n let docDefinition: unknown;\n let customThemes: Record<string, any> | undefined;\n if (prepared) {\n docDefinition = prepared.model.authored;\n } else {\n const resolved = await this.resolveThemes(options);\n const normalized = withRequestedTheme(\n parsed,\n resolved.requested,\n resolved.customThemes\n );\n docDefinition = normalized.document;\n customThemes = normalized.customThemes;\n }\n const services = withRequestedServices(\n buildServicesFromEnv(),\n options.services\n );\n // The warnings-returning entry point: `generateBufferFromJson` allocates\n // the pipeline's warning array internally and throws it away, so core\n // warnings (FONT_UNRESOLVED among them) never reached the terminal or the\n // server.\n const result = await core.generateBufferWithWarnings(docDefinition as any, {\n customThemes,\n services,\n fonts: options.fonts,\n validation: {\n allowUnknownFields: options.validation?.allowUnknownFields,\n },\n deterministic: options.deterministic,\n generatedAt: options.generatedAt,\n baseDir: options.baseDir,\n renderer: options.renderer as PptxRendererId | undefined,\n prepared,\n });\n const emitted = withoutPreparedWarnings(\n toGenerationWarnings(result.warnings),\n prepared\n );\n emitGenerationWarnings(emitted);\n options.warnings?.push(...emitted);\n return result.buffer;\n }\n\n async createGenerator(\n plugins: any[],\n options: GeneratorOptions\n ): Promise<GeneratorResult> {\n const core = await import('@json-to-office/core-pptx');\n const hasPlugins = plugins.length > 0;\n const pluginNames = plugins.map((p) => p.name);\n const services = withRequestedServices(\n buildServicesFromEnv(),\n options.services\n );\n\n // Resolve once unless the canonical model already carries the result.\n const prepared =\n !hasPlugins && options.prepared?.format === 'pptx'\n ? (options.prepared as ReturnType<\n typeof core.preparePptxQualityDocument\n >)\n : undefined;\n const resolved = prepared ? undefined : await this.resolveThemes(options);\n const requestedTheme = resolved?.requested;\n const customThemes = resolved?.customThemes;\n const themeLabel = prepared\n ? preparedThemeLabel(prepared)\n : resolved?.label;\n\n if (!hasPlugins) {\n // A prepared model renders only its own document; any other one falls\n // back to resolving themes here, memoized so a bad `--theme` still\n // warns once per generator.\n let fallbackThemes: Promise<ResolvedThemes> | undefined;\n const themesFor = (): Promise<ResolvedThemes> =>\n (fallbackThemes ??= resolved\n ? Promise.resolve(resolved)\n : this.resolveThemes(options));\n return {\n generateBuffer: async (document: any) => {\n const parsed =\n typeof document === 'string' ? JSON.parse(document) : document;\n const usable = preparedFor(prepared, parsed);\n const themes = usable ? undefined : await themesFor();\n const normalized = usable\n ? { document: usable.model.authored, customThemes: undefined }\n : withRequestedTheme(\n parsed,\n themes?.requested,\n themes?.customThemes\n );\n const result = await core.generateBufferWithWarnings(\n normalized.document,\n {\n customThemes: normalized.customThemes,\n services,\n fonts: options.fonts,\n validation: {\n allowUnknownFields: options.validation?.allowUnknownFields,\n },\n deterministic: options.deterministic,\n generatedAt: options.generatedAt,\n baseDir: options.baseDir,\n renderer: options.renderer as PptxRendererId | undefined,\n prepared: usable,\n }\n );\n const warnings = withoutPreparedWarnings(\n toGenerationWarnings(result.warnings),\n usable\n );\n emitGenerationWarnings(warnings);\n options.warnings?.push(...warnings);\n return result.buffer;\n },\n hasPlugins: false,\n pluginNames: [],\n themeLabel,\n };\n }\n\n let generator: GeneratorBuilder = core.createPresentationGenerator({\n // Undefined when nothing was requested: a constructor theme beats the\n // generator's own `props.theme` lookup, so forcing one here would render\n // every document in it.\n theme: requestedTheme,\n customThemes: requestedTheme\n ? { ...customThemes, [CLI_THEME_KEY]: requestedTheme }\n : customThemes,\n debug: process.env.DEBUG === 'true',\n services,\n fonts: options.fonts,\n validation: {\n allowUnknownFields: options.validation?.allowUnknownFields,\n },\n deterministic: options.deterministic,\n generatedAt: options.generatedAt,\n baseDir: options.baseDir,\n renderer: options.renderer as PptxRendererId | undefined,\n });\n\n for (const plugin of plugins) {\n generator = generator.addComponent(plugin);\n }\n\n return {\n generateBuffer: async (document: any) => {\n const parsed =\n typeof document === 'string' ? JSON.parse(document) : document;\n const { document: docDefinition } = withRequestedTheme(\n parsed,\n requestedTheme,\n customThemes\n );\n const result = await generator.generateBuffer(docDefinition, {\n validation: {\n allowUnknownFields: options.validation?.allowUnknownFields,\n },\n deterministic: options.deterministic,\n generatedAt: options.generatedAt,\n baseDir: options.baseDir,\n renderer: options.renderer as PptxRendererId | undefined,\n });\n const normalized = toGenerationWarnings(result.warnings);\n emitGenerationWarnings(normalized);\n options.warnings?.push(...normalized);\n return result.buffer;\n },\n hasPlugins: true,\n pluginNames,\n themeLabel,\n };\n }\n\n parseJson(input: string | object): unknown {\n return typeof input === 'string' ? JSON.parse(input) : input;\n }\n\n validateDocument(doc: unknown): { valid: boolean; errors?: any[] } {\n const result = validatePresentationDocument(doc);\n return {\n valid: result.valid,\n ...(result.errors.length > 0 && { errors: result.errors }),\n };\n }\n\n async validateDocumentWithPlugins(\n doc: unknown,\n plugins: any[]\n ): Promise<{ valid: boolean; errors?: any[] }> {\n const core = await import('@json-to-office/core-pptx');\n const parsed = typeof doc === 'string' ? JSON.parse(doc) : doc;\n const result = core.validatePresentation(parsed as any, plugins);\n return {\n valid: result.valid,\n ...(result.errors.length > 0 && { errors: result.errors }),\n };\n }\n\n async analyzeQuality(\n doc: unknown,\n options: GeneratorOptions = {}\n ): Promise<QualityAnalysis> {\n const core = await import('@json-to-office/core-pptx');\n const parsed = typeof doc === 'string' ? JSON.parse(doc) : doc;\n let prepared = preparedFor(\n options.prepared?.format === 'pptx'\n ? (options.prepared as ReturnType<\n typeof core.preparePptxQualityDocument\n >)\n : undefined,\n parsed\n );\n if (!prepared) {\n try {\n prepared = (await this.prepareModel(parsed, options, [])) as ReturnType<\n typeof core.preparePptxQualityDocument\n >;\n } catch {\n // Structural validation owns malformed trees. The core guards its own\n // preparation, so handing it the document instead of a model turns a\n // throw into an analysis that reports the failure.\n }\n }\n return core.analyzePptxQuality(prepared?.model.authored ?? parsed, {\n ...(prepared && { prepared }),\n renderer: options.renderer ?? documentRenderer(parsed),\n profile: options.quality?.profile,\n policy: options.quality?.policy,\n });\n }\n\n async prepareDocument(\n doc: unknown,\n options: GeneratorOptions = {}\n ): Promise<PreparedDocument> {\n // Preparation resolves the theme context the render then skips, so these\n // warnings reach the host from here or not at all.\n const warnings: any[] = [];\n const prepared = await this.prepareModel(doc, options, warnings);\n const normalized = toGenerationWarnings(warnings);\n emitGenerationWarnings(normalized);\n options.warnings?.push(...normalized);\n return prepared;\n }\n\n /** Prepare into a caller-owned sink; `prepareDocument` owns the reporting. */\n private async prepareModel(\n doc: unknown,\n options: GeneratorOptions,\n warnings: any[]\n ): Promise<PreparedDocument> {\n const core = await import('@json-to-office/core-pptx');\n const parsed = typeof doc === 'string' ? JSON.parse(doc) : doc;\n const resolved = await this.resolveThemes(options);\n const normalized = withRequestedTheme(\n parsed,\n resolved.requested,\n resolved.customThemes\n );\n const prepared = core.preparePptxQualityDocument(\n normalized.document as any,\n {\n customThemes: normalized.customThemes,\n fonts: options.fonts,\n services: withRequestedServices(\n buildServicesFromEnv(),\n options.services\n ),\n renderer: options.renderer ?? documentRenderer(parsed),\n warnings,\n }\n );\n return stampPrepared(\n {\n ...prepared,\n metadata: {\n ...prepared.metadata,\n ...(resolved.label && { themeLabel: resolved.label }),\n },\n },\n parsed,\n toGenerationWarnings(warnings)\n );\n }\n\n generateSchema(_options?: any): any {\n return null;\n }\n\n getBuiltinThemes(): Record<string, any> {\n try {\n const core = require('@json-to-office/core-pptx');\n return core.pptxThemes || {};\n } catch {\n return {};\n }\n }\n\n async getBuiltinThemeValues(): Promise<Record<string, any>> {\n const core = await import('@json-to-office/core-pptx');\n return core.pptxThemes || {};\n }\n\n async resolveTheme(options: GeneratorOptions): Promise<any> {\n const core = await import('@json-to-office/core-pptx');\n const themes = (core as any).pptxThemes || {};\n const { requested } = await this.resolveThemes(options);\n return requested ?? themes.minimal ?? {};\n }\n\n /**\n * Resolve `theme`/`themePath` once for a whole run: `themePath` is read a\n * single time and feeds both the requested theme and the custom-theme\n * registry, so a bad path warns once instead of once per consumer.\n */\n private async resolveThemes(\n options: GeneratorOptions\n ): Promise<ResolvedThemes> {\n const core = await import('@json-to-office/core-pptx');\n const themes = (core as any).pptxThemes || {};\n // Themes passed directly from the client (playground UI) come first.\n const registry: Record<string, any> = { ...options.customThemes };\n\n if (typeof options.theme === 'object' && options.theme !== null) {\n registry[safeThemeKey(options.theme.name)] = options.theme;\n }\n\n let fileTheme: any | undefined;\n if (options.themePath) {\n try {\n if (options.themePath.endsWith('.json')) {\n const content = fs.readFileSync(\n path.resolve(process.cwd(), options.themePath),\n 'utf-8'\n );\n // Validated, not merely parsed. The DOCX branch has always gone\n // through `loadThemeFromFile`; this one used to hand whatever JSON\n // it found straight to the compiler, which reads\n // `theme.defaults.fontSize` unguarded — so a theme with the wrong\n // shape surfaced as a TypeError in the IR rather than as a\n // diagnostic naming the bad field.\n const shared = await import('@json-to-office/shared-pptx');\n const checked = shared.validatePptxTheme(JSON.parse(content));\n if (!checked.valid) {\n const detail = checked.errors\n .slice(0, 3)\n .map((error: { path?: string; message: string }) =>\n error.path ? `${error.path}: ${error.message}` : error.message\n )\n .join('; ');\n throw new Error(\n `not a valid pptx theme — ${detail}${\n checked.errors.length > 3\n ? ` (and ${checked.errors.length - 3} more)`\n : ''\n }`\n );\n }\n fileTheme = checked.data;\n } else {\n const themePath = path.resolve(process.cwd(), options.themePath);\n const themeModule = await import(themePath);\n fileTheme = themeModule.default || themeModule.theme;\n }\n } catch (error: any) {\n emitDiagnostic(\n `Failed to load theme from ${options.themePath}: ${error.message}`,\n 'warning'\n );\n }\n if (fileTheme) {\n registry[safeThemeKey(fileTheme.name)] = fileTheme;\n }\n }\n\n const customThemes =\n Object.keys(registry).length > 0 ? registry : undefined;\n\n if (fileTheme) {\n return { requested: fileTheme, customThemes, label: options.themePath };\n }\n\n if (typeof options.theme === 'string') {\n // Deliberately not getPptxTheme(): it answers every unknown name with\n // the default theme, which would silently swap a typo'd `--theme` in\n // over the document's own.\n const named =\n options.customThemes?.[options.theme] ?? themes[options.theme];\n if (named)\n return { requested: named, customThemes, label: options.theme };\n\n if (options.theme.endsWith('.json') && fs.existsSync(options.theme)) {\n try {\n const content = fs.readFileSync(\n path.resolve(process.cwd(), options.theme),\n 'utf-8'\n );\n return {\n requested: JSON.parse(content),\n customThemes,\n label: options.theme,\n };\n } catch {}\n }\n\n emitDiagnostic(\n `Unknown theme \"${options.theme}\"; keeping the document's own theme`,\n 'warning'\n );\n }\n\n if (typeof options.theme === 'object' && options.theme !== null) {\n return {\n requested: options.theme,\n customThemes,\n label: safeThemeKey(options.theme.name),\n };\n }\n\n return { requested: undefined, customThemes, label: undefined };\n }\n\n async loadCustomThemes(\n options: GeneratorOptions\n ): Promise<Record<string, any> | undefined> {\n return (await this.resolveThemes(options)).customThemes;\n }\n}\n\nexport function createAdapter(format: FormatName): FormatAdapter {\n switch (format) {\n case 'docx':\n return new DocxFormatAdapter();\n case 'pptx':\n return new PptxFormatAdapter();\n default:\n throw new Error(`Unknown format: ${format}`);\n }\n}\n","/**\n * PPTX rasterizer — the concrete service backing docx `visual` components.\n *\n * Pipeline: presentation JSON → (core-pptx) .pptx → (LibreOffice) PDF →\n * (poppler/pdftoppm) PNG. Returns a base64 data URI plus the natural pixel\n * dimensions. Results are content-addressed and cached on disk so repeated\n * builds of an unchanged visual skip the (multi-second) LibreOffice run.\n *\n * Single and batch rasterization share one engine (#153). A batch keeps one\n * .pptx per slide and converts them all in a single `soffice` launch — the\n * launch is the dominant cost, and per-file conversion keeps slides fully\n * independent: each has its own PDF and PNG (no page↔slide index mapping),\n * its own dpi, and a cache key identical to the single-slide path, so both\n * paths share the same disk cache.\n *\n * A request may carry `fonts`: base64 font faces that are staged for the\n * soffice launches (via the shared FontStager pipeline) so the slide renders\n * with the document's real families instead of whatever the host happens to\n * have installed. Those fonts are part of the disk-cache key — the same\n * slide is genuinely different pixels with and without them, and the cache\n * is shared process-wide across callers.\n *\n * Every engine run works against a wall-clock deadline (one batch-scaled\n * soffice window plus one pdftoppm window) so a wedged conversion fails the\n * remaining slides quickly instead of holding the caller — and its\n * concurrency slot — for minutes.\n *\n * This is injected via `services.pptx.render` / `services.pptx.renderBatch`;\n * the published engine packages never depend on these binaries.\n */\n\nimport { execFile } from 'node:child_process';\nimport { promises as fs } from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport crypto from 'node:crypto';\nimport {\n DEFAULT_VISUAL_DPI,\n type PptxRasterizer,\n type PptxBatchRasterizer,\n type PptxRasterizeRequest,\n type PptxRasterizeResult,\n type PptxRasterizeBatchSlideResult,\n type PptxRasterizeFailureStage,\n type RasterizeFontFace,\n} from '@json-to-office/shared';\nimport { fromRasterizeFontFaces } from '@json-to-office/shared/fonts/node';\nimport { getFontStager } from './font-staging/index.js';\nimport type { FontStageHandle } from './font-staging/index.js';\n\nconst SOFFICE_TIMEOUT_MS = 60000;\n/** Extra soffice budget per additional slide in a batch launch. */\nconst SOFFICE_BATCH_EXTRA_PER_SLIDE_MS = 15000;\n/** Hard ceiling for one batch soffice launch. */\nconst SOFFICE_BATCH_TIMEOUT_CAP_MS = 300000;\n/**\n * Max isolated single-file launches after a batch launch left PDFs missing.\n * Covers the realistic case (one poisoned slide crashed the batch; its\n * successors are fine) without letting a broken environment turn one request\n * into dozens of sequential 60s timeouts.\n */\nconst MAX_ISOLATED_RETRIES = 3;\nconst PDFTOPPM_TIMEOUT_MS = 30000;\nconst PROBE_TIMEOUT_MS = 5000;\nconst MAX_BUFFER = 64 * 1024 * 1024;\n\nfunction exec(\n binary: string,\n args: string[],\n timeoutMs: number,\n /**\n * Extra env for the child, merged over `process.env`. Carries a font\n * stager's `envOverrides` (FONTCONFIG_FILE / JTO_FONT_PATHS /\n * SAL_DISABLE_SKIA). Only the soffice CONVERSION launches get it: the\n * memoized `--version` probe must not have its resolution polluted, and\n * pdftoppm has no use for it.\n */\n env?: Record<string, string>\n): Promise<void> {\n return new Promise((resolve, reject) => {\n execFile(\n binary,\n args,\n {\n timeout: timeoutMs,\n maxBuffer: MAX_BUFFER,\n windowsHide: true,\n env: env ? { ...process.env, ...env } : process.env,\n },\n (error) => (error ? reject(error) : resolve())\n );\n });\n}\n\nasync function binaryWorks(binary: string): Promise<boolean> {\n if (binary.includes(path.sep)) {\n try {\n await fs.access(binary);\n } catch {\n return false;\n }\n }\n try {\n await exec(binary, ['--version'], PROBE_TIMEOUT_MS);\n return true;\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code;\n // A non-ENOENT failure still means the binary exists (e.g. bad flag).\n return code !== 'ENOENT' && code !== 'EACCES';\n }\n}\n\nfunction sofficeCandidates(): string[] {\n const candidates: string[] = [];\n const configured = process.env.LIBREOFFICE_PATH?.trim();\n if (configured) candidates.push(configured);\n if (process.platform === 'darwin') {\n candidates.push('/Applications/LibreOffice.app/Contents/MacOS/soffice');\n } else if (process.platform === 'win32') {\n candidates.push('C:\\\\Program Files\\\\LibreOffice\\\\program\\\\soffice.exe');\n candidates.push(\n 'C:\\\\Program Files (x86)\\\\LibreOffice\\\\program\\\\soffice.exe'\n );\n }\n candidates.push('soffice', 'libreoffice');\n return [...new Set(candidates)];\n}\n\nfunction pdftoppmCandidates(): string[] {\n const candidates: string[] = [];\n const configured = process.env.PDFTOPPM_PATH?.trim();\n if (configured) candidates.push(configured);\n candidates.push('pdftoppm');\n return [...new Set(candidates)];\n}\n\nasync function resolveBinary(\n candidates: string[],\n label: string,\n install: string\n): Promise<string> {\n for (const candidate of candidates) {\n if (await binaryWorks(candidate)) return candidate;\n }\n throw new Error(\n `Visual rasterization needs ${label}, which was not found. ${install} ` +\n `(searched: ${candidates.join(', ')}).`\n );\n}\n\n// Memoize resolved binary paths per process — they don't change at runtime, so\n// re-probing (spawning `--version` for every cache-miss rasterize) is wasted\n// work. A failed resolution is NOT cached, so a later call retries.\nlet sofficePromise: Promise<string> | undefined;\nlet pdftoppmPromise: Promise<string> | undefined;\nfunction resolveSoffice(): Promise<string> {\n if (!sofficePromise) {\n sofficePromise = resolveBinary(\n sofficeCandidates(),\n 'LibreOffice (soffice)',\n 'Install LibreOffice or set LIBREOFFICE_PATH.'\n ).catch((error) => {\n sofficePromise = undefined;\n throw error;\n });\n }\n return sofficePromise;\n}\nfunction resolvePdftoppm(): Promise<string> {\n if (!pdftoppmPromise) {\n pdftoppmPromise = resolveBinary(\n pdftoppmCandidates(),\n 'pdftoppm (poppler)',\n 'Install poppler-utils or set PDFTOPPM_PATH.'\n ).catch((error) => {\n pdftoppmPromise = undefined;\n throw error;\n });\n }\n return pdftoppmPromise;\n}\n\n/**\n * Process-wide rasterizer disk-cache counters (#156).\n */\nexport interface RasterizerCacheStats {\n /** Slides served from the content-addressed PNG disk cache. */\n diskHits: number;\n /** Unique slides that missed the disk cache and needed the engine. */\n diskMisses: number;\n /** diskHits / (diskHits + diskMisses), 0 when no lookups. */\n hitRate: number;\n /** Requests resolved by batch-internal dedupe (duplicate slides). */\n dedupedRequests: number;\n /** Slides successfully rendered by the engine (LibreOffice + pdftoppm). */\n rendered: number;\n /** Slides that failed at any engine stage. */\n failed: number;\n /** PNG files currently in the disk cache directories. */\n entries: number;\n /** Total bytes of those PNG files. */\n bytes: number;\n}\n\nconst rasterizerCounters = {\n diskHits: 0,\n diskMisses: 0,\n dedupedRequests: 0,\n rendered: 0,\n failed: 0,\n};\n\n/** Cache directories any engine run has used (for the disk scan). */\nconst knownCacheDirs = new Set<string>();\n\n/**\n * Get rasterizer cache statistics: process-lifetime counters plus a live\n * scan of the disk cache directories (default dir included, so entries from\n * previous processes are visible too).\n */\nexport async function getRasterizerCacheStats(): Promise<RasterizerCacheStats> {\n const dirs = new Set(knownCacheDirs);\n const defaultDir = resolveCacheDir();\n if (defaultDir) dirs.add(defaultDir);\n\n let entries = 0;\n let bytes = 0;\n for (const dir of dirs) {\n try {\n const files = await fs.readdir(dir);\n for (const file of files) {\n if (!file.endsWith('.png')) continue;\n try {\n const stat = await fs.stat(path.join(dir, file));\n entries++;\n bytes += stat.size;\n } catch {}\n }\n } catch {\n // Missing dir — nothing cached there.\n }\n }\n\n const lookups = rasterizerCounters.diskHits + rasterizerCounters.diskMisses;\n return {\n ...rasterizerCounters,\n hitRate: lookups > 0 ? rasterizerCounters.diskHits / lookups : 0,\n entries,\n bytes,\n };\n}\n\n/**\n * Delete every cached PNG in the known cache directories (default dir\n * included) and reset the counters. Backs \"Clear all caches\" (#156) — the\n * disk cache used to survive it.\n */\nexport async function clearRasterizerCache(): Promise<void> {\n const dirs = new Set(knownCacheDirs);\n const defaultDir = resolveCacheDir();\n if (defaultDir) dirs.add(defaultDir);\n\n for (const dir of dirs) {\n try {\n const files = await fs.readdir(dir);\n await Promise.all(\n files\n .filter((file) => file.endsWith('.png'))\n .map((file) => fs.rm(path.join(dir, file), { force: true }))\n );\n } catch {}\n }\n\n rasterizerCounters.diskHits = 0;\n rasterizerCounters.diskMisses = 0;\n rasterizerCounters.dedupedRequests = 0;\n rasterizerCounters.rendered = 0;\n rasterizerCounters.failed = 0;\n}\n\nconst PNG_SIGNATURE = Buffer.from([\n 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,\n]);\n\n/**\n * Validate a PNG buffer and read its width/height from the IHDR chunk. Returns\n * null for anything that isn't a complete PNG (empty/truncated/corrupt) so the\n * caller can re-render or error rather than emit a 0×0 / broken image. pdftoppm\n * always outputs PNG, so a PNG-only check is sufficient here.\n */\nfunction parsePngSize(png: Buffer): { width: number; height: number } | null {\n // 8-byte signature, then IHDR: length(4) + \"IHDR\"(4) + width(4) + height(4)\n if (png.length < 24) return null;\n if (!png.subarray(0, 8).equals(PNG_SIGNATURE)) return null;\n if (png.toString('ascii', 12, 16) !== 'IHDR') return null;\n const width = png.readUInt32BE(16);\n const height = png.readUInt32BE(20);\n if (width <= 0 || height <= 0) return null;\n return { width, height };\n}\n\nlet tmpCounter = 0;\n/**\n * Write the cache file atomically (temp file + rename) so a concurrent reader\n * never observes a half-written PNG. Best-effort: failures are swallowed (the\n * cache is an optimization), but a partial temp file is cleaned up.\n */\nasync function writeCacheAtomic(\n cacheDir: string,\n cachePath: string,\n png: Buffer\n): Promise<void> {\n const tmp = `${cachePath}.tmp-${process.pid}-${tmpCounter++}`;\n try {\n await fs.mkdir(cacheDir, { recursive: true });\n await fs.writeFile(tmp, png);\n await fs.rename(tmp, cachePath);\n } catch {\n await fs.rm(tmp, { force: true }).catch(() => {});\n }\n}\n\n/**\n * Identity of a font set for cache-keying. Order-insensitive (the per-face\n * digests are sorted) and content-addressed (hashes the decoded bytes, not\n * the base64 blob) so the same faces supplied in a different order, or\n * re-fetched to identical bytes, still hit the same cache entry.\n */\nexport function fontsDigest(\n fonts?: readonly RasterizeFontFace[]\n): string | undefined {\n if (!fonts || fonts.length === 0) return undefined;\n const parts = fonts\n .map(\n (f) =>\n `${f.family}|${f.weight}|${f.italic ? 'i' : 'r'}|` +\n crypto\n .createHash('sha256')\n // DECODED bytes, not the base64 text. `fromRasterizeFontFaces`\n // decodes before staging, and Node's base64 decoder is lenient:\n // missing padding, embedded newlines, and stray characters all\n // decode to the same bytes. Hashing the text would give two\n // spellings of one font two different cache keys — a silent\n // cache-miss multiplier on a shared, restart-surviving render\n // server cache.\n .update(Buffer.from(f.data, 'base64'))\n .digest('hex')\n )\n .sort();\n return crypto.createHash('sha256').update(parts.join('\\n')).digest('hex');\n}\n\nexport function cacheKey(request: {\n presentation: unknown;\n dpi: number;\n baseDir?: string;\n fontsKey?: string;\n}): string {\n return (\n crypto\n .createHash('sha256')\n // baseDir joins the key: the same relative asset path means different\n // pixels under different base directories (#142).\n //\n // fontsKey joins the key for the same reason: the same slide renders\n // DIFFERENT pixels with and without the document's fonts staged. This\n // cache is process-wide, shared by /rasterize and /rasterize/batch,\n // shared by every caller of the hosted render server, and it survives\n // restarts — keying without fonts would serve one document's\n // Inter-rendered PNG to a document that never asked for Inter.\n .update(\n JSON.stringify({\n p: request.presentation,\n dpi: request.dpi,\n base: request.baseDir,\n f: request.fontsKey ?? null,\n })\n )\n .digest('hex')\n );\n}\n\nfunction toDataUri(png: Buffer): string {\n return `data:image/png;base64,${png.toString('base64')}`;\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nasync function fileExists(filePath: string): Promise<boolean> {\n try {\n await fs.access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\n/** Engine-internal result: the wire shape plus the original error object. */\ntype EngineSlideResult = PptxRasterizeBatchSlideResult & { cause?: unknown };\n\n/** One unique unit of work: a slide deck plus every request index it serves. */\ninterface SlideJob {\n presentation: unknown;\n dpi: number;\n /** Request indexes resolved by this job (batch-internal dedup). */\n indexes: number[];\n cachePath: string | null;\n pptxPath: string;\n pdfPath: string;\n pngPrefix: string;\n}\n\nconst sofficeArgs = (\n profileDir: string,\n outDir: string,\n files: string[]\n): string[] => [\n '--headless',\n '--norestore',\n '--nolockcheck',\n '--nodefault',\n `-env:UserInstallation=file://${profileDir.replace(/\\\\/g, '/')}`,\n '--convert-to',\n 'pdf:impress_pdf_Export',\n '--outdir',\n outDir,\n ...files,\n];\n\n/**\n * Rasterize N independent single-slide presentations, amortizing the soffice\n * launch across every cache miss. Returns per-slide results (index-aligned);\n * only environment-level failures (missing binaries) throw.\n */\nasync function rasterizeSlidesWithEngine(\n slides: Array<{ presentation: unknown; dpi: number }>,\n baseDir: string | undefined,\n cacheDir: string | null,\n fonts?: readonly RasterizeFontFace[]\n): Promise<EngineSlideResult[]> {\n const results: EngineSlideResult[] = new Array(slides.length);\n if (cacheDir) knownCacheDirs.add(cacheDir);\n // Request-level, so every slide in this run shares one font set — which is\n // exactly why it can join the per-slide cache key without being per-slide\n // data.\n const fontsKey = fontsDigest(fonts);\n\n // 1. Dedupe by content-addressed key: identical slides build, convert, and\n // hit the cache exactly once, then fan out to every requesting index.\n const jobsByKey = new Map<string, SlideJob>();\n for (let i = 0; i < slides.length; i++) {\n const slide = slides[i];\n const key = cacheKey({\n presentation: slide.presentation,\n dpi: slide.dpi,\n baseDir,\n fontsKey,\n });\n const existing = jobsByKey.get(key);\n if (existing) {\n existing.indexes.push(i);\n } else {\n jobsByKey.set(key, {\n presentation: slide.presentation,\n dpi: slide.dpi,\n indexes: [i],\n cachePath: cacheDir ? path.join(cacheDir, `${key}.png`) : null,\n pptxPath: '',\n pdfPath: '',\n pngPrefix: '',\n });\n }\n }\n\n rasterizerCounters.dedupedRequests += slides.length - jobsByKey.size;\n\n const fail = (\n job: SlideJob,\n stage: PptxRasterizeFailureStage,\n error: string,\n cause?: unknown\n ) => {\n rasterizerCounters.failed++;\n for (const i of job.indexes)\n results[i] = { ok: false, error, stage, cause };\n };\n const succeed = (job: SlideJob, result: PptxRasterizeResult) => {\n for (const i of job.indexes) results[i] = { ok: true, ...result };\n };\n\n // 2. Resolve cache hits for the unique jobs in parallel.\n const uncached: SlideJob[] = [];\n await Promise.all(\n [...jobsByKey.values()].map(async (job) => {\n if (job.cachePath) {\n const cached = await fs.readFile(job.cachePath).catch(() => null);\n if (cached) {\n const size = parsePngSize(cached);\n if (size) {\n rasterizerCounters.diskHits++;\n succeed(job, { base64DataUri: toDataUri(cached), ...size });\n return;\n }\n // Corrupt/partial cache file (e.g. a killed prior render) — discard\n // it and re-render rather than embed a broken image.\n await fs.rm(job.cachePath, { force: true }).catch(() => {});\n }\n rasterizerCounters.diskMisses++;\n }\n uncached.push(job);\n })\n );\n if (uncached.length === 0) return results;\n\n // 3. JSON → .pptx (in-process, pure JS). A build failure is a per-slide\n // content error; the remaining slides still convert.\n const corePptx = await import('@json-to-office/core-pptx');\n const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'jto-visual-'));\n // Declared out here so the `finally` can always close it, however the try\n // exits. Assigned only after we know a soffice launch is actually needed.\n let stageHandle: FontStageHandle | null = null;\n try {\n const built: SlideJob[] = [];\n for (let j = 0; j < uncached.length; j++) {\n const job = uncached[j];\n job.pptxPath = path.join(tempDir, `slide-${j}.pptx`);\n job.pdfPath = path.join(tempDir, `slide-${j}.pdf`);\n job.pngPrefix = path.join(tempDir, `slide-${j}`);\n try {\n const pptxBuffer = await corePptx.generateBufferFromJson(\n job.presentation as any,\n { baseDir }\n );\n await fs.writeFile(job.pptxPath, pptxBuffer);\n built.push(job);\n } catch (error) {\n fail(job, 'build', errorMessage(error), error);\n }\n }\n if (built.length === 0) return results;\n\n // Stage the request's fonts for the soffice launches. Deliberately AFTER\n // the cache probe and the build loop: a request whose slides all hit the\n // disk cache, or whose decks all failed to build, launches no soffice at\n // all and must not pay for writing N font files (and, on macOS, four\n // profile trees).\n //\n // profileDirs MUST enumerate the isolated-retry profiles too. They are\n // distinct UserInstallation directories, and the macOS stager's macro is\n // only reachable from a profile it was seeded into — miss them and a\n // mid-batch soffice crash yields fontless PNGs for the salvaged slides.\n const profileDirs = [\n path.join(tempDir, 'profile'),\n ...Array.from({ length: MAX_ISOLATED_RETRIES }, (_, r) =>\n path.join(tempDir, `profile-retry-${r}`)\n ),\n ];\n const stageFonts = fonts?.length ? fromRasterizeFontFaces(fonts) : [];\n if (stageFonts.length > 0) {\n stageHandle = await getFontStager().stage(stageFonts, tempDir, {\n profileDirs,\n });\n }\n const sofficeEnv = stageHandle?.envOverrides;\n\n const [soffice, pdftoppm] = await Promise.all([\n resolveSoffice(),\n resolvePdftoppm(),\n ]);\n\n // 4. .pptx → PDF: ONE soffice launch converts every deck (the launch is\n // the multi-second cost being amortized). The timeout scales with the\n // slide count so a large batch is not misread as a hang, and the whole\n // engine run gets a deadline of one batch window + one pdftoppm window\n // per slide — bounded work no matter how conversions misbehave. The\n // PNG phase is sequential over every slide, so its share must scale\n // with the slide count like the soffice window does; otherwise a slow\n // soffice launch drains the budget and slides whose PDFs converted\n // fine fail spuriously. An HTTP client that gives up sooner falls\n // back to per-visual calls on its own.\n const batchTimeoutMs = Math.min(\n SOFFICE_TIMEOUT_MS +\n SOFFICE_BATCH_EXTRA_PER_SLIDE_MS * (built.length - 1),\n SOFFICE_BATCH_TIMEOUT_CAP_MS\n );\n const deadlineAt =\n Date.now() + batchTimeoutMs + PDFTOPPM_TIMEOUT_MS * built.length;\n const remainingMs = () => deadlineAt - Date.now();\n\n let batchError: unknown;\n try {\n await exec(\n soffice,\n sofficeArgs(\n profileDirs[0],\n tempDir,\n built.map((job) => job.pptxPath)\n ),\n batchTimeoutMs,\n sofficeEnv\n );\n } catch (error) {\n batchError = error;\n }\n\n // 5. Per-slide PDF check. soffice reports batch conversion coarsely (it\n // can skip a file or die mid-run), so the PDFs on disk are the truth.\n // A missing PDF gets an isolated single-file launch — salvaging slides\n // left unprocessed by a mid-batch crash and pinning the error on the\n // slide that caused it — but only within MAX_ISOLATED_RETRIES and the\n // deadline, and not when nothing at all converted (an environmental\n // failure that a retry would only repeat).\n const converted: SlideJob[] = [];\n const missing: SlideJob[] = [];\n for (const job of built) {\n ((await fileExists(job.pdfPath)) ? converted : missing).push(job);\n }\n\n let retriesLeft =\n missing.length > 0 &&\n built.length > 1 &&\n (batchError === undefined || converted.length > 0)\n ? MAX_ISOLATED_RETRIES\n : 0;\n for (const [r, job] of missing.entries()) {\n const retryBudget = Math.min(SOFFICE_TIMEOUT_MS, remainingMs());\n if (retriesLeft > 0 && retryBudget > 1000) {\n retriesLeft--;\n let retryError: unknown;\n try {\n await exec(\n soffice,\n // Same directories seeded in `profileDirs` above (index 0 is the\n // batch profile, so retry `r` is `profileDirs[r + 1]`); the\n // literal is kept as the fallback for an out-of-range retry.\n sofficeArgs(\n profileDirs[r + 1] ?? path.join(tempDir, `profile-retry-${r}`),\n tempDir,\n [job.pptxPath]\n ),\n retryBudget,\n sofficeEnv\n );\n } catch (error) {\n retryError = error;\n }\n if (await fileExists(job.pdfPath)) {\n converted.push(job);\n continue;\n }\n batchError ??= retryError;\n }\n const cause = batchError;\n fail(\n job,\n 'convert',\n `LibreOffice failed to convert the slide to PDF${\n cause ? `: ${errorMessage(cause)}` : '.'\n }`,\n cause\n );\n }\n\n // 6. PDF → PNG at each slide's own dpi (single page, no page suffix).\n // pdftoppm is cheap per file; each conversion still respects the\n // engine deadline so a pathological PDF cannot stack 30s timeouts.\n for (const job of converted) {\n const budget = Math.min(PDFTOPPM_TIMEOUT_MS, remainingMs());\n if (budget <= 1000) {\n fail(\n job,\n 'rasterize',\n 'Rasterization deadline exceeded before this slide could be converted to PNG.'\n );\n continue;\n }\n try {\n await exec(\n pdftoppm,\n [\n '-r',\n String(job.dpi),\n '-png',\n '-singlefile',\n job.pdfPath,\n job.pngPrefix,\n ],\n budget\n );\n const png = await fs.readFile(`${job.pngPrefix}.png`);\n const size = parsePngSize(png);\n if (!size) {\n throw new Error(\n 'Rasterization produced an invalid PNG (empty or truncated output from pdftoppm).'\n );\n }\n if (job.cachePath) {\n await writeCacheAtomic(cacheDir!, job.cachePath, png);\n }\n rasterizerCounters.rendered++;\n succeed(job, { base64DataUri: toDataUri(png), ...size });\n } catch (error) {\n fail(job, 'rasterize', errorMessage(error), error);\n }\n }\n\n return results;\n } finally {\n // Order matters: FontconfigStager.cleanup() restores write permission on\n // the staged fonts dir (stage() freezes it to 0o555), and rm cannot\n // unlink inside a non-writable directory. Reversed, the whole temp tree\n // leaks — silently, because the rm error is swallowed.\n if (stageHandle) await stageHandle.cleanup().catch(() => {});\n await fs.rm(tempDir, { recursive: true, force: true }).catch(() => {});\n }\n}\n\nfunction resolveCacheDir(options?: {\n cacheDir?: string | null;\n}): string | null {\n return options?.cacheDir === undefined\n ? path.join(os.tmpdir(), 'jto-visual-cache')\n : options.cacheDir;\n}\n\n/**\n * Build a LibreOffice-backed pptx rasterizer.\n *\n * @param options.cacheDir - directory for the content-addressed PNG cache\n * (default: <tmp>/jto-visual-cache). Pass `null` to disable caching.\n */\nexport function createLibreOfficePptxRasterizer(options?: {\n cacheDir?: string | null;\n}): PptxRasterizer {\n const cacheDir = resolveCacheDir(options);\n\n return async function rasterize(\n request: PptxRasterizeRequest\n ): Promise<PptxRasterizeResult> {\n const [result] = await rasterizeSlidesWithEngine(\n [{ presentation: request.presentation, dpi: request.dpi }],\n request.baseDir,\n cacheDir,\n request.fonts\n );\n if (!result) throw new Error('Rasterization produced no result.');\n if (!result.ok) {\n const error = new Error(result.error) as Error & { cause?: unknown };\n // Preserve the original failure (exec exit code/signal, fs error) for\n // programmatic consumers — parity with the pre-batch implementation\n // that threw the underlying error directly.\n if (result.cause !== undefined) error.cause = result.cause;\n throw error;\n }\n return {\n base64DataUri: result.base64DataUri,\n width: result.width,\n height: result.height,\n };\n };\n}\n\n/**\n * Build a LibreOffice-backed BATCH pptx rasterizer (#153): many independent\n * single-slide presentations, one soffice launch, per-slide results. Shares\n * the content-addressed disk cache with the single-slide rasterizer — the\n * per-slide cache key is identical on both paths.\n */\nexport function createLibreOfficePptxBatchRasterizer(options?: {\n cacheDir?: string | null;\n}): PptxBatchRasterizer {\n const cacheDir = resolveCacheDir(options);\n\n return async function rasterizeBatch(request) {\n const engineResults = await rasterizeSlidesWithEngine(\n request.slides.map((slide) => ({\n presentation: slide.presentation,\n dpi: slide.dpi ?? DEFAULT_VISUAL_DPI,\n })),\n request.baseDir,\n cacheDir,\n request.fonts\n );\n // Strip the engine-internal `cause` (non-serializable) from the results.\n return {\n results: engineResults.map((result) =>\n result.ok\n ? result\n : { ok: false, error: result.error, stage: result.stage }\n ),\n };\n };\n}\n","import type { FontStager, FontStageHandle, FontStageOptions } from './types';\n\nexport class NoopFontStager implements FontStager {\n // Signature matches FontStager; every parameter (including `options`) is\n // deliberately ignored on platforms with no staging mechanism.\n async stage(\n _fonts?: unknown,\n _tempDir?: string,\n _options?: FontStageOptions\n ): Promise<FontStageHandle> {\n return {\n envOverrides: {},\n cleanup: async () => {},\n };\n }\n}\n","/**\n * Linux + macOS: use fontconfig to expose staged TTFs to LibreOffice.\n *\n * Writes each resolved font to `<tempDir>/fonts/` and a minimal\n * fontconfig.xml that includes that dir plus the system font config.\n * LibreOffice honors the per-invocation FONTCONFIG_FILE env var.\n *\n * The caller removes the whole tempDir in its own finally block; `cleanup()`\n * only has to undo the read-only freeze stage() puts on the fonts dir so\n * that recursive rm can actually unlink.\n */\n\nimport { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport type { ResolvedFont } from '@json-to-office/shared';\nimport {\n synthesizeFamilyName,\n rewriteFontFamilyName,\n} from '@json-to-office/shared';\nimport type { FontStager, FontStageHandle, FontStageOptions } from './types';\nimport { nextStagingId, safeFilenamePart } from './types';\n\nconst SYSTEM_FONTS_CONF_CANDIDATES = [\n '/etc/fonts/fonts.conf',\n '/opt/homebrew/etc/fonts/fonts.conf',\n '/usr/local/etc/fonts/fonts.conf',\n];\n\nexport class FontconfigStager implements FontStager {\n async stage(\n fonts: ResolvedFont[],\n tempDir: string,\n // Ignored: fontconfig discovery is driven by FONTCONFIG_FILE, not by the\n // soffice UserInstallation profile.\n _options?: FontStageOptions\n ): Promise<FontStageHandle> {\n const id = nextStagingId();\n const fontsDir = path.join(tempDir, 'fonts');\n await fs.mkdir(fontsDir, { recursive: true });\n\n let serial = 0;\n for (const r of fonts) {\n if (r.sources.length === 0) continue;\n for (const s of r.sources) {\n serial += 1;\n const suffix = s.italic ? 'i' : 'r';\n // Rewrite `name` table so fontconfig indexes the file under the\n // synthetic sub-family (\"Inter Light\"), matching the doc's\n // `rFonts`/`fontFace` references after synthesizeFamilyName.\n //\n // The unrewritten branch (RIBBI: the run rides bold/italic toggles\n // on the base family) leans on the bytes already declaring\n // `r.family`. That is FontRegistry's `stampResolvedFamily` — do not\n // read the skip as \"this file is fine by construction\".\n const synth = synthesizeFamilyName(r.family, s.weight, s.italic);\n const data =\n synth.family === r.family\n ? s.data\n : rewriteFontFamilyName(s.data, synth.family);\n const name = `${safeFilenamePart(synth.family)}-${s.weight}${suffix}-${id}-${serial}.ttf`;\n await fs.writeFile(path.join(fontsDir, name), data);\n }\n }\n\n // Freeze the fonts dir read-only after staging so a misbehaving\n // fontconfig run (or a concurrent soffice spawn) can't corrupt the\n // file contents we just wrote. fc-cache writes its own indexes into\n // `cacheDir` (next step), which stays writable. The whole tree gets\n // rm'd by the converter in finally, so mode matters only during the\n // conversion window.\n await fs.chmod(fontsDir, 0o555).catch(() => {\n // Some filesystems (e.g. certain Windows-mounted shares under WSL)\n // refuse chmod — ignore and proceed. The defensive-in-depth case\n // still wins on native Linux/macOS.\n });\n const includeLines = await this.pickSystemIncludes();\n // Redirect fontconfig's scan cache into tempDir. Without this, fontconfig\n // writes cache entries for `fontsDir` into the user's ~/.cache/fontconfig\n // and leaves them behind after tempDir is rm'd. Per-invocation isolation\n // also prevents two concurrent conversions from racing on the same\n // fontconfig cache directory.\n const cacheDir = path.join(tempDir, 'fc-cache');\n await fs.mkdir(cacheDir, { recursive: true });\n const configPath = path.join(tempDir, 'fontconfig.xml');\n const configXml = [\n '<?xml version=\"1.0\"?>',\n '<!DOCTYPE fontconfig SYSTEM \"fonts.dtd\">',\n '<fontconfig>',\n ` <dir>${escapeXml(fontsDir)}</dir>`,\n ` <cachedir>${escapeXml(cacheDir)}</cachedir>`,\n ...includeLines,\n '</fontconfig>',\n '',\n ].join('\\n');\n await fs.writeFile(configPath, configXml, 'utf8');\n\n return {\n envOverrides: {\n FONTCONFIG_FILE: configPath,\n XDG_CACHE_HOME: cacheDir,\n },\n cleanup: async () => {\n // Restore write permission before the caller's recursive rm. stage()\n // froze fontsDir to 0o555, and you cannot unlink entries inside a\n // non-writable directory: `fs.rm(tempDir, { recursive: true })` fails\n // with EACCES and leaks the whole temp tree (both callers swallow\n // that error, so it was silent). Removing the files themselves is\n // still the caller's job.\n await fs.chmod(fontsDir, 0o755).catch(() => {});\n },\n };\n }\n\n private async pickSystemIncludes(): Promise<string[]> {\n for (const candidate of SYSTEM_FONTS_CONF_CANDIDATES) {\n try {\n await fs.access(candidate);\n return [\n ` <include ignore_missing=\"yes\">${escapeXml(candidate)}</include>`,\n ];\n } catch {\n /* try next */\n }\n }\n // Fall back to the conventional path; fontconfig will fail softly.\n return [` <include ignore_missing=\"yes\">/etc/fonts/fonts.conf</include>`];\n }\n}\n\nfunction escapeXml(s: string): string {\n return s\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;');\n}\n","/**\n * Make resolved fonts visible to the LibreOffice child process for the\n * duration of one PDF conversion, then clean up.\n *\n * Linux/macOS: fontconfig + FONTCONFIG_FILE env var.\n * Windows: GDI session registration via koffi (AddFontResourceW).\n *\n * The caller calls `stage(fonts, tempDir)` before spawning soffice, merges\n * `envOverrides` into the child process env, waits for conversion, then\n * awaits `cleanup()` regardless of success or failure.\n *\n * Two consumers today: the playground's LibreOffice PDF-preview converter\n * (`@json-to-office/jto`) and the pptx rasterizer that backs docx `visual`\n * components (`../pptx-rasterizer.ts`).\n */\n\nimport type { ResolvedFont } from '@json-to-office/shared';\n\nexport interface FontStageHandle {\n /** Merged into the child process env. Empty object if nothing to stage. */\n envOverrides: Record<string, string>;\n /** Always call in a finally block. Safe to call multiple times (idempotent). */\n cleanup(): Promise<void>;\n}\n\nexport interface FontStageOptions {\n /**\n * UserInstallation profile directories the soffice launch(es) will use.\n * The macOS stager seeds its OnStartApp Python macro into EACH of them —\n * a launch with an unseeded profile registers no fonts and silently falls\n * back to system faces. Absent → `<tempDir>/user-profile`, the\n * LibreOfficeConverterService convention.\n */\n profileDirs?: string[];\n}\n\nexport interface FontStager {\n stage(\n fonts: ResolvedFont[],\n tempDir: string,\n options?: FontStageOptions\n ): Promise<FontStageHandle>;\n}\n\n/** Per-process monotonic counter to disambiguate concurrent conversions. */\nlet counter = 0;\nexport function nextStagingId(): string {\n counter += 1;\n return `${process.pid}-${counter}`;\n}\n\n/** Sanitize a font family for use in a filename. */\nexport function safeFilenamePart(s: string): string {\n return s.replace(/[^a-zA-Z0-9._-]/g, '_').slice(0, 48);\n}\n","/**\n * Windows: register staged TTFs with GDI via AddFontResourceW so the soffice\n * child process finds them at startup. Forces LibreOffice onto the GDI\n * backend via SAL_DISABLE_SKIA=1 — Skia/DirectWrite doesn't reliably pick\n * up GDI-registered fonts on recent LO builds.\n *\n * Scope, precisely: `AddFontResourceW` adds to the **session** font table, not\n * a private per-process one. That is deliberate and unavoidable here — the\n * private variant (`AddFontResourceExW` with `FR_PRIVATE`) is visible only to\n * the registering process, and the process that has to see these fonts is the\n * `soffice` CHILD. Node stays alive for the full conversion so the fonts\n * persist until cleanup, and GDI releases them on process exit if Node\n * crashes, so nothing leaks past the process.\n *\n * KNOWN LIMITATION — concurrent conversions on one Windows host share that\n * session table. Two conversions staging different bytes under the same\n * synthesized family (say two documents that each embed their own \"Inter\")\n * register two faces claiming one name, and which one GDI hands to soffice is\n * then order-dependent. Staged FILES never collide — each carries a\n * pid-plus-counter suffix — so this is a resolution ambiguity, not corruption.\n *\n * Not fixed here because the only correct fix is a host-wide lease held from\n * stage() through cleanup(), which serializes every Windows conversion; that\n * is a real throughput cost for a risk the deployed images do not carry (both\n * production containers are Linux/fontconfig, where staging is per-process via\n * FONTCONFIG_FILE and cannot collide). It bites a Windows host running\n * concurrent conversions — worth a lease if that becomes a supported topology.\n */\n\nimport { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport type { ResolvedFont } from '@json-to-office/shared';\nimport {\n synthesizeFamilyName,\n rewriteFontFamilyName,\n} from '@json-to-office/shared';\nimport type { FontStager, FontStageHandle, FontStageOptions } from './types';\nimport { nextStagingId, safeFilenamePart } from './types';\n\ntype KoffiLib = {\n func: (sig: string) => (...args: unknown[]) => number | boolean;\n};\ntype KoffiModule = {\n load: (libName: string) => KoffiLib;\n};\n\n// Lazy-load koffi so Linux/macOS don't incur the FFI init cost.\nlet cachedBindings: {\n addFont: (pathW: string) => number;\n removeFont: (pathW: string) => boolean;\n} | null = null;\n\nasync function getGdiBindings() {\n if (cachedBindings) return cachedBindings;\n const koffi = (await import('koffi')) as unknown as {\n default?: KoffiModule;\n } & KoffiModule;\n const mod = koffi.default ?? koffi;\n const gdi32 = mod.load('gdi32.dll');\n cachedBindings = {\n addFont: gdi32.func('int __stdcall AddFontResourceW(str16)') as (\n path: string\n ) => number,\n removeFont: gdi32.func('bool __stdcall RemoveFontResourceW(str16)') as (\n path: string\n ) => boolean,\n };\n return cachedBindings;\n}\n\nexport class WindowsFontStager implements FontStager {\n async stage(\n fonts: ResolvedFont[],\n tempDir: string,\n // Ignored: GDI registration is session-wide, not scoped to any one\n // LibreOffice UserInstallation profile, so the retry profiles need no\n // separate seeding the way the macOS Core Text stager's do.\n _options?: FontStageOptions\n ): Promise<FontStageHandle> {\n const id = nextStagingId();\n const fontsDir = path.join(tempDir, 'fonts');\n await fs.mkdir(fontsDir, { recursive: true });\n\n const stagedPaths: string[] = [];\n let serial = 0;\n for (const r of fonts) {\n if (r.sources.length === 0) continue;\n for (const s of r.sources) {\n serial += 1;\n const suffix = s.italic ? 'i' : 'r';\n // Rewrite the TTF's internal family name so GDI registers the\n // file under the synthetic sub-family the doc references.\n //\n // The unrewritten branch (RIBBI: the run rides bold/italic toggles\n // on the base family) leans on the bytes already declaring\n // `r.family`. That is FontRegistry's `stampResolvedFamily` — do not\n // read the skip as \"this file is fine by construction\".\n const synth = synthesizeFamilyName(r.family, s.weight, s.italic);\n const data =\n synth.family === r.family\n ? s.data\n : rewriteFontFamilyName(s.data, synth.family);\n const name = `${safeFilenamePart(synth.family)}-${s.weight}${suffix}-${id}-${serial}.ttf`;\n const fullPath = path.join(fontsDir, name);\n await fs.writeFile(fullPath, data);\n stagedPaths.push(fullPath);\n }\n }\n\n if (stagedPaths.length === 0) {\n return { envOverrides: {}, cleanup: async () => {} };\n }\n\n const { addFont, removeFont } = await getGdiBindings();\n const registered: string[] = [];\n for (const p of stagedPaths) {\n const added = addFont(p);\n if (added > 0) registered.push(p);\n }\n\n let cleaned = false;\n return {\n envOverrides: {\n // Force GDI backend so the freshly-registered fonts are visible.\n // Skia on Windows uses DirectWrite which does not reliably see\n // fonts added via AddFontResourceW.\n SAL_DISABLE_SKIA: '1',\n },\n cleanup: async () => {\n if (cleaned) return;\n cleaned = true;\n for (const p of registered) {\n try {\n removeFont(p);\n } catch {\n // Swallow — GDI will drop it on process exit anyway.\n }\n }\n },\n };\n }\n}\n","/**\n * macOS: make staged fonts visible to the soffice child process by\n * registering them *inside* soffice via a Python macro bound to the\n * `OnStartApp` event.\n *\n * Why this works on macOS 26. Apple tightened `CTFontManagerScopeSession`\n * and `kCTFontManagerScopePersistent` to require signed+notarized callers\n * (unsigned Node processes get `paramErr -50`), and `Process` scope only\n * registers fonts for the calling process — so the Node server can't\n * register fonts the soffice child sees. But Process scope DOES work from\n * inside soffice. LibreOffice for macOS bundles Python 3.12 with `ctypes`,\n * and UNO lets us bind a Python macro to application-start events. We seed\n * a per-invocation UserInstallation profile with:\n *\n * - `user/Scripts/python/JtoFontRegister.py` — a ~20-line macro that\n * reads `JTO_FONT_PATHS` from the process env and calls\n * `CTFontManagerRegisterFontsForURL(url, kScopeProcess, NULL)` via\n * ctypes for each path (Process = 1 in Core Text's scope enum).\n * - `user/registrymodifications.xcu` — binds the macro to `OnStartApp`\n * and sets `MacroSecurityLevel=0` for this ephemeral profile only.\n *\n * SECURITY INVARIANT — macro execution scope. The seeded profile disables\n * soffice's macro-security prompt (`MacroSecurityLevel=0`) so our\n * OnStartApp macro runs without a dialog. This profile MUST ONLY be used\n * to open files this server just generated (i.e., well-formed outputs\n * from `@json-to-office/core-*`). Piping user-supplied .docx/.pptx/.odt\n * through a soffice invocation that uses this stager would execute any\n * embedded VBA/Basic macros silently — an RCE primitive. If a future\n * code path ever converts user-supplied documents (e.g. \"PDF-ify my\n * upload\"), it must build a separate converter that does NOT share this\n * profile, or call soffice with `--safe-mode` / a default profile.\n *\n * The pptx rasterizer (`./pptx-rasterizer.ts`) is the second consumer and\n * upholds the same invariant: it only ever opens .pptx files this process\n * just built from `@json-to-office/core-pptx` out of the request's own\n * presentation JSON — never a user-supplied binary document.\n *\n * PROFILE DIRS. The macro is only reachable from the UserInstallation\n * profile it was seeded into, so `options.profileDirs` must list EVERY\n * profile the caller will launch soffice with. The converter uses exactly\n * one (`<tempDir>/user-profile`, the default here); the rasterizer uses a\n * batch profile plus one per isolated retry, and a launch against an\n * unseeded profile registers nothing and silently falls back.\n *\n * Flow: the converter spawns\n * `soffice --headless -env:UserInstallation=file://<tempDir>/user-profile ...`\n * with env `{ JTO_FONT_PATHS: \"<ttf1>:<ttf2>:...\" }`. LO boots, reads our\n * seeded profile, fires `OnStartApp`, runs our macro, registers each\n * staged TTF at Process scope in its own process. Font enumeration then\n * resolves the synthetic family names (`Inter Bold`, `Source Code Pro\n * Medium`, …) against those Process-scope registrations. The PDF export\n * ships the correct glyphs.\n *\n * Elegant side-effects:\n * - Nothing outside the converter's `tempDir` is touched. The user's\n * real `~/Library/Fonts` and `~/Library/Application Support/LibreOffice`\n * stay untouched.\n * - Cleanup is the converter's `fs.rm(tempDir, …)` — no orphan sweep\n * needed. If Node crashes mid-conversion, the per-invocation tempDir\n * is reaped by macOS tmpreaper (or the OS's next boot cleanup).\n * - No DYLD injection, no notarized helper, no filesystem-scan races.\n *\n * Failure mode: if LibreOffice can't run the macro for any reason\n * (macro-security policy applied at bootstrap before our XCU loads,\n * Python not present in a minimal LO build, …) the soffice process still\n * runs and produces a PDF — just with system-fallback fonts, exactly the\n * pre-fix behavior. Non-catastrophic. Diagnose via stderr: the macro\n * writes failures to stderr via `print(..., file=sys.stderr)`.\n */\n\nimport { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport type { ResolvedFont } from '@json-to-office/shared';\nimport {\n synthesizeFamilyName,\n rewriteFontFamilyName,\n} from '@json-to-office/shared';\nimport type { FontStager, FontStageHandle, FontStageOptions } from './types';\nimport { nextStagingId, safeFilenamePart } from './types';\nimport { emitDiagnostic } from '../diagnostics.js';\n\n/**\n * Python macro source. Written verbatim into\n * `<profile>/user/Scripts/python/JtoFontRegister.py`. Kept inline (rather\n * than a separate .py asset) so this TypeScript module is self-contained\n * and survives bundler reshuffles — no runtime file-system lookup for a\n * static asset.\n */\nconst PYTHON_MACRO = `# Auto-generated by @json-to-office/jto. Runs inside soffice on OnStartApp\n# to make staged fonts visible to LibreOffice's font enumeration. macOS 26\n# blocks Session/Persistent CT registration for unsigned callers, but\n# Process scope still works from inside the target process — which is\n# exactly where this macro runs.\nimport os\nimport sys\nimport ctypes\nimport ctypes.util\n\n\ndef _log(msg):\n # soffice swallows Python stdout in headless mode; stderr surfaces to\n # the parent's pipe. The stager doesn't read this today but the\n # converter logs stderr on failure, which is how we'll debug.\n sys.stderr.write(\"[jto-font-register] \" + msg + \"\\\\n\")\n\n\ndef register(*_args):\n paths_env = os.environ.get(\"JTO_FONT_PATHS\", \"\")\n if not paths_env:\n return\n try:\n cf = ctypes.CDLL(ctypes.util.find_library(\"CoreFoundation\"))\n ct = ctypes.CDLL(ctypes.util.find_library(\"CoreText\"))\n except Exception as e:\n _log(\"failed to load CoreFoundation/CoreText: \" + repr(e))\n return\n cf.CFURLCreateFromFileSystemRepresentation.argtypes = [\n ctypes.c_void_p, ctypes.c_char_p, ctypes.c_long, ctypes.c_bool,\n ]\n cf.CFURLCreateFromFileSystemRepresentation.restype = ctypes.c_void_p\n cf.CFRelease.argtypes = [ctypes.c_void_p]\n ct.CTFontManagerRegisterFontsForURL.argtypes = [\n ctypes.c_void_p, ctypes.c_uint32, ctypes.c_void_p,\n ]\n ct.CTFontManagerRegisterFontsForURL.restype = ctypes.c_bool\n\n kCTFontManagerScopeProcess = 1\n registered = 0\n for p in paths_env.split(os.pathsep):\n if not p:\n continue\n try:\n b = p.encode(\"utf-8\")\n url = cf.CFURLCreateFromFileSystemRepresentation(\n None, b, len(b), False\n )\n if not url:\n _log(\"CFURL failed for \" + p)\n continue\n ok = ct.CTFontManagerRegisterFontsForURL(\n url, kCTFontManagerScopeProcess, None\n )\n cf.CFRelease(url)\n if ok:\n registered += 1\n else:\n _log(\"CT register returned false for \" + p)\n except Exception as e:\n _log(\"exception registering \" + p + \": \" + repr(e))\n _log(\"registered \" + str(registered) + \" font(s) at Process scope\")\n\n\n# Expose under \"register\" (event-binding URL) and module-level run so\n# command-line vnd.sun.star.script invocation works either way.\ng_exportedScripts = (register,)\n`;\n\n/**\n * XCU that LO merges into its Bootstrap registry on startup. Two keys:\n *\n * 1. `/org.openoffice.Office.Events/ApplicationEvents/Bindings/OnStartApp`\n * → fires our Python macro before any document is loaded, so font\n * registration completes before LO enumerates fonts for rendering.\n *\n * 2. `/org.openoffice.Office.Common/Security/Scripting/MacroSecurityLevel`\n * → 0 (allow all). This only applies to the ephemeral UserInstallation\n * profile we create under `<tempDir>/user-profile`; the user's real\n * LibreOffice profile config is untouched. Scope of the security\n * relaxation is bounded by the per-invocation profile's lifetime.\n */\nconst REGISTRY_MOD_XCU = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<oor:items xmlns:oor=\"http://openoffice.org/2001/registry\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\">\n <item oor:path=\"/org.openoffice.Office.Events/ApplicationEvents/Bindings\">\n <node oor:name=\"OnStartApp\" oor:op=\"replace\">\n <prop oor:name=\"BindingURL\" oor:type=\"xs:string\">\n <value>vnd.sun.star.script:JtoFontRegister.py$register?language=Python&amp;location=user</value>\n </prop>\n </node>\n </item>\n <item oor:path=\"/org.openoffice.Office.Common/Security/Scripting\">\n <prop oor:name=\"MacroSecurityLevel\" oor:op=\"fuse\">\n <value>0</value>\n </prop>\n </item>\n</oor:items>\n`;\n\nexport class MacOSCoreTextStager implements FontStager {\n async stage(\n fonts: ResolvedFont[],\n tempDir: string,\n options?: FontStageOptions\n ): Promise<FontStageHandle> {\n const embeddable = fonts.filter((r) => r.sources.length > 0);\n if (embeddable.length === 0) {\n return { envOverrides: {}, cleanup: async () => {} };\n }\n\n // Fonts go to a subdir of the converter's tempDir. No ~/Library writes.\n const fontsDir = path.join(tempDir, 'fonts');\n await fs.mkdir(fontsDir, { recursive: true });\n\n const id = nextStagingId();\n const fontPaths: string[] = [];\n const staged: {\n family: string;\n weight: number;\n italic: boolean;\n path: string;\n }[] = [];\n let serial = 0;\n for (const r of embeddable) {\n for (const s of r.sources) {\n serial += 1;\n const suffix = s.italic ? 'i' : 'r';\n // Rewrite the TTF's internal family name to match the synthetic\n // sub-family the doc references (e.g. \"Inter Light\"). Without\n // this, Core Text indexes the staged file as \"Inter\" and\n // LibreOffice can't resolve `rFonts w:ascii=\"Inter Light\"`.\n //\n // The unrewritten branch (RIBBI: the run rides bold/italic toggles\n // on the base family) leans on the bytes already declaring\n // `r.family`. That is FontRegistry's `stampResolvedFamily` — do not\n // read the skip as \"this file is fine by construction\".\n const synth = synthesizeFamilyName(r.family, s.weight, s.italic);\n const data =\n synth.family === r.family\n ? s.data\n : rewriteFontFamilyName(s.data, synth.family);\n const name = `${safeFilenamePart(synth.family)}-${s.weight}${suffix}-${id}-${serial}.ttf`;\n const full = path.join(fontsDir, name);\n await fs.writeFile(full, data);\n fontPaths.push(full);\n staged.push({\n family: synth.family,\n weight: s.weight,\n italic: s.italic,\n path: full,\n });\n }\n }\n\n if (process.env.JTO_DEBUG_FONTS === '1') {\n // Through the sink, never a stream: a host may own stdout as a\n // protocol channel (the MCP server does), so this package writes\n // nowhere on its own. Install a sink to see it.\n emitDiagnostic(\n '[jto macos-stager] staged ' +\n staged.length +\n ' font(s) for CT Process-scope registration; JTO_FONT_PATHS has ' +\n fontPaths.length +\n ' entries\\n' +\n staged\n .map(\n (s) =>\n ` ${s.family} (w=${s.weight}${s.italic ? ' italic' : ''}) → ${path.basename(s.path)}`\n )\n .join('\\n')\n );\n }\n\n // Seed EVERY per-invocation UserInstallation profile the caller will\n // launch with, so LO reads the macro + event binding from the exact\n // path its `-env:UserInstallation=` points at. The converter passes\n // nothing and gets `<tempDir>/user-profile`; the rasterizer passes its\n // batch profile plus every isolated-retry profile.\n const profileDirs = options?.profileDirs?.length\n ? options.profileDirs\n : [path.join(tempDir, 'user-profile')];\n for (const profileDir of profileDirs) {\n const profileUser = path.join(profileDir, 'user');\n const scriptsDir = path.join(profileUser, 'Scripts', 'python');\n await fs.mkdir(scriptsDir, { recursive: true });\n await fs.writeFile(\n path.join(scriptsDir, 'JtoFontRegister.py'),\n PYTHON_MACRO\n );\n await fs.writeFile(\n path.join(profileUser, 'registrymodifications.xcu'),\n REGISTRY_MOD_XCU\n );\n }\n\n return {\n envOverrides: {\n // Colon-separated list of staged TTF paths (`:` matches Python's\n // `os.pathsep` on macOS). The macro reads this at OnStartApp.\n JTO_FONT_PATHS: fontPaths.join(':'),\n // Force LibreOffice's Core Graphics backend. Skia on macOS can\n // skip Core Text's freshly-registered fonts in some builds.\n SAL_DISABLE_SKIA: '1',\n },\n cleanup: async () => {\n // No-op: the converter's `fs.rm(tempDir, { recursive: true })` in\n // its own finally block sweeps everything we wrote here.\n },\n };\n }\n}\n","import { AsyncLocalStorage } from 'node:async_hooks';\n\nexport type DiagnosticTone =\n | 'default'\n | 'info'\n | 'success'\n | 'warning'\n | 'error'\n | 'muted';\n\nexport type DiagnosticSink = (text: string, tone?: DiagnosticTone) => void;\n\nconst sinks = new AsyncLocalStorage<DiagnosticSink>();\n\n/**\n * Scope a sink to one operation. The CLI installs an Ink-backed one per\n * task; the MCP server installs one that collects into its response.\n */\nexport function runWithDiagnosticSink<T>(\n sink: DiagnosticSink,\n callback: () => T\n): T {\n return sinks.run(sink, callback);\n}\n\n/**\n * This package writes to no stream of its own — a host may own stdout as a\n * protocol channel — so with no sink installed the message is dropped.\n */\nexport function emitDiagnostic(\n text: string,\n tone: DiagnosticTone = 'muted'\n): void {\n sinks.getStore()?.(text, tone);\n}\n\n/**\n * Plain-text sink for hosts with no UI of their own. stderr, not stdout,\n * so it stays safe to install alongside a stdout protocol stream.\n */\nexport const stderrDiagnosticSink: DiagnosticSink = (text) => {\n process.stderr.write(`${text}\\n`);\n};\n","/**\n * Factory entry point for the font staging pipeline shared by every\n * LibreOffice launch in the toolchain: the playground's PDF preview\n * converter (`@json-to-office/jto`) and the pptx rasterizer that backs docx\n * `visual` components (`../pptx-rasterizer.ts`).\n */\n\nimport type { FontStager } from './types';\nimport { NoopFontStager } from './noop-stager';\nimport { FontconfigStager } from './fontconfig-stager';\nimport { WindowsFontStager } from './windows-stager';\nimport { MacOSCoreTextStager } from './macos-stager';\n\nexport type { FontStager, FontStageHandle, FontStageOptions } from './types';\n\nconst cached = new Map<NodeJS.Platform, FontStager>();\n\nexport function getFontStager(\n platform: NodeJS.Platform = process.platform\n): FontStager {\n const hit = cached.get(platform);\n if (hit) return hit;\n let stager: FontStager;\n switch (platform) {\n case 'win32':\n stager = new WindowsFontStager();\n break;\n case 'darwin':\n // LibreOffice-for-macOS uses Core Text for font enumeration and does\n // not honor FONTCONFIG_FILE reliably. macOS 26 further blocks\n // Session/Persistent CT registration for unsigned callers, and\n // Process scope only works inside the target process. We register\n // fonts from *inside* soffice via a Python UNO macro bound to\n // OnStartApp, seeded into a per-invocation UserInstallation profile.\n // The user's ~/Library/Fonts is never touched. See macos-stager.ts.\n stager = new MacOSCoreTextStager();\n break;\n case 'linux':\n case 'freebsd':\n case 'openbsd':\n stager = new FontconfigStager();\n break;\n default:\n stager = new NoopFontStager();\n }\n cached.set(platform, stager);\n return stager;\n}\n\nexport {\n NoopFontStager,\n FontconfigStager,\n WindowsFontStager,\n MacOSCoreTextStager,\n};\n","/**\n * Text geometry from a rendered PDF — the ground truth the quality\n * estimators are guessing at (#216 follow-up).\n *\n * The pptx preview pipeline already produces a PDF (soffice → pdftoppm) and\n * uses it purely as a bitmap source. That PDF records the exact position of\n * every glyph as laid out by LibreOffice — the same engine the quality rules\n * try to predict. `pdftotext -bbox` (poppler, already a rasterizer\n * dependency alongside pdftoppm) dumps per-word bounding boxes; this module\n * parses them into slide-space points so callers can compare a rule's\n * estimate against what the renderer actually did.\n *\n * Coordinates: PDF points (1/72 in), origin at the page's top-left corner,\n * y increasing downward — the same frame as authored inches × 72. A PDF page\n * rendered from a slide has the slide's dimensions, so word boxes compare\n * directly against authored shape geometry with no transform.\n *\n * Consumers: the quality ground-truth harness (estimator calibration) today;\n * a `rendered`-certainty analysis pass tomorrow.\n */\n\nimport { execFile } from 'child_process';\nimport type { PdfPageInk } from './pdf-page-ink.js';\nimport * as path from 'path';\n\n/** One word as laid out on the page, in PDF points, top-left origin. */\nexport interface PdfTextWord {\n text: string;\n xMin: number;\n yMin: number;\n xMax: number;\n yMax: number;\n}\n\n/**\n * One line as poppler's layout analysis grouped it (`-bbox-layout` only):\n * its box plus the indices into `words` of the words on it.\n */\nexport interface PdfTextLine {\n xMin: number;\n yMin: number;\n xMax: number;\n yMax: number;\n words: number[];\n}\n\n/** One PDF page: its size in points plus every word poppler segmented. */\nexport interface PdfTextPage {\n widthPt: number;\n heightPt: number;\n /** Every word in stream order, whichever mode produced the page. */\n words: PdfTextWord[];\n /** Line grouping; empty for plain `-bbox` output, which groups nothing. */\n lines: PdfTextLine[];\n /**\n * Which raster rows carry ink, when the host sampled the page (see\n * `pdf-page-ink.ts`). Absent, the rules that need it stay silent.\n */\n ink?: PdfPageInk;\n}\n\nconst ENTITIES: Readonly<Record<string, string>> = {\n '&amp;': '&',\n '&lt;': '<',\n '&gt;': '>',\n '&quot;': '\"',\n '&apos;': \"'\",\n '&#34;': '\"',\n '&#39;': \"'\",\n};\n\nfunction decodeEntities(value: string): string {\n return value.replace(\n /&(?:amp|lt|gt|quot|apos|#34|#39);/g,\n (entity) => ENTITIES[entity] ?? entity\n );\n}\n\nconst PAGE_PATTERN =\n /<page\\s+width=\"([\\d.]+)\"\\s+height=\"([\\d.]+)\">([\\s\\S]*?)<\\/page>/g;\n// A line opens or closes; words between the two belong to it. Plain -bbox\n// output has no lines at all, so the same walk yields an empty grouping.\nconst LINE_OR_WORD_PATTERN =\n /<line\\s+xMin=\"(-?[\\d.]+)\"\\s+yMin=\"(-?[\\d.]+)\"\\s+xMax=\"(-?[\\d.]+)\"\\s+yMax=\"(-?[\\d.]+)\">|<\\/line>|<word\\s+xMin=\"(-?[\\d.]+)\"\\s+yMin=\"(-?[\\d.]+)\"\\s+xMax=\"(-?[\\d.]+)\"\\s+yMax=\"(-?[\\d.]+)\">([\\s\\S]*?)<\\/word>/g;\n\n/**\n * Parse `pdftotext -bbox` or `-bbox-layout` output (XHTML with `<page>`,\n * optionally `<line>`, and `<word>` elements). Pure — feed it a captured\n * document for tests, or the runner's stdout.\n */\nexport function parsePdfTextBbox(bboxXml: string): PdfTextPage[] {\n const pages: PdfTextPage[] = [];\n for (const pageMatch of bboxXml.matchAll(PAGE_PATTERN)) {\n const words: PdfTextWord[] = [];\n const lines: PdfTextLine[] = [];\n let open: PdfTextLine | undefined;\n for (const token of pageMatch[3].matchAll(LINE_OR_WORD_PATTERN)) {\n if (token[0] === '</line>') {\n if (open) lines.push(open);\n open = undefined;\n } else if (token[1] !== undefined) {\n open = {\n xMin: Number(token[1]),\n yMin: Number(token[2]),\n xMax: Number(token[3]),\n yMax: Number(token[4]),\n words: [],\n };\n } else {\n open?.words.push(words.length);\n words.push({\n xMin: Number(token[5]),\n yMin: Number(token[6]),\n xMax: Number(token[7]),\n yMax: Number(token[8]),\n text: decodeEntities(token[9]),\n });\n }\n }\n pages.push({\n widthPt: Number(pageMatch[1]),\n heightPt: Number(pageMatch[2]),\n words,\n lines,\n });\n }\n return pages;\n}\n\nfunction pdftotextCandidates(): string[] {\n const candidates: string[] = [];\n const configured = process.env.PDFTOTEXT_PATH?.trim();\n if (configured) candidates.push(configured);\n candidates.push('pdftotext');\n return [...new Set(candidates)];\n}\n\nasync function run(\n binary: string,\n args: string[],\n timeoutMs: number\n): Promise<string> {\n return new Promise((resolve, reject) => {\n execFile(\n binary,\n args,\n { timeout: timeoutMs, maxBuffer: 64 * 1024 * 1024 },\n (error, stdout) => {\n if (error) reject(error);\n else resolve(stdout);\n }\n );\n });\n}\n\n// Same memoization shape as the rasterizer's soffice/pdftoppm resolution:\n// success is cached per process, failure retries on the next call.\nlet pdftotextPromise: Promise<string> | undefined;\nasync function resolvePdftotext(): Promise<string> {\n if (!pdftotextPromise) {\n pdftotextPromise = (async () => {\n for (const candidate of pdftotextCandidates()) {\n if (candidate.includes(path.sep)) {\n try {\n await run(candidate, ['-v'], 10_000);\n return candidate;\n } catch {\n continue;\n }\n }\n try {\n await run(candidate, ['-v'], 10_000);\n return candidate;\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code;\n // pdftotext -v exits 0 on modern poppler; a non-ENOENT failure\n // still means the binary exists.\n if (code !== 'ENOENT' && code !== 'EACCES') return candidate;\n }\n }\n throw new Error(\n 'Text geometry extraction needs pdftotext (poppler), which was not ' +\n 'found. Install poppler-utils or set PDFTOTEXT_PATH ' +\n `(searched: ${pdftotextCandidates().join(', ')}).`\n );\n })().catch((error) => {\n pdftotextPromise = undefined;\n throw error;\n });\n }\n return pdftotextPromise;\n}\n\n/** True when a `pdftotext` binary is reachable — lets harnesses skip early. */\nexport async function pdftotextAvailable(): Promise<boolean> {\n try {\n await resolvePdftotext();\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Extract per-word text geometry from a PDF on disk. One pdftotext spawn,\n * output streamed through stdout — nothing else touches the filesystem.\n * `layout: true` asks poppler for its line grouping too (`-bbox-layout`).\n */\nexport async function extractPdfTextGeometry(\n pdfPath: string,\n options: { timeoutMs?: number; layout?: boolean } = {}\n): Promise<PdfTextPage[]> {\n const binary = await resolvePdftotext();\n const stdout = await run(\n binary,\n [options.layout ? '-bbox-layout' : '-bbox', pdfPath, '-'],\n options.timeoutMs ?? 60_000\n );\n return parsePdfTextBbox(stdout);\n}\n","/**\n * Where the ink is on a rendered page, row by row.\n *\n * Text geometry says where the words are; it says nothing about a chart, a\n * table rule or a full-bleed image, and a page that holds only those would\n * read as blank. A low-resolution grayscale raster of the same PDF answers\n * the other question — how far down the page anything at all was painted —\n * and that is what the page-fill rule needs. `pdftoppm -gray` writes one\n * PGM (P5) file per page: an ASCII header, then one byte per pixel, so\n * reading it needs no image library.\n */\n\nimport { execFile } from 'child_process';\nimport { promises as fs } from 'fs';\nimport os from 'os';\nimport path from 'path';\n\n/** One page's ink profile, as cached beside its text geometry. */\nexport interface PdfPageInk {\n /** Raster rows the page was sampled into; row `r` spans `heightPt * r / rows` to `heightPt * (r + 1) / rows`. */\n rows: number;\n /** Row indices, ascending, where at least one pixel is darker than the ink threshold. */\n inked: number[];\n}\n\nexport interface Pgm {\n width: number;\n height: number;\n maxValue: number;\n /** Row-major, one value per pixel, `width * height` long. */\n pixels: Uint8Array;\n}\n\n/** Pixels at or below this value count as ink: dark enough to be text, a rule or a chart bar, not anti-aliasing haze. */\nexport const INK_THRESHOLD = 200;\n\n/** Dots per inch the fill measurement rasterizes at: an A4 page is ~280 rows, enough to place content to within 3pt. */\nexport const INK_DPI = 24;\n\n/** Parse a binary PGM (`P5`) as pdftoppm writes it: header tokens, one whitespace byte, then the pixels. */\nexport function parsePgm(buffer: Buffer): Pgm {\n const header = /^P5\\s+(?:#[^\\n]*\\n\\s*)*(\\d+)\\s+(\\d+)\\s+(\\d+)\\s/.exec(\n buffer.subarray(0, 64).toString('latin1')\n );\n if (!header) throw new Error('Not a binary PGM (P5) image.');\n const width = Number(header[1]);\n const height = Number(header[2]);\n const maxValue = Number(header[3]);\n if (maxValue > 255) throw new Error('16-bit PGM is not supported.');\n const start = header[0].length;\n // Check the Buffer's own length, not the view: a small Buffer lives in\n // Node's shared pool, where a longer view would silently read a neighbour.\n if (buffer.length - start < width * height) {\n throw new Error('PGM shorter than its declared size.');\n }\n const pixels = new Uint8Array(\n buffer.buffer,\n buffer.byteOffset + start,\n width * height\n );\n return { width, height, maxValue, pixels };\n}\n\n/** Rows carrying at least one pixel at or below `threshold`, ascending. */\nexport function inkedRows(pgm: Pgm, threshold = INK_THRESHOLD): number[] {\n const rows: number[] = [];\n for (let row = 0; row < pgm.height; row += 1) {\n const start = row * pgm.width;\n const end = start + pgm.width;\n for (let i = start; i < end; i += 1) {\n if (pgm.pixels[i] <= threshold) {\n rows.push(row);\n break;\n }\n }\n }\n return rows;\n}\n\nexport function pageInkFromPgm(buffer: Buffer, threshold?: number): PdfPageInk {\n const pgm = parsePgm(buffer);\n return { rows: pgm.height, inked: inkedRows(pgm, threshold) };\n}\n\nfunction pdftoppmCandidates(): string[] {\n const configured = process.env.PDFTOPPM_PATH?.trim();\n return [...new Set([...(configured ? [configured] : []), 'pdftoppm'])];\n}\n\nasync function run(\n binary: string,\n args: string[],\n timeoutMs: number,\n signal?: AbortSignal\n) {\n return new Promise<void>((resolve, reject) => {\n execFile(\n binary,\n args,\n {\n timeout: timeoutMs,\n maxBuffer: 16 * 1024 * 1024,\n windowsHide: true,\n ...(signal && { signal }),\n },\n (error) => (error ? reject(error) : resolve())\n );\n });\n}\n\n// Same memoization shape as the pdftotext resolution beside this file:\n// success is cached per process, failure retries on the next call.\nlet pdftoppmPromise: Promise<string> | undefined;\nasync function resolvePdftoppm(): Promise<string> {\n if (!pdftoppmPromise) {\n pdftoppmPromise = (async () => {\n for (const candidate of pdftoppmCandidates()) {\n try {\n await run(candidate, ['-v'], 10_000);\n return candidate;\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code;\n // pdftoppm -v prints to stderr and exits 0 on modern poppler; a\n // non-ENOENT failure still means the binary exists.\n if (code !== 'ENOENT' && code !== 'EACCES') return candidate;\n }\n }\n throw new Error(\n 'Page ink extraction needs pdftoppm (poppler), which was not found. ' +\n `Install poppler-utils or set PDFTOPPM_PATH (searched: ${pdftoppmCandidates().join(', ')}).`\n );\n })().catch((error) => {\n pdftoppmPromise = undefined;\n throw error;\n });\n }\n return pdftoppmPromise;\n}\n\nexport interface ExtractPdfPageInkOptions {\n dpi?: number;\n threshold?: number;\n /** A resolved pdftoppm path, when the caller already found one. */\n binary?: string;\n timeoutMs?: number;\n /** Aborting kills the pdftoppm process and rejects. */\n signal?: AbortSignal;\n}\n\n/**\n * Rasterize every page of a PDF in grayscale at a low resolution and read\n * back which rows carry ink. One pdftoppm spawn into a private temp dir,\n * removed before returning; the result is small enough to cache with the\n * text geometry.\n */\nexport async function extractPdfPageInk(\n pdfPath: string,\n options: ExtractPdfPageInkOptions = {}\n): Promise<PdfPageInk[]> {\n const binary = options.binary ?? (await resolvePdftoppm());\n const dpi = options.dpi ?? INK_DPI;\n const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'jto-page-ink-'));\n try {\n const prefix = path.join(dir, 'page');\n await run(\n binary,\n ['-gray', '-r', String(dpi), pdfPath, prefix],\n options.timeoutMs ?? 60_000,\n options.signal\n );\n const files = (await fs.readdir(dir))\n .filter((name) => /^page-\\d+\\.pgm$/.test(name))\n .sort(\n (a, b) =>\n Number(/\\d+/.exec(a)?.[0] ?? 0) - Number(/\\d+/.exec(b)?.[0] ?? 0)\n );\n const pages: PdfPageInk[] = [];\n for (const file of files) {\n pages.push(\n pageInkFromPgm(\n await fs.readFile(path.join(dir, file)),\n options.threshold\n )\n );\n }\n return pages;\n } finally {\n await fs.rm(dir, { recursive: true, force: true });\n }\n}\n","/**\n * Font names from a rendered PDF — the other half of substitution detection.\n *\n * `pdftotext -bbox` says where every word landed but not which face drew it.\n * `pdffonts` (poppler, next to pdftotext) lists every font the PDF embeds or\n * references; LibreOffice embeds the face it actually used, so a requested\n * family that never appears in that list was substituted on the way.\n *\n * Names are PostScript-style: a subset tag (`BAAAAA+`), the family with\n * spaces removed, and a style suffix (`-Bold`, `Medium-Regular`). Comparison\n * therefore happens on a folded form — lowercase alphanumerics only — where\n * \"Space Grotesk\" and `CAAAAA+SpaceGrotesk-Regular` meet as a prefix match.\n */\n\nimport { execFile } from 'child_process';\nimport * as path from 'path';\n\n/** One font row as `pdffonts` prints it. */\nexport interface PdfFontInfo {\n /** The name as printed, subset tag included. */\n name: string;\n /** The name without its subset tag: `DMSans-Regular`. */\n baseName: string;\n type: string;\n embedded: boolean;\n}\n\nconst ROW_PATTERN =\n /^(\\S+)\\s+(.+?)\\s+(\\S+)\\s+(yes|no)\\s+(yes|no)\\s+(yes|no)\\s+/;\n\n/**\n * Parse `pdffonts` output. Pure; the header and rule lines are skipped and\n * a row that does not fit the column layout is ignored rather than guessed.\n */\nexport function parsePdfFonts(stdout: string): PdfFontInfo[] {\n const fonts: PdfFontInfo[] = [];\n for (const line of stdout.split(/\\r?\\n/)) {\n if (line.startsWith('name ') || line.startsWith('---') || !line.trim()) {\n continue;\n }\n const match = ROW_PATTERN.exec(line);\n if (!match) continue;\n const name = match[1];\n fonts.push({\n name,\n baseName: name.replace(/^[A-Z]{6}\\+/, ''),\n type: match[2].trim(),\n embedded: match[4] === 'yes',\n });\n }\n return fonts;\n}\n\n/** Lowercase alphanumerics only: the form family names are compared in. */\nexport function foldFontName(value: string): string {\n return value.toLowerCase().replace(/[^a-z0-9]/g, '');\n}\n\n/**\n * Whether a requested family is present among the rendered fonts. A family\n * matches a PDF font whose folded base name starts with the folded family\n * — `dmsans` against `dmsanslightregular` — so style suffixes never split\n * one family into several.\n */\nexport function familyRendered(\n family: string,\n fonts: readonly PdfFontInfo[]\n): boolean {\n const folded = foldFontName(family);\n if (folded === '') return true;\n return fonts.some((font) => foldFontName(font.baseName).startsWith(folded));\n}\n\n/** Where to look for `pdffonts`, most explicit first. */\nfunction pdffontsCandidates(): string[] {\n const candidates: string[] = [];\n const configured = process.env.PDFFONTS_PATH?.trim();\n if (configured) candidates.push(configured);\n // Poppler ships its tools side by side; a configured pdftotext locates them.\n const sibling = process.env.PDFTOTEXT_PATH?.trim();\n if (sibling) {\n candidates.push(\n path.join(\n path.dirname(sibling),\n process.platform === 'win32' ? 'pdffonts.exe' : 'pdffonts'\n )\n );\n }\n candidates.push('pdffonts');\n return [...new Set(candidates)];\n}\n\n/** One spawn, stdout captured; nothing here touches the filesystem. */\nasync function run(\n binary: string,\n args: string[],\n timeoutMs: number\n): Promise<string> {\n return new Promise((resolve, reject) => {\n execFile(\n binary,\n args,\n { timeout: timeoutMs, maxBuffer: 16 * 1024 * 1024 },\n (error, stdout) => {\n if (error) reject(error);\n else resolve(stdout);\n }\n );\n });\n}\n\nlet pdffontsPromise: Promise<string> | undefined;\n/** The binary, memoized: success is cached, failure retries next call. */\nasync function resolvePdffonts(): Promise<string> {\n if (!pdffontsPromise) {\n pdffontsPromise = (async () => {\n for (const candidate of pdffontsCandidates()) {\n try {\n await run(candidate, ['-v'], 10_000);\n return candidate;\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code;\n if (code !== 'ENOENT' && code !== 'EACCES') return candidate;\n }\n }\n throw new Error(\n 'Font inspection needs pdffonts (poppler), which was not found. ' +\n 'Install poppler-utils or set PDFFONTS_PATH ' +\n `(searched: ${pdffontsCandidates().join(', ')}).`\n );\n })().catch((error) => {\n pdffontsPromise = undefined;\n throw error;\n });\n }\n return pdffontsPromise;\n}\n\n/** True when a `pdffonts` binary is reachable. */\nexport async function pdffontsAvailable(): Promise<boolean> {\n try {\n await resolvePdffonts();\n return true;\n } catch {\n return false;\n }\n}\n\n/** The fonts a PDF on disk carries. One spawn, stdout only. */\nexport async function extractPdfFonts(\n pdfPath: string,\n options: { timeoutMs?: number } = {}\n): Promise<PdfFontInfo[]> {\n const binary = await resolvePdffonts();\n const stdout = await run(binary, [pdfPath], options.timeoutMs ?? 30_000);\n return parsePdfFonts(stdout);\n}\n","/**\n * The rendered-certainty pass (#344): findings from what LibreOffice\n * actually laid out, mapped back to the pointers that authored them.\n *\n * Static rules predict; this pass measures. It reads per-word geometry from\n * the preview PDF, the fonts the PDF embeds, and the document's authored\n * text inventory, and reports the defects only ink can show: text past the\n * page or its frame, words drawn over each other, text that never rendered\n * (fully clipped, or set in a place poppler cannot read), faces substituted\n * on the way, pages with nothing on them, headings stranded at a page foot\n * and paragraphs split into a lone line.\n *\n * Every finding carries `certainty: 'rendered'` and an explicit mapping\n * status. A defect whose words match no inventory entry is still reported,\n * at the document root, as `unmapped` — an unmapped finding is a mapping\n * gap to close, never a defect to hide.\n *\n * Pure: geometry and fonts are extracted by the caller (see\n * `extractPdfTextGeometry`, `extractPdfFonts`), so every rule here is\n * testable from captured fixtures with no converter on the host.\n *\n * The checks draft findings; the quality engine turns them into\n * diagnostics. Each check is one rule of `RENDERED_QUALITY_RULES`, so a\n * profile can switch one off or move its severity, a policy can suppress\n * one at a pointer and a gate can make one blocking — the same levers every\n * static rule answers to, applied to the pass that measures.\n */\n\nimport {\n QualityEngine,\n type QualityAnalysis,\n type QualityAnalyzeOptions,\n type QualityDiagnostic,\n type QualityFact,\n type QualityRuleFinding,\n} from '@json-to-office/quality';\nimport type { PdfFontInfo } from './pdf-fonts';\nimport { familyRendered } from './pdf-fonts';\nimport type { PdfTextPage, PdfTextWord } from './pdf-text-geometry';\nimport { RENDERED_QUALITY_RULES, type RenderedRuleId } from './rendered-rules';\nimport {\n assignInventory,\n CHROME_BAND,\n type InventoryEntry,\n type InventoryMatch,\n type MappingStatus,\n type OccurrencePart,\n} from './rendered-text-match';\n\n/** One authored string the pass can match rendered words back to. */\nexport interface RenderedTextEntry extends InventoryEntry {\n role:\n | 'heading'\n | 'body'\n | 'list-item'\n | 'table-header'\n | 'table-cell'\n | 'statistic'\n | 'caption'\n | 'toc-entry'\n | 'chrome'\n | 'slide-text';\n level?: number;\n /** A declared box the text must fit: a docx frame, a pptx text box. */\n box?: { widthPt?: number; heightPt?: number };\n}\n\nexport interface RequestedFont {\n family: string;\n /** Where the family was requested, when the document names it. */\n path?: string;\n /**\n * The document supplied a source for the family (a file, a URL), so the\n * preview should have had it and a substitution is the document's defect.\n * Without one the family is a host font — Calibri on a Mac without Office\n * — and the substitution says something about the preview, not the file.\n */\n declared?: boolean;\n}\n\nexport interface RenderedAnalysisInput {\n format: 'docx' | 'pptx';\n /** Renderer identity, for a profile that declares renderer targets. */\n renderer?: string;\n pages: readonly PdfTextPage[];\n inventory: readonly RenderedTextEntry[];\n /** Fonts the PDF carries; `undefined` when the host could not inspect them. */\n fonts?: readonly PdfFontInfo[];\n requestedFonts?: readonly RequestedFont[];\n}\n\n/**\n * Below this share of the body area painted, a page that is neither the\n * cover nor the last is under-filled. Judges called a page \"two-thirds\n * blank\" a defect and a page that ends at its midpoint acceptable, so the\n * line sits between.\n */\nexport const MINIMUM_PAGE_FILL = 0.5;\n\n/**\n * The last page may end wherever the text ends; but a last page that holds\n * only the tail — the notes, a source line, one orphaned paragraph — is a\n * stub, and the judge names it as one. Below this share it is reported.\n */\nexport const MINIMUM_LAST_PAGE_FILL = 0.25;\n\n/** How a finding reached its pointer — always on `context.mapping`. */\nexport type RenderedMapping = 'mapped' | 'ambiguous' | 'unmapped';\n\nexport interface RenderedAnalysisSummary {\n pages: number;\n words: number;\n /** Inventory entries by mapping outcome. */\n inventory: Record<MappingStatus, number>;\n /** Findings by mapping outcome, after suppressions. */\n findings: Record<RenderedMapping, number>;\n fonts?: { requested: number; substituted: number };\n /** Findings a policy suppression removed. */\n suppressed: number;\n /** Whether a policy gate made any finding blocking. */\n blocked: boolean;\n /** Whether a policy `maxDiagnostics` budget cut the findings returned. */\n truncated: boolean;\n /** The quality profile the pass ran under, when one applied. */\n profileId?: string;\n}\n\nexport interface RenderedAnalysis {\n /** The pass's diagnostics, as the quality engine finalised them. */\n findings: readonly QualityDiagnostic[];\n summary: RenderedAnalysisSummary;\n /** The engine's own account: evaluated rules, rule errors, truncation. */\n analysis: QualityAnalysis;\n}\n\n/**\n * The checks' output before the engine: a rule finding with its rule id.\n * Code, category and default severity are the rule's, declared once in\n * `RENDERED_QUALITY_RULES`; a draft states a severity only where one rule\n * varies it per finding.\n */\nexport interface RenderedFindingDraft extends QualityRuleFinding {\n ruleId: RenderedRuleId;\n path: string;\n}\n\nexport interface RenderedDraft {\n drafts: readonly RenderedFindingDraft[];\n /** The same drafts partitioned by rule, for the pack's `evaluate`. */\n byRule: ReadonlyMap<RenderedRuleId, readonly RenderedFindingDraft[]>;\n /** Inventory entries by mapping outcome. */\n inventory: Record<MappingStatus, number>;\n fonts?: { requested: number; substituted: number };\n}\n\n/** What the rules read: the whole input of the pass, as one fact. */\nexport interface RenderedGeometryFact extends QualityFact {\n kind: 'rendered/geometry';\n input: RenderedAnalysisInput;\n}\n\nexport function isRenderedGeometryFact(\n fact: QualityFact\n): fact is RenderedGeometryFact {\n return fact.kind === 'rendered/geometry';\n}\n\nconst drafts = new WeakMap<RenderedGeometryFact, RenderedDraft>();\n\n/** The pass's drafts for a fact, computed once and shared by every rule. */\nexport function renderedDraftFor(fact: RenderedGeometryFact): RenderedDraft {\n let draft = drafts.get(fact);\n if (!draft) {\n draft = draftRenderedFindings(fact.input);\n drafts.set(fact, draft);\n }\n return draft;\n}\n\n/** Spill below this is sub-visual: renderer rounding, descender fuzz. */\nexport const VISIBLE_SPILL_PT = 2;\n/** Two words overlap when their intersection covers this much of the smaller. */\nconst OVERLAP_FRACTION = 0.3;\n/**\n * The share of a word box, at its top and at its bottom, that is font metric\n * rather than ink. Poppler sizes a word by its face's ascent and descent —\n * 1.1 to 1.6 times the type size across the bundled and stock faces — while\n * capitals start well below the ascent and figures sit on the baseline. On\n * the stock deck templates, display titles set tight run their word boxes up\n * to 16% of their height past boxes they visibly fit, and 31% into the line\n * under them; a label under a large figure sits inside the figure's descent.\n * Text that truly overflows runs 70% of a line or more. Spill and overlap are\n * judged on the band that is left, the ink.\n */\nexport const WORD_BOX_SLACK = 0.2;\n\n/** A word's vertical extent with its ascent and descent slack taken off. */\nfunction inkBand(word: PdfTextWord): { yMin: number; yMax: number } {\n const slack = WORD_BOX_SLACK * Math.max(0, word.yMax - word.yMin);\n return { yMin: word.yMin + slack, yMax: word.yMax - slack };\n}\n\ninterface FindingDraft {\n ruleId: RenderedRuleId;\n /** Only for a rule whose severity varies per finding. */\n severity?: QualityRuleFinding['severity'];\n message: string;\n path: string;\n mapping: RenderedMapping;\n page?: number;\n suggestion?: string;\n relatedPaths?: string[];\n evidence?: QualityDiagnostic['evidence'];\n context?: Record<string, unknown>;\n}\n\n/**\n * A draft as the engine reads it. Every rendered finding carries its\n * mapping status and page on `context`, so one place decides that rather\n * than eight rules; severity and blocking are the engine's to settle.\n */\nfunction finding(draft: FindingDraft): RenderedFindingDraft {\n const { mapping, page, context, ...rest } = draft;\n return {\n ...rest,\n context: {\n ...context,\n mapping,\n ...(page !== undefined && { page }),\n },\n };\n}\n\nfunction round(value: number): number {\n return Math.round(value * 10) / 10;\n}\n\n/** Ink the two words share, in square points; 0 when they miss. */\nfunction intersects(a: PdfTextWord, b: PdfTextWord): number {\n const inkA = inkBand(a);\n const inkB = inkBand(b);\n const w = Math.min(a.xMax, b.xMax) - Math.max(a.xMin, b.xMin);\n const h = Math.min(inkA.yMax, inkB.yMax) - Math.max(inkA.yMin, inkB.yMin);\n return w > 0 && h > 0 ? w * h : 0;\n}\n\nfunction area(w: PdfTextWord): number {\n const ink = inkBand(w);\n return Math.max(0, w.xMax - w.xMin) * Math.max(0, ink.yMax - ink.yMin);\n}\n\n/** Text as a message can quote it: whitespace collapsed, length capped. */\nfunction excerpt(text: string, max = 60): string {\n const flat = text.replace(/\\s+/g, ' ').trim();\n return flat.length > max ? `${flat.slice(0, max - 1)}…` : flat;\n}\n\n/** Word → the inventory entry whose match covers it. */\nfunction wordOwners(\n matches: readonly InventoryMatch<RenderedTextEntry>[]\n): Map<string, InventoryMatch<RenderedTextEntry>> {\n const owners = new Map<string, InventoryMatch<RenderedTextEntry>>();\n for (const match of matches) {\n if (match.status !== 'mapped') continue;\n for (const o of match.occurrences) {\n for (const part of o.parts) {\n for (const w of part.words) owners.set(`${part.pageIndex}:${w}`, match);\n }\n }\n }\n return owners;\n}\n\n/** Line index for every word of a page, when poppler grouped lines. */\nconst lineMaps = new WeakMap<PdfTextPage, Map<number, number>>();\nfunction lineOfWord(page: PdfTextPage): Map<number, number> {\n const cached = lineMaps.get(page);\n if (cached) return cached;\n const lines = new Map<number, number>();\n page.lines.forEach((line, index) => {\n for (const w of line.words) lines.set(w, index);\n });\n lineMaps.set(page, lines);\n return lines;\n}\n\n/** The box around every part of an occurrence. */\nfunction unionBox(parts: readonly OccurrencePart[]) {\n return parts.reduce(\n (box, part) => ({\n xMin: Math.min(box.xMin, part.xMin),\n yMin: Math.min(box.yMin, part.yMin),\n xMax: Math.max(box.xMax, part.xMax),\n yMax: Math.max(box.yMax, part.yMax),\n }),\n { xMin: Infinity, yMin: Infinity, xMax: -Infinity, yMax: -Infinity }\n );\n}\n\n/**\n * Run every check and return its drafts, ungated. `analyzeRenderedDocument`\n * is the entry point; the rule pack reads this through `renderedDraftFor`,\n * once per fact, so eight rules share one matching pass.\n */\nfunction draftRenderedFindings(input: RenderedAnalysisInput): RenderedDraft {\n const { pages, format } = input;\n const { matches, chromeWords } = assignInventory(pages, input.inventory);\n const owners = wordOwners(matches);\n const findings: RenderedFindingDraft[] = [];\n\n const ownerOf = (pageIndex: number, word: number) =>\n owners.get(`${pageIndex}:${word}`);\n\n // -- Clip: a word past the page edge. Reported once per entry, with the\n // count, so a paragraph running off the page is one finding, not sixty.\n const clipped = new Map<\n string,\n {\n match?: InventoryMatch<RenderedTextEntry>;\n words: PdfTextWord[];\n page: number;\n maxPt: number;\n }\n >();\n pages.forEach((page, pageIndex) => {\n page.words.forEach((word, index) => {\n const beyond = Math.max(\n -word.xMin,\n word.xMax - page.widthPt,\n -word.yMin,\n word.yMax - page.heightPt\n );\n if (beyond <= VISIBLE_SPILL_PT) return;\n const owner = ownerOf(pageIndex, index);\n const key = owner ? `entry:${owner.entry.path}` : `page:${pageIndex}`;\n const bucket = clipped.get(key) ?? {\n match: owner,\n words: [],\n page: pageIndex + 1,\n maxPt: 0,\n };\n bucket.words.push(word);\n bucket.maxPt = Math.max(bucket.maxPt, beyond);\n clipped.set(key, bucket);\n });\n });\n for (const bucket of clipped.values()) {\n const sample = excerpt(bucket.words.map((w) => w.text).join(' '));\n findings.push(\n finding({\n ruleId: 'rendered/clip',\n mapping: bucket.match ? 'mapped' : 'unmapped',\n page: bucket.page,\n path: bucket.match?.entry.path ?? '',\n message: `${bucket.words.length} word${bucket.words.length === 1 ? '' : 's'} rendered past the page edge on page ${bucket.page} by up to ${round(bucket.maxPt)} pt: \"${sample}\".`,\n suggestion:\n 'Shorten the text, allow it to wrap, or widen the column or frame that holds it; text outside the page is cut off in print and in Word.',\n evidence: {\n summary: 'Word boxes beyond the page bounds',\n actual: round(bucket.maxPt),\n expected: 0,\n unit: 'pt',\n values: { words: bucket.words.length },\n },\n })\n );\n }\n\n // -- Truncation: only the head of an entry rendered. The tail went past\n // a frame, a box or the page, wherever poppler stopped seeing it.\n for (const match of matches) {\n if (!match.partial || match.status !== 'mapped') continue;\n const o = match.occurrences[0];\n const shown = Math.round(\n (100 * match.partial.matchedChars) / match.partial.totalChars\n );\n findings.push(\n finding({\n ruleId: 'rendered/clip',\n mapping: 'mapped',\n page: o.endPageIndex + 1,\n path: match.entry.path,\n message: `\"${excerpt(match.entry.text)}\" is cut off: about ${shown}% of it rendered on page ${o.endPageIndex + 1} and the rest is nowhere on the page.`,\n suggestion:\n 'Shorten the text or enlarge the frame, box or cell that holds it; what did not render here will not print either.',\n evidence: {\n summary: 'Share of the authored text found in the PDF',\n actual: shown,\n expected: 100,\n unit: '%',\n values: match.partial,\n },\n context: { kind: 'truncated' },\n })\n );\n }\n\n // -- Spill: a matched entry drawn larger than the box it declared. Where\n // the entry says where its box is — a slide text box — the ink is held to\n // the box's own edges, top and bottom; a box that only states its size is\n // held to that size.\n for (const match of matches) {\n const box = match.entry.box;\n const region = match.entry.region;\n if (!box || match.status !== 'mapped') continue;\n for (const o of match.occurrences) {\n const boxHeight = box.heightPt;\n const byEdges = region !== undefined && boxHeight !== undefined;\n if (region && boxHeight !== undefined) {\n const words = o.parts.flatMap((part) =>\n part.words.map((w) => pages[part.pageIndex].words[w])\n );\n const inkTop = Math.min(...words.map((w) => inkBand(w).yMin));\n const inkBottom = Math.max(...words.map((w) => inkBand(w).yMax));\n const edges = [\n { edge: 'bottom', over: inkBottom - region.yMax, at: inkBottom },\n { edge: 'top', over: region.yMin - inkTop, at: inkTop },\n ].sort((a, b) => b.over - a.over);\n const [worst] = edges;\n if (worst.over > VISIBLE_SPILL_PT) {\n findings.push(\n finding({\n ruleId: 'rendered/spill',\n mapping: 'mapped',\n page: o.pageIndex + 1,\n path: match.entry.path,\n message: `\"${excerpt(match.entry.text)}\" runs ${round(worst.over)} pt past the ${worst.edge} of its ${round(boxHeight)} pt box on page ${o.pageIndex + 1}.`,\n suggestion:\n 'Shorten the text, reduce its size, or enlarge the box; the renderer let it spill past the edge the author drew.',\n evidence: {\n summary: 'Rendered ink against the edge of the declared box',\n actual: round(worst.at),\n expected: round(\n worst.edge === 'bottom' ? region.yMax : region.yMin\n ),\n unit: 'pt',\n values: { edge: worst.edge, overPt: round(worst.over) },\n },\n })\n );\n continue;\n }\n }\n const extent = unionBox(o.parts);\n const rendered = {\n heightPt: extent.yMax - extent.yMin,\n widthPt: extent.xMax - extent.xMin,\n };\n const worst = (['heightPt', 'widthPt'] as const)\n .filter((axis) => box[axis] !== undefined)\n .filter((axis) => !(byEdges && axis === 'heightPt'))\n .map((axis) => ({\n axis,\n declared: box[axis] as number,\n rendered: rendered[axis],\n over: rendered[axis] - (box[axis] as number),\n }))\n .sort((a, b) => b.over - a.over)[0];\n if (!worst || worst.over <= VISIBLE_SPILL_PT) continue;\n const word = worst.axis === 'heightPt' ? 'taller' : 'wider';\n findings.push(\n finding({\n ruleId: 'rendered/spill',\n mapping: 'mapped',\n page: o.pageIndex + 1,\n path: match.entry.path,\n message: `\"${excerpt(match.entry.text)}\" rendered ${round(worst.over)} pt ${word} than its ${round(worst.declared)} pt box on page ${o.pageIndex + 1}.`,\n suggestion:\n 'Shorten the text, reduce its size, or enlarge the box; the renderer let it spill past the edge the author drew.',\n evidence: {\n summary: 'Rendered extent against the declared box',\n actual: round(worst.rendered),\n expected: round(worst.declared),\n unit: 'pt',\n values: { marginPt: round(-worst.over) },\n },\n })\n );\n }\n }\n\n // -- Overlap: two words from different lines drawn over each other.\n pages.forEach((page, pageIndex) => {\n const lines = lineOfWord(page);\n const seen = new Set<string>();\n const words = page.words;\n // Sorted by top edge so the inner loop stops at the first word that\n // starts below the outer one: nothing after it can intersect.\n const byTop = words\n .map((_, i) => i)\n .sort((a, b) => words[a].yMin - words[b].yMin);\n for (let p = 0; p < byTop.length; p++) {\n const i = byTop[p];\n const a = words[i];\n for (let q = p + 1; q < byTop.length; q++) {\n const j = byTop[q];\n const b = words[j];\n if (b.yMin >= a.yMax) break;\n const shared = intersects(a, b);\n if (shared <= 0) continue;\n const lineA = lines.get(i);\n if (lineA !== undefined && lineA === lines.get(j)) continue;\n const smaller = Math.min(area(a), area(b));\n if (smaller <= 0 || shared / smaller < OVERLAP_FRACTION) continue;\n const ownerA = ownerOf(pageIndex, i);\n const ownerB = ownerOf(pageIndex, j);\n const pathA = ownerA?.entry.path ?? '';\n const pathB = ownerB?.entry.path ?? '';\n // One finding per authored pair; unmapped pairs are each their own.\n const key =\n pathA && pathB\n ? [pathA, pathB].sort().join('|')\n : `${Math.min(i, j)}:${Math.max(i, j)}`;\n if (seen.has(key)) continue;\n seen.add(key);\n const mapping: RenderedMapping =\n ownerA && ownerB\n ? 'mapped'\n : ownerA || ownerB\n ? 'ambiguous'\n : 'unmapped';\n findings.push(\n finding({\n ruleId: 'rendered/overlap',\n mapping,\n page: pageIndex + 1,\n path: pathA || pathB,\n ...(pathA && pathB && pathA !== pathB && { relatedPaths: [pathB] }),\n message: `\"${a.text}\" and \"${b.text}\" are drawn over each other on page ${pageIndex + 1}.`,\n suggestion:\n 'Give the two elements separate room: move one, reduce the text, or increase the spacing between them.',\n evidence: {\n summary: 'Word boxes intersect',\n actual: round((100 * shared) / smaller),\n expected: 0,\n unit: '% of the smaller word',\n },\n })\n );\n }\n }\n });\n\n // -- Missing: authored text that never rendered anywhere.\n for (const match of matches) {\n if (match.status !== 'missing') continue;\n findings.push(\n finding({\n ruleId: 'rendered/text-missing',\n mapping: 'mapped',\n path: match.entry.path,\n message: `\"${excerpt(match.entry.text)}\" does not appear anywhere in the rendered pages.`,\n suggestion:\n 'The text was fully clipped, hidden behind another element, or set somewhere the renderer could not place it; preview the page it belongs to.',\n evidence: { summary: 'No rendered occurrence of the authored text' },\n context: { role: match.entry.role },\n })\n );\n }\n\n // -- Font substitution: a requested family absent from the PDF.\n let substituted = 0;\n if (input.fonts && input.fonts.length > 0 && input.requestedFonts) {\n const seen = new Set<string>();\n for (const requested of input.requestedFonts) {\n const family = requested.family.trim();\n if (family === '' || seen.has(family.toLowerCase())) continue;\n seen.add(family.toLowerCase());\n if (familyRendered(family, input.fonts)) continue;\n substituted += 1;\n findings.push(\n finding({\n ruleId: 'rendered/font-substituted',\n severity: requested.declared ? 'warning' : 'info',\n mapping: requested.path ? 'mapped' : 'unmapped',\n path: requested.path ?? '',\n message: requested.declared\n ? `\"${family}\" was declared with a source but the rendered PDF embeds no face of it; the renderer substituted another family.`\n : `\"${family}\" is not installed on this host, so the preview substituted another family; a recipient with the font sees the intended face.`,\n suggestion: requested.declared\n ? 'Check the font source: the file or URL must resolve to a face LibreOffice can load, or the recipient will see the same substitution.'\n : 'Install the family on the host for a faithful preview, or declare a source for it in the document fonts so every renderer has it.',\n context: { declared: requested.declared === true },\n evidence: {\n summary: 'Requested family against embedded PDF fonts',\n expected: family,\n actual: [...new Set(input.fonts.map((f) => f.baseName))].join(', '),\n },\n })\n );\n }\n }\n\n // -- Empty pages: no body text. In docx the running head and footer repeat\n // on a stray page too, so chrome does not count as content there; a deck\n // declares no chrome inventory, so a slide with any word is not empty (an\n // image slide carrying its slide number is the legitimate case). A\n // full-page figure is legitimate, so this is information, not a warning.\n pages.forEach((page, pageIndex) => {\n const hasBody =\n format === 'docx'\n ? page.words.some((_, i) => !chromeWords.has(`${pageIndex}:${i}`))\n : page.words.length > 0;\n if (hasBody) return;\n const kind = page.words.length > 0 ? 'chrome-only' : 'blank';\n findings.push(\n finding({\n ruleId: 'rendered/empty-page',\n mapping: 'unmapped',\n page: pageIndex + 1,\n path: '',\n message: `Page ${pageIndex + 1} carries ${kind === 'chrome-only' ? 'only its running head or footer' : 'no text'}. A full-page figure is fine; a blank page from a stray break or an empty section is not.`,\n suggestion:\n 'Preview the page; remove the page break or the empty section if nothing was meant to be there.',\n context: { kind },\n })\n );\n });\n\n // -- Under-filled page (docx): the ink stops well above the footer on a\n // page that is neither the cover nor the end, so a page break, not the\n // content, decided where the page ended. Ink, not words: a chart or a\n // table fills a page without a word the geometry can see.\n if (format === 'docx') {\n pages.forEach((page, pageIndex) => {\n if (!page.ink || pageIndex === 0) return;\n const last = pageIndex === pages.length - 1;\n const rowPt = page.heightPt / page.ink.rows;\n let headerBottom = 0;\n let footerTop = page.heightPt;\n page.words.forEach((w, i) => {\n if (!chromeWords.has(`${pageIndex}:${i}`)) return;\n if (w.yMax <= page.heightPt * CHROME_BAND) {\n headerBottom = Math.max(headerBottom, w.yMax);\n } else if (w.yMin >= page.heightPt * (1 - CHROME_BAND)) {\n footerTop = Math.min(footerTop, w.yMin);\n }\n });\n // Skip the row under the header text too: its hairline is chrome.\n const body = page.ink.inked.filter(\n (row) =>\n row * rowPt > headerBottom + rowPt && (row + 1) * rowPt < footerTop\n );\n if (body.length === 0) return; // the empty-page rule owns this page\n const top = body[0] * rowPt;\n const bottom = (body[body.length - 1] + 1) * rowPt;\n const fill = Math.round(((bottom - top) / (footerTop - top)) * 100) / 100;\n if (fill >= (last ? MINIMUM_LAST_PAGE_FILL : MINIMUM_PAGE_FILL)) return;\n // The section owning the page: the top-level child of the last body\n // text mapped onto it.\n let owner: { path: string; yMax: number } | undefined;\n for (const match of matches) {\n if (match.status !== 'mapped' || match.entry.repeats) continue;\n for (const o of match.occurrences) {\n // Every part on this page: a paragraph that starts here and runs\n // on still owns this page through the part it left behind.\n for (const part of o.parts) {\n if (part.pageIndex !== pageIndex) continue;\n if (!owner || part.yMax > owner.yMax) {\n owner = { path: match.entry.path, yMax: part.yMax };\n }\n }\n }\n }\n // A last page belongs to the section that closes the document, which\n // is not always the section its text was authored in: the notes are\n // painted from source slots the sections that cited them own, so the\n // text furthest down a stub last page names a section near the front.\n // The repair is a page break in the closing section, so that is the\n // pointer the finding carries — but only when the page mapped at all,\n // since `mapping` still says whether the words on it were owned.\n const owned = owner\n ? /^\\/children\\/\\d+/.exec(owner.path)?.[0]\n : undefined;\n const section =\n owned && last ? closingSection(input.inventory) ?? owned : owned;\n findings.push(\n finding({\n ruleId: 'rendered/page-underfilled',\n mapping: section ? 'mapped' : 'unmapped',\n page: pageIndex + 1,\n path: section ?? '',\n message: last\n ? `Page ${pageIndex + 1}, the last, is ${Math.round(fill * 100)}% filled: only the tail of the document reached it.`\n : `Page ${pageIndex + 1} is ${Math.round(fill * 100)}% filled: its content stops ${Math.round(footerTop - bottom)}pt above the footer and the next page begins anyway.`,\n suggestion: last\n ? 'Set `pageBreak: true` on this section — or on the one before it, when this section already starts a page — so the closing argument and its notes share one designed page instead of the last page taking only their tail, then re-preview. Keeping the notes with the paragraph above them moves the tail, not the page.'\n : 'Merge this section into its neighbour, or give the page the table or chart its argument owes. Do not pad it with prose: a short section on its own page reads as unfinished, a padded one reads as filler.',\n evidence: {\n summary: 'Share of the body area the page paints, header to footer',\n expected: `≥ ${Math.round((last ? MINIMUM_LAST_PAGE_FILL : MINIMUM_PAGE_FILL) * 100)}%`,\n actual: fill,\n },\n context: { fill, kind: last ? 'last-page' : 'middle-page' },\n })\n );\n });\n }\n\n // -- Stranded heading (docx): the last body line on its page.\n if (format === 'docx') {\n for (const match of matches) {\n if (match.entry.role !== 'heading' || match.status !== 'mapped') continue;\n const o = match.occurrences[0];\n const part = o.parts[o.parts.length - 1];\n if (part.pageIndex >= pages.length - 1) continue;\n const page = pages[part.pageIndex];\n const below = page.words.some(\n (w, i) =>\n !chromeWords.has(`${part.pageIndex}:${i}`) &&\n !part.words.includes(i) &&\n w.yMin >= part.yMax - 1\n );\n if (below) continue;\n findings.push(\n finding({\n ruleId: 'rendered/heading-stranded',\n mapping: 'mapped',\n page: part.pageIndex + 1,\n path: match.entry.path,\n message: `Heading \"${excerpt(match.entry.text)}\" is the last line on page ${part.pageIndex + 1}; its content starts on the next page.`,\n suggestion:\n 'Keep the heading with its first paragraph (keep-with-next), or move a page break so they land together.',\n })\n );\n }\n\n // -- Split paragraph: one line alone on either side of a page break.\n for (const match of matches) {\n const role = match.entry.role;\n if (\n (role !== 'body' && role !== 'list-item') ||\n match.status !== 'mapped'\n )\n continue;\n const o = match.occurrences[0];\n if (o.pageIndex === o.endPageIndex) continue;\n const first = o.parts[0];\n const last = o.parts[o.parts.length - 1];\n const linesOn = (part: OccurrencePart) => {\n const lines = lineOfWord(pages[part.pageIndex]);\n const set = new Set<number>();\n for (const w of part.words) {\n const line = lines.get(w);\n if (line !== undefined) set.add(line);\n }\n return set.size;\n };\n const before = linesOn(first);\n const after = linesOn(last);\n if (before === 0 || after === 0) continue;\n if (before > 1 && after > 1) continue;\n const which = before === 1 ? 'orphan' : 'widow';\n findings.push(\n finding({\n ruleId: 'rendered/paragraph-split',\n mapping: 'mapped',\n page: (which === 'orphan' ? first.pageIndex : last.pageIndex) + 1,\n path: match.entry.path,\n message:\n which === 'orphan'\n ? `\"${excerpt(match.entry.text)}\" leaves one line alone at the foot of page ${first.pageIndex + 1}.`\n : `\"${excerpt(match.entry.text)}\" leaves one line alone at the top of page ${last.pageIndex + 1}.`,\n suggestion:\n 'Edit the paragraph a few words shorter or longer, or set widow/orphan control on the style.',\n evidence: {\n summary: 'Lines on each side of the page break',\n values: { linesBefore: before, linesAfter: after },\n },\n context: { kind: which },\n })\n );\n }\n\n // -- Split table: the header alone at a page foot, or one row alone on\n // either side of the break. A table is grouped by its own pointer, and a\n // row by the index in the cell pointers the inventory wrote.\n // A cell pointer ends at the cell or inside it — `.../cells/2/content`\n // when the cell is an object rather than a bare string — and a block's\n // table reports at the slot that filled it, `.../props/slots/columns/…`.\n const TABLE_CELL =\n /^(.*)\\/props\\/(?:slots\\/)?columns\\/(\\d+)\\/(?:header|cells\\/(\\d+))(?:\\/.*)?$/;\n const tables = new Map<\n string,\n {\n pages: Map<number, Set<number>>;\n paths: Map<number, { column: number; path: string }>;\n }\n >();\n for (const match of matches) {\n if (\n (match.entry.role !== 'table-header' &&\n match.entry.role !== 'table-cell') ||\n match.status !== 'mapped'\n )\n continue;\n const parsed = TABLE_CELL.exec(match.entry.path);\n if (!parsed) continue;\n const [, table, column, row] = parsed;\n // A header carries no row index; -1 orders it above every row.\n const rowIndex = row === undefined ? -1 : Number(row);\n const entry = tables.get(table) ?? {\n pages: new Map<number, Set<number>>(),\n paths: new Map<number, { column: number; path: string }>(),\n };\n for (const occurrence of match.occurrences)\n for (const part of occurrence.parts) {\n const rows = entry.pages.get(part.pageIndex) ?? new Set<number>();\n rows.add(rowIndex);\n entry.pages.set(part.pageIndex, rows);\n }\n // The row's leftmost cell, so a finding always lands on the same\n // pointer however the matcher happened to order the columns.\n const held = entry.paths.get(rowIndex);\n if (!held || Number(column) < held.column)\n entry.paths.set(rowIndex, {\n column: Number(column),\n path: match.entry.path,\n });\n tables.set(table, entry);\n }\n for (const [table, { pages: onPage, paths }] of tables) {\n const ordered = [...onPage.keys()].sort((a, b) => a - b);\n if (ordered.length < 2) continue;\n const bodyRows = (page: number) =>\n [...(onPage.get(page) ?? [])].filter((row) => row >= 0);\n const report = (\n kind: 'header-alone' | 'orphan-row' | 'widow-row',\n page: number,\n path: string,\n message: string,\n suggestion: string\n ) =>\n findings.push(\n finding({\n ruleId: 'rendered/table-split',\n mapping: 'mapped',\n page: page + 1,\n path,\n message,\n suggestion,\n context: { kind, table },\n })\n );\n const firstPage = ordered[0];\n const lastPage = ordered[ordered.length - 1];\n if (onPage.get(firstPage)!.has(-1) && bodyRows(firstPage).length === 0) {\n report(\n 'header-alone',\n firstPage,\n paths.get(-1)!.path,\n `The table's header is the only part of it on page ${firstPage + 1}; its rows start on the next page.`,\n 'Keep the header with its first rows, or move the table so it starts on the next page.'\n );\n continue;\n }\n const opening = bodyRows(firstPage);\n const closing = bodyRows(lastPage);\n if (opening.length === 1)\n report(\n 'orphan-row',\n firstPage,\n paths.get(opening[0])!.path,\n `The table leaves one row alone at the foot of page ${firstPage + 1}.`,\n 'Keep the table together on one page, or edit the rows above it so the break falls elsewhere.'\n );\n else if (closing.length === 1)\n report(\n 'widow-row',\n lastPage,\n paths.get(closing[0])!.path,\n `The table leaves one row alone at the top of page ${lastPage + 1}.`,\n 'Keep the table together on one page, or edit the rows above it so the break falls elsewhere.'\n );\n }\n }\n\n const inventory: Record<MappingStatus, number> = {\n mapped: 0,\n ambiguous: 0,\n missing: 0,\n skipped: 0,\n };\n for (const match of matches) inventory[match.status] += 1;\n\n const byRule = new Map<RenderedRuleId, RenderedFindingDraft[]>();\n for (const draft of findings) {\n const bucket = byRule.get(draft.ruleId);\n if (bucket) bucket.push(draft);\n else byRule.set(draft.ruleId, [draft]);\n }\n\n return {\n drafts: findings,\n byRule,\n inventory,\n // The same gate the check itself runs under: an empty font list\n // verified nothing, and must not read as \"nothing was substituted\".\n ...(input.fonts &&\n input.fonts.length > 0 &&\n input.requestedFonts && {\n fonts: { requested: input.requestedFonts.length, substituted },\n }),\n };\n}\n\nlet engine: QualityEngine | undefined;\n\n/** The pass's engine: the rendered rule pack, built once. */\nfunction renderedEngine(): QualityEngine {\n engine ??= new QualityEngine(RENDERED_QUALITY_RULES.rules);\n return engine;\n}\n\n/**\n * The top-level section that closes the document: the highest `/children/n`\n * any authored string sits under. Read off the inventory because the\n * analysis never sees the document — the inventory is in document order and\n * every entry carries the pointer it was written at.\n */\nfunction closingSection(\n inventory: readonly RenderedTextEntry[]\n): string | undefined {\n let highest: number | undefined;\n for (const entry of inventory) {\n if (entry.repeats) continue;\n const index = /^\\/children\\/(\\d+)/.exec(entry.path)?.[1];\n if (index === undefined) continue;\n const value = Number(index);\n if (highest === undefined || value > highest) highest = value;\n }\n return highest === undefined ? undefined : `/children/${highest}`;\n}\n\n/**\n * Run the pass. Geometry, fonts and inventory become one `rendered/geometry`\n * fact; the rendered rule pack reads it under the caller's profile and\n * policy, so the findings come back suppressed, re-severed and gated the\n * way `jto_validate`'s would.\n */\nexport function analyzeRenderedDocument(\n input: RenderedAnalysisInput,\n options: QualityAnalyzeOptions = {}\n): RenderedAnalysis {\n const fact: RenderedGeometryFact = {\n id: 'rendered:geometry',\n kind: 'rendered/geometry',\n path: '',\n input,\n };\n const analysis = renderedEngine().analyzeSync(\n {\n format: input.format,\n model: undefined,\n facts: [fact],\n provenance: {},\n ...(input.renderer !== undefined && { renderer: input.renderer }),\n },\n options\n );\n // The rules met any fault in the measuring pass as a rule error; the\n // summary must not turn that into a crash of an advisory pass.\n let draft: RenderedDraft | undefined;\n try {\n draft = renderedDraftFor(fact);\n } catch {\n draft = undefined;\n }\n const byMapping: Record<RenderedMapping, number> = {\n mapped: 0,\n ambiguous: 0,\n unmapped: 0,\n };\n for (const f of analysis.diagnostics) {\n byMapping[(f.context?.mapping as RenderedMapping) ?? 'unmapped'] += 1;\n }\n return {\n findings: analysis.diagnostics,\n summary: {\n pages: input.pages.length,\n words: input.pages.reduce((n, p) => n + p.words.length, 0),\n inventory: draft?.inventory ?? {\n mapped: 0,\n ambiguous: 0,\n missing: 0,\n skipped: 0,\n },\n findings: byMapping,\n ...(draft?.fonts && { fonts: draft.fonts }),\n suppressed: analysis.suppressedCount,\n blocked: analysis.blocked,\n truncated: analysis.truncated,\n ...(analysis.profileId !== undefined && {\n profileId: analysis.profileId,\n }),\n },\n analysis,\n };\n}\n","/**\n * The rendered pass as a quality rule pack (#344).\n *\n * `draftRenderedFindings` measures; these rules are how the measurement\n * enters the quality contract. Each rule owns one code, one description the\n * design guide prints, and the defaults a profile or policy can move. All of\n * them read a single `rendered/geometry` fact — the PDF's word boxes, its\n * embedded fonts and the document's text inventory — and share one matching\n * pass over it, memoised per fact, so eight rules cost one search.\n */\n\nimport {\n QUALITY_CODES,\n type QualityRule,\n type QualityRuleFinding,\n type QualityRulePack,\n} from '@json-to-office/quality';\nimport { isRenderedGeometryFact, renderedDraftFor } from './rendered-analysis';\n\nexport type RenderedRuleId =\n | 'rendered/clip'\n | 'rendered/spill'\n | 'rendered/overlap'\n | 'rendered/text-missing'\n | 'rendered/font-substituted'\n | 'rendered/empty-page'\n | 'rendered/heading-stranded'\n | 'rendered/paragraph-split'\n | 'rendered/table-split'\n | 'rendered/page-underfilled';\n\ninterface RenderedRuleSpec {\n id: RenderedRuleId;\n code: string;\n description: string;\n category: QualityRule['category'];\n defaultSeverity: QualityRule['defaultSeverity'];\n formats?: readonly ('docx' | 'pptx')[];\n}\n\nfunction renderedRule(spec: RenderedRuleSpec): QualityRule {\n return {\n id: spec.id,\n code: spec.code,\n description: spec.description,\n category: spec.category,\n defaultSeverity: spec.defaultSeverity,\n defaultCertainty: 'rendered',\n ...(spec.formats && { formats: spec.formats }),\n evaluate({ facts }): readonly QualityRuleFinding[] {\n const fact = facts.find(isRenderedGeometryFact);\n if (!fact) return [];\n // A draft is a rule finding with one extra key the engine never reads.\n return renderedDraftFor(fact).byRule.get(spec.id) ?? [];\n },\n };\n}\n\nexport const RENDERED_QUALITY_RULES: QualityRulePack = {\n id: 'rendered',\n rules: [\n renderedRule({\n id: 'rendered/clip',\n code: QUALITY_CODES.RENDERED_CLIP,\n description:\n 'Words rendered past the page edge by more than 2 pt, or an authored string of which only a leading part rendered (`kind: truncated`, with the share found).',\n category: 'integrity',\n defaultSeverity: 'warning',\n }),\n renderedRule({\n id: 'rendered/spill',\n code: QUALITY_CODES.RENDERED_SPILL,\n description:\n 'A framed paragraph or slide text box drawn wider (DOCX) or larger (PPTX) than the box it declared.',\n category: 'integrity',\n defaultSeverity: 'warning',\n }),\n renderedRule({\n id: 'rendered/overlap',\n code: QUALITY_CODES.RENDERED_OVERLAP,\n description:\n 'Two words from different lines whose boxes intersect by more than 30% of the smaller.',\n category: 'integrity',\n defaultSeverity: 'warning',\n }),\n renderedRule({\n id: 'rendered/text-missing',\n code: QUALITY_CODES.RENDERED_TEXT_MISSING,\n description:\n 'An authored string that appears nowhere in the PDF: fully clipped, hidden, or dropped by the renderer.',\n category: 'integrity',\n defaultSeverity: 'warning',\n }),\n renderedRule({\n id: 'rendered/font-substituted',\n code: QUALITY_CODES.RENDERED_FONT_SUBSTITUTED,\n description:\n 'A requested family the PDF embeds no face of; a warning when the document declared a source for it, information when the host simply lacks it.',\n category: 'brand',\n defaultSeverity: 'info',\n }),\n renderedRule({\n id: 'rendered/empty-page',\n code: QUALITY_CODES.RENDERED_EMPTY_PAGE,\n description:\n 'A wordless page (`kind: blank`), or in docx one whose only words are its running head or footer (`kind: chrome-only`): a full-page figure is fine, a blank page from a stray break or an empty section is not.',\n category: 'integrity',\n defaultSeverity: 'info',\n }),\n renderedRule({\n id: 'rendered/page-underfilled',\n code: QUALITY_CODES.RENDERED_PAGE_UNDERFILLED,\n description:\n 'A docx page whose ink stops well above the footer: a middle page less than half filled before the next page begins (`kind: middle-page`), or a last page less than a quarter filled that holds only the tail of the document (`kind: last-page`); `context.fill` is the share used. The first page is never judged.',\n category: 'composition',\n defaultSeverity: 'info',\n formats: ['docx'],\n }),\n renderedRule({\n id: 'rendered/heading-stranded',\n code: QUALITY_CODES.RENDERED_HEADING_STRANDED,\n description: 'A heading that is the last body line on its page.',\n category: 'composition',\n defaultSeverity: 'warning',\n formats: ['docx'],\n }),\n renderedRule({\n id: 'rendered/table-split',\n code: QUALITY_CODES.RENDERED_TABLE_SPLIT,\n description:\n 'A table broken across a page badly: its header left alone at the foot of a page (`kind: header-alone`), or one row alone on either side of the break (`kind: orphan-row` or `widow-row`).',\n category: 'composition',\n defaultSeverity: 'info',\n formats: ['docx'],\n }),\n renderedRule({\n id: 'rendered/paragraph-split',\n code: QUALITY_CODES.RENDERED_PARAGRAPH_SPLIT,\n description:\n 'A paragraph leaving one line alone on either side of a page break (`kind: orphan` or `widow`).',\n category: 'composition',\n defaultSeverity: 'info',\n formats: ['docx'],\n }),\n ],\n};\n","/**\n * Locate authored text in rendered PDF geometry.\n *\n * Promoted from the ground-truth harness's sentinel search: fold both sides\n * into lowercase alphanumerics (NFKC splits ligatures, punctuation and bullet\n * glyphs drop out), concatenate every word fragment into one stream, and\n * search that. Narrow boxes hard-wrap a word mid-word and letter-spaced text\n * makes poppler emit per-cluster fragments; a per-word comparison loses both,\n * a stream keeps them. A match must start where a fragment starts and end\n * where one ends, so \"page\" never matches inside \"homepage\".\n *\n * The stream spans the whole document, not one page, so a paragraph that\n * breaks across a page is still one match — which is exactly the case the\n * widow and orphan checks need. Running heads and footers would interleave\n * the two halves, so text that repeats by design is matched first, inside\n * the top and bottom bands of each page, and its rows are removed from the\n * stream before anything else is; a row of nothing but digits in those\n * bands is a page number and goes with them.\n *\n * Fields, footnote marks and cross-references render a value the author\n * never typed. The needle is cut at each of them into segments that must\n * follow one another in the stream with at most a short gap between.\n *\n * Duplicate strings — \"Total\" in three tables — are why a search returns\n * every occurrence. `assignInventory` resolves them by reading order: the\n * inventory is walked in document order and the stream is in page order, so\n * the next unclaimed occurrence at or after the previous claim is the one.\n * Where the format knows more before rendering — a slide is its own page,\n * and its text boxes sit where the deck put them — the entry says so, and\n * only an occurrence on that page, preferably inside that box, is its own.\n */\n\nimport type {\n PdfTextLine,\n PdfTextWord,\n PdfTextPage,\n} from './pdf-text-geometry';\n\n/** Fold rendered and authored text into one comparable form. */\nexport function normalizeForMatch(value: string): string {\n return value\n .normalize('NFKC')\n .toLowerCase()\n .replace(/[^a-z0-9]/g, '');\n}\n\n/** Marks a place where the renderer paints something the author did not type. */\nconst FIELD = '\u001f';\n\n/**\n * Authored strings carry markup the page never shows: emphasis markers,\n * link targets, and fields, footnote references and cross-references whose\n * rendered value is unknowable here. The first two are dropped; the others\n * become a `FIELD` mark that `needleSegments` splits on.\n */\nexport function authoredTextForMatch(text: string): string {\n return text\n .replace(/\\{[^}\\s]+\\}/g, FIELD)\n .replace(/\\[\\^[^\\]\\s]+\\]/g, FIELD)\n .replace(/\\[@[^\\]\\s]+\\]/g, FIELD)\n .replace(/\\[([^\\]]*)\\]\\([^)]*\\)/g, '$1')\n .replace(/[*_`]+/g, '');\n}\n\n/** Folded segments of an authored string, split at every field. */\nexport function needleSegments(text: string): string[] {\n return authoredTextForMatch(text)\n .split(FIELD)\n .map(normalizeForMatch)\n .filter((segment) => segment !== '');\n}\n\n/** A word of the document: which page, which index on it. */\nexport interface WordRef {\n pageIndex: number;\n word: number;\n}\n\n/** The part of a match that lies on one page, with its union box. */\nexport interface OccurrencePart {\n pageIndex: number;\n words: number[];\n xMin: number;\n yMin: number;\n xMax: number;\n yMax: number;\n}\n\nexport interface TextOccurrence {\n /** Offset into the document stream, for ordering. */\n at: number;\n /** Offset just past the match: `[at, end)` is the range a claim holds. */\n end: number;\n /** First and last page the match touches; equal unless it breaks across. */\n pageIndex: number;\n endPageIndex: number;\n parts: OccurrencePart[];\n}\n\nexport interface StreamIndex {\n pages: readonly PdfTextPage[];\n /** One entry per word of the document, in stream order. */\n refs: WordRef[];\n fragments: string[];\n /** Offset of each fragment in `stream`; excluded fragments are empty. */\n positions: number[];\n stream: string;\n /** Where the rotated text of every page starts: all upright text is before. */\n rotatedFrom: number;\n}\n\n/**\n * Index every word of every page into one stream. Words in `exclude` keep\n * their slot with an empty fragment, so positions stay comparable while a\n * match can never touch them.\n *\n * The upright text of every page comes first, page after page, so a\n * paragraph that turns the page is one run of the stream; the rotated text\n * of every page follows it all. Anywhere inside the upright text a rotated\n * run cuts whatever string it lands in: where poppler emitted it, a deck's\n * takeaway beside a chart; at the top of a page, a report paragraph that\n * turns onto the page above a chart.\n */\nexport function indexDocument(\n pages: readonly PdfTextPage[],\n exclude: ReadonlySet<string> = new Set()\n): StreamIndex {\n const refs: WordRef[] = [];\n const fragments: string[] = [];\n const positions: number[] = [];\n let cursor = 0;\n const push = (pageIndex: number, index: number): void => {\n const word = pages[pageIndex].words[index];\n const key = `${pageIndex}:${index}`;\n const fragment = exclude.has(key) ? '' : normalizeForMatch(word.text);\n refs.push({ pageIndex, word: index });\n fragments.push(fragment);\n positions.push(cursor);\n cursor += fragment.length;\n };\n const orders = pages.map((page) => pageOrder(page.words, page.lines));\n orders.forEach((order, pageIndex) => {\n for (const index of order.upright) push(pageIndex, index);\n });\n const rotatedFrom = cursor;\n orders.forEach((order, pageIndex) => {\n for (const index of order.rotated) push(pageIndex, index);\n });\n return {\n pages,\n refs,\n fragments,\n positions,\n stream: fragments.join(''),\n rotatedFrom,\n };\n}\n\n/** A run of words on one row with no wide gap between them: one cell's line. */\ninterface RowFragment {\n xMin: number;\n xMax: number;\n words: number[];\n}\n\n/**\n * Word indices in the order a reader takes them, decided from geometry\n * rather than from the order poppler emitted them: poppler 24 lists a table\n * row line by line across every cell, poppler 26 lists it cell by cell, and\n * an authored string in a wrapped cell only survives the second.\n *\n * Rows are grouped by vertical overlap and split into fragments at gaps\n * wider than half the line is tall. A row of two or more fragments opens a\n * column block; the rows under it belong to the block while each of their\n * fragments sits inside one column (a right-aligned cell's second line\n * starts further right; a wrapped label's later lines are the only\n * fragment of their row) and the rows stay a line apart. A block reads\n * column by column, so a cell's lines come out together; a row of one\n * fragment outside any block — prose — reads as it lies.\n *\n * Rotated text — a chart's value-axis title — has no rows or columns to\n * take part in. It reads as one run in the order poppler gave it, after the\n * page's upright text: inside that text it could fall between two lines of\n * a paragraph and cut it in two, and a slide's takeaway set beside a chart\n * then reads as clipped, or as never rendered at all. Across a document the\n * stream sets it after every page (`indexDocument`).\n */\nexport function readingOrder(\n words: readonly PdfTextWord[],\n lines: readonly PdfTextLine[] = []\n): number[] {\n const { upright, rotated } = pageOrder(words, lines);\n return [...upright, ...rotated];\n}\n\n/** A page's upright words in reading order, and its rotated ones apart. */\nfunction pageOrder(\n words: readonly PdfTextWord[],\n lines: readonly PdfTextLine[] = []\n): { upright: number[]; rotated: number[] } {\n const rotated = rotatedWords(words, lines);\n return {\n upright: uprightOrder(\n words,\n words.map((_, i) => i).filter((i) => !rotated.has(i))\n ),\n rotated: [...rotated].sort((a, b) => a - b),\n };\n}\n\n/**\n * Words set on their side. A long enough word says so itself, its box\n * taller than it is wide; a short one does not — \"of\" in a rotated title is\n * still wider than tall — but poppler groups a rotated title into one line\n * whose words stack along the page instead of across it, and every word of\n * such a line is rotated.\n */\nfunction rotatedWords(\n words: readonly PdfTextWord[],\n lines: readonly PdfTextLine[]\n): Set<number> {\n const rotated = new Set(\n words\n .map((_, i) => i)\n .filter((i) => {\n const w = words[i];\n // Three glyphs or more: a \"1:\" or an \"I\" is narrow, not rotated.\n return w.text.length > 2 && w.yMax - w.yMin > 1.5 * (w.xMax - w.xMin);\n })\n );\n for (const line of lines) {\n if (line.words.length < 2) continue;\n const stacked = line.words.slice(1).every((index, n) => {\n const a = words[line.words[n]];\n const b = words[index];\n const across = Math.min(a.xMax, b.xMax) - Math.max(a.xMin, b.xMin);\n const along = Math.min(a.yMax, b.yMax) - Math.max(a.yMin, b.yMin);\n return across > 0 && along <= 0;\n });\n if (stacked) for (const index of line.words) rotated.add(index);\n }\n return rotated;\n}\n\n/** Middle value of a non-empty list, for a row's typical word gap. */\nfunction median(values: readonly number[]): number {\n const sorted = [...values].sort((a, b) => a - b);\n const mid = sorted.length >> 1;\n return sorted.length % 2 === 1\n ? sorted[mid]\n : (sorted[mid - 1] + sorted[mid]) / 2;\n}\n\n/** A row of text: the words that share it, and the box they fill. */\ninterface TextRow {\n words: number[];\n yMin: number;\n yMax: number;\n}\n\n/**\n * Two words set at sizes this far apart are not one line of text, however\n * they overlap: a display title's box spans both lines of the small print\n * beside it.\n */\nconst ROW_SIZE_RATIO = 2;\n\n/** Whether two words are set at sizes one line of text could hold. */\nfunction comparableSize(a: PdfTextWord, b: PdfTextWord): boolean {\n const ha = a.yMax - a.yMin;\n const hb = b.yMax - b.yMin;\n return Math.max(ha, hb) <= ROW_SIZE_RATIO * Math.min(ha, hb);\n}\n\n/**\n * The given words grouped into the rows they are read on, top down. The\n * reading order and the page-number sweep ask the same question of a page,\n * so they ask it once. A word joins a row it overlaps that holds a word of\n * about its own size; measured only against the row's first word, a title\n * beside a two-line caption took both caption lines into its row, and they\n * were read one word from each line at a time.\n */\nfunction groupRows(\n words: readonly PdfTextWord[],\n indices: readonly number[]\n): TextRow[] {\n const rows: TextRow[] = [];\n const byY = [...indices].sort(\n (a, b) => words[a].yMin - words[b].yMin || words[a].xMin - words[b].xMin\n );\n for (const i of byY) {\n const word = words[i];\n const row = rows[rows.length - 1];\n const peer = row?.words.find((w) => comparableSize(words[w], word));\n if (row && peer !== undefined && sameRow(words[peer], word)) {\n row.words.push(i);\n row.yMin = Math.min(row.yMin, word.yMin);\n row.yMax = Math.max(row.yMax, word.yMax);\n } else rows.push({ words: [i], yMin: word.yMin, yMax: word.yMax });\n }\n return rows;\n}\n\nfunction uprightOrder(\n words: readonly PdfTextWord[],\n indices: readonly number[]\n): number[] {\n const grouped = groupRows(words, indices);\n const rows = grouped.map((row) => row.words);\n const fragmentsOf = (row: number[]): RowFragment[] => {\n const sorted = [...row].sort((a, b) => words[a].xMin - words[b].xMin);\n // A space is a quarter of the size; half a line's height is two of\n // them, and a cell gap on a 23-point figure is more than that. Tight\n // cell padding puts less than that between two columns, though, and\n // then poppler runs the two cells into one fragment and the column\n // they belong to cannot be told. So a gap the row's own spaces cannot\n // account for parts cells as well: within a row a word space holds to\n // its median within a hundredth — justification stretches every space\n // of a line alike — while a cell boundary is close to twice it.\n const gaps = sorted.slice(1).map((i, n) => {\n const height = words[i].yMax - words[i].yMin;\n const gap = words[i].xMin - words[sorted[n]].xMax;\n return { gap, height, wide: gap > height / 2 };\n });\n const spaces = gaps.filter((g) => !g.wide).map((g) => g.gap);\n const typical = spaces.length > 1 ? median(spaces) : undefined;\n const isCellBoundary = (n: number): boolean => {\n const g = gaps[n - 1];\n return (\n g.wide ||\n (typical !== undefined &&\n g.gap > g.height / 3 &&\n g.gap >= typical * 1.5)\n );\n };\n const out: RowFragment[] = [];\n sorted.forEach((i, n) => {\n const last = out[out.length - 1];\n const w = words[i];\n if (last && !isCellBoundary(n)) {\n last.words.push(i);\n last.xMax = Math.max(last.xMax, w.xMax);\n } else out.push({ xMin: w.xMin, xMax: w.xMax, words: [i] });\n });\n return out;\n };\n const fragments = rows.map(fragmentsOf);\n const boxes = grouped;\n\n // Rows less than a line apart or overlapping (a cell centred beside a\n // wrapped label; DejaVu's wrapped lines sit two thirds of a line apart)\n // belong to one cluster. Columns are the x-intervals its fragments\n // fall in, merged where they overlap, so a right-aligned cell's shorter\n // second line joins its first. A cluster that resolves into more than\n // one column is a table and reads column by column — a single value\n // centred between the two lines of the label beside it parts them\n // exactly as a row of several cells does, and no row of that block has\n // to hold two fragments for the block to exist. A fragment across two\n // columns is prose under the table: it closes the cluster and opens\n // the next. The other way round, prose over the table, a row that sets\n // two fragments in one column shows the column was several all along:\n // what the column holds so far is read first, whole, and each fragment\n // opens a column of its own. Left as one column, the cells under the\n // prose would be read row by row, one cell's second line after its\n // neighbour's first.\n interface Column {\n xMin: number;\n xMax: number;\n fragments: RowFragment[];\n }\n const order: number[] = [];\n const read = new Set<RowFragment>();\n const readColumn = (column: Column): void => {\n column.fragments.sort(\n (a, b) =>\n words[a.words[0]].yMin - words[b.words[0]].yMin || a.xMin - b.xMin\n );\n for (const f of column.fragments) {\n order.push(...f.words);\n read.add(f);\n }\n };\n let cluster: { rows: number[]; columns: Column[] } | null = null;\n const flush = (): void => {\n if (!cluster) return;\n if (cluster.columns.length < 2) {\n for (const r of cluster.rows)\n for (const f of fragments[r]) if (!read.has(f)) order.push(...f.words);\n } else {\n cluster.columns.sort((a, b) => a.xMin - b.xMin);\n for (const column of cluster.columns) readColumn(column);\n }\n cluster = null;\n };\n const overlapping = (columns: Column[], f: RowFragment): Column[] =>\n columns.filter(\n (c) => Math.min(c.xMax, f.xMax) - Math.max(c.xMin, f.xMin) > 0\n );\n for (let r = 0; r < rows.length; r++) {\n const lineHeight = boxes[r].yMax - boxes[r].yMin;\n const adjacent =\n cluster !== null && boxes[r].yMin - boxes[r - 1].yMax <= lineHeight * 0.9;\n const wide =\n cluster !== null &&\n fragments[r].some((f) => overlapping(cluster!.columns, f).length > 1);\n if (!adjacent || wide) flush();\n if (!cluster) cluster = { rows: [], columns: [] };\n cluster.rows.push(r);\n const landing = new Map<Column, number>();\n for (const f of fragments[r]) {\n const hit = overlapping(cluster.columns, f)[0];\n if (hit) landing.set(hit, (landing.get(hit) ?? 0) + 1);\n }\n for (const [column, count] of landing) {\n if (count < 2) continue;\n readColumn(column);\n cluster.columns.splice(cluster.columns.indexOf(column), 1);\n }\n for (const f of fragments[r]) {\n const hit = overlapping(cluster.columns, f)[0];\n if (hit) {\n hit.xMin = Math.min(hit.xMin, f.xMin);\n hit.xMax = Math.max(hit.xMax, f.xMax);\n hit.fragments.push(f);\n } else\n cluster.columns.push({ xMin: f.xMin, xMax: f.xMax, fragments: [f] });\n }\n }\n flush();\n return order;\n}\n\n/** Index of the first non-empty fragment starting at or after `offset`. */\nfunction fragmentAt(index: StreamIndex, offset: number): number {\n let low = 0;\n let high = index.positions.length - 1;\n // Last fragment whose position is <= offset; empty fragments share their\n // successor's position, so walk forward past them afterwards.\n while (low < high) {\n const mid = Math.ceil((low + high) / 2);\n if (index.positions[mid] <= offset) low = mid;\n else high = mid - 1;\n }\n while (low < index.fragments.length && index.fragments[low] === '') low++;\n return low;\n}\n\n/** Whether [at, end) starts on a fragment boundary and ends on one. */\nfunction alignedToFragments(\n index: StreamIndex,\n at: number,\n end: number\n): boolean {\n const first = fragmentAt(index, at);\n if (first >= index.fragments.length || index.positions[first] !== at) {\n return false;\n }\n let i = first;\n while (\n i < index.fragments.length &&\n index.positions[i] + index.fragments[i].length < end\n ) {\n i++;\n }\n return (\n i < index.fragments.length &&\n index.positions[i] + index.fragments[i].length === end\n );\n}\n\n/**\n * The stream range [at, end) as one part per page it touches, each with the\n * union box of its words — how a paragraph broken across a page reports the\n * geometry of both halves without losing which page each is on.\n */\nfunction partsOf(\n index: StreamIndex,\n at: number,\n end: number\n): OccurrencePart[] {\n const parts: OccurrencePart[] = [];\n for (let i = fragmentAt(index, at); i < index.fragments.length; i++) {\n const start = index.positions[i];\n if (start >= end) break;\n if (index.fragments[i] === '') continue;\n const ref = index.refs[i];\n const w = index.pages[ref.pageIndex].words[ref.word];\n let part = parts[parts.length - 1];\n if (!part || part.pageIndex !== ref.pageIndex) {\n part = {\n pageIndex: ref.pageIndex,\n words: [],\n xMin: Infinity,\n yMin: Infinity,\n xMax: -Infinity,\n yMax: -Infinity,\n };\n parts.push(part);\n }\n part.words.push(ref.word);\n part.xMin = Math.min(part.xMin, w.xMin);\n part.yMin = Math.min(part.yMin, w.yMin);\n part.xMax = Math.max(part.xMax, w.xMax);\n part.yMax = Math.max(part.yMax, w.yMax);\n }\n return parts;\n}\n\n/** One match of a stream range, with the pages it spans. */\nfunction occurrence(\n index: StreamIndex,\n at: number,\n end: number\n): TextOccurrence {\n const parts = partsOf(index, at, end);\n return {\n at,\n end,\n pageIndex: parts[0].pageIndex,\n endPageIndex: parts[parts.length - 1].pageIndex,\n parts,\n };\n}\n\n/** The most a rendered field value may add between two segments, folded. */\nexport const MAX_FIELD_GAP = 24;\n\n/**\n * Every occurrence of a segmented needle in the stream, in reading order:\n * the first segment anywhere, each following one within `MAX_FIELD_GAP`\n * characters of the previous, the whole aligned to fragment boundaries.\n */\nexport function findOccurrences(\n index: StreamIndex,\n segments: readonly string[]\n): TextOccurrence[] {\n if (segments.length === 0 || segments[0] === '') return [];\n const hits: TextOccurrence[] = [];\n let searchFrom = 0;\n for (;;) {\n const at = index.stream.indexOf(segments[0], searchFrom);\n if (at === -1) break;\n searchFrom = at + 1;\n let end = at + segments[0].length;\n let complete = true;\n for (let s = 1; s < segments.length; s++) {\n const next = index.stream.indexOf(segments[s], end);\n if (next === -1 || next - end > MAX_FIELD_GAP) {\n complete = false;\n break;\n }\n end = next + segments[s].length;\n }\n if (!complete || !alignedToFragments(index, at, end)) continue;\n const hit = occurrence(index, at, end);\n // The stream sets the rotated text of every page after all the upright\n // text: upright text running on into it, or one page's rotated run into\n // the next page's, is two neighbours of the index, not text of a page.\n if (\n at < index.rotatedFrom\n ? end > index.rotatedFrom\n : hit.pageIndex !== hit.endPageIndex\n ) {\n continue;\n }\n hits.push(hit);\n searchFrom = end;\n }\n return hits;\n}\n\n/** What an inventory entry needs to be matched: its text, in reading order. */\nexport interface InventoryEntry {\n path: string;\n text: string;\n /** Header/footer text repeats per page: every occurrence is legitimate. */\n repeats?: boolean;\n /**\n * Painted by the renderer from other authored text — a contents entry —\n * so it claims its occurrence when there is one and is `skipped`, never\n * `missing`, when there is not.\n */\n optional?: boolean;\n /**\n * The 0-based page the text is drawn on, when the format knows it before\n * rendering: a slide is its own page. Only an occurrence starting there\n * is the entry's — \"13 pts\" on slide 2 is not \"+1.3 pts\" on slide 8,\n * however alike the two fold.\n */\n page?: number;\n /**\n * Where on that page the text was laid out, in PDF points: a slide text\n * box. Of the entry's occurrences, one inside this region is its own. A\n * section tracker that repeats the first word of the title under it is\n * two occurrences of one word, and reading order cannot say which box\n * painted which; the boxes can.\n */\n region?: { xMin: number; yMin: number; xMax: number; yMax: number };\n}\n\nexport type MappingStatus = 'mapped' | 'ambiguous' | 'missing' | 'skipped';\n\nexport interface InventoryMatch<T extends InventoryEntry = InventoryEntry> {\n entry: T;\n needle: string;\n status: MappingStatus;\n /** One occurrence when mapped; every occurrence when the text repeats. */\n occurrences: TextOccurrence[];\n /**\n * Set when only a leading part of the text rendered: the rest was cut\n * off by a frame, a page edge or a box. Folded character counts.\n */\n partial?: { matchedChars: number; totalChars: number };\n}\n\n/** Below this many folded characters a needle matches too much to trust. */\nexport const MIN_NEEDLE_LENGTH = 3;\n/** A prefix shorter than this could be any sentence; it proves nothing. */\nexport const MIN_PARTIAL_PREFIX = 16;\n/** Running heads and footers live in the outer fifth of the page. */\nexport const CHROME_BAND = 0.2;\n\n/**\n * The longest leading part of `needle` that occurs in the stream, when the\n * whole does not — a paragraph whose tail was clipped still starts where it\n * was written. Every place the first `MIN_PARTIAL_PREFIX` characters occur\n * on a fragment boundary is extended character by character; the longest\n * extension wins. Given a page, only a prefix starting on it counts, and it\n * ends where the page does: a slide's text never runs on into the next\n * slide's. Upright text never runs on into the rotated text after it, and\n * rotated text never past its own page's run.\n */\nexport function longestRenderedPrefix(\n index: StreamIndex,\n needle: string,\n page?: number,\n region?: InventoryEntry['region']\n): { length: number; at: number } | undefined {\n if (needle.length < MIN_PARTIAL_PREFIX) return undefined;\n const anchor = needle.slice(0, MIN_PARTIAL_PREFIX);\n let best: { length: number; at: number } | undefined;\n let searchFrom = 0;\n for (;;) {\n const at = index.stream.indexOf(anchor, searchFrom);\n if (at === -1) break;\n searchFrom = at + 1;\n const first = fragmentAt(index, at);\n if (first >= index.fragments.length || index.positions[first] !== at) {\n continue;\n }\n if (page !== undefined && index.refs[first].pageIndex !== page) continue;\n const rotated = at >= index.rotatedFrom;\n const limit = Math.min(\n page !== undefined || rotated\n ? pageEnd(index, first)\n : index.stream.length,\n rotated ? index.stream.length : index.rotatedFrom\n );\n if (at + MIN_PARTIAL_PREFIX > limit) continue;\n let length = MIN_PARTIAL_PREFIX;\n while (\n length < needle.length &&\n at + length < limit &&\n index.stream.charCodeAt(at + length) === needle.charCodeAt(length)\n ) {\n length++;\n }\n if (region && !touchesRegion(partsOf(index, at, at + length), region)) {\n continue;\n }\n if (!best || length > best.length) best = { length, at };\n }\n return best;\n}\n\n/** Offset where the stream leaves the page of fragment `from`. */\nfunction pageEnd(index: StreamIndex, from: number): number {\n const page = index.refs[from].pageIndex;\n let i = from;\n while (i < index.refs.length && index.refs[i].pageIndex === page) i++;\n return i < index.positions.length ? index.positions[i] : index.stream.length;\n}\n\n/** A little past a box's edge still touches it: renderer rounding, insets. */\nconst REGION_TOLERANCE_PT = 2;\n\n/**\n * Whether any part of an occurrence reaches into a region. Slide text starts\n * inside its box however far it overflows, so an occurrence that nowhere\n * touches the box is some other box's words that happen to read the same.\n */\nfunction touchesRegion(\n parts: readonly OccurrencePart[],\n region: NonNullable<InventoryEntry['region']>\n): boolean {\n return parts.some(\n (part) =>\n part.xMax >= region.xMin - REGION_TOLERANCE_PT &&\n part.xMin <= region.xMax + REGION_TOLERANCE_PT &&\n part.yMax >= region.yMin - REGION_TOLERANCE_PT &&\n part.yMin <= region.yMax + REGION_TOLERANCE_PT\n );\n}\n\n/** Vertical overlap of more than half the shorter box: one row of text. */\nfunction sameRow(\n a: { yMin: number; yMax: number },\n b: { yMin: number; yMax: number }\n): boolean {\n const overlap = Math.min(a.yMax, b.yMax) - Math.max(a.yMin, b.yMin);\n const shorter = Math.min(a.yMax - a.yMin, b.yMax - b.yMin);\n return shorter > 0 && overlap > shorter / 2;\n}\n\n/** Whether a box sits in the top or bottom fifth, where chrome lives. */\nfunction inChromeBand(\n page: PdfTextPage,\n box: { yMin: number; yMax: number }\n): boolean {\n return (\n box.yMax <= page.heightPt * CHROME_BAND ||\n box.yMin >= page.heightPt * (1 - CHROME_BAND)\n );\n}\n\nconst PAGE_NUMBER_ROW = /^[0-9\\s./|-]*$/;\n\n/** Rows this close in `yMin` are the same chrome position on different pages. */\nconst CHROME_ROW_TOLERANCE_PT = 6;\n\n/**\n * Which of a repeating entry's in-band occurrences are its chrome.\n *\n * Chrome sits nearest the page edge and repeats at the same height. So on\n * each page only the occurrence closest to the top or bottom edge stands —\n * a body line near the top of a page that happens to say the same words is\n * further in than the running head above it — and, when the entry shows on\n * more than one page, only heights that recur on another page count. A\n * header that exists on a single page (a two-page report whose cover has\n * none) has nothing to recur against and is kept as it is.\n */\nfunction chromeOccurrences<\n T extends { pageIndex: number; parts: { yMin: number; yMax: number }[] },\n>(occurrences: readonly T[], pages: readonly PdfTextPage[]): T[] {\n const nearestPerPage = new Map<number, T>();\n const edgeDistance = (o: T) =>\n Math.min(o.parts[0].yMin, pages[o.pageIndex].heightPt - o.parts[0].yMax);\n for (const o of occurrences) {\n const held = nearestPerPage.get(o.pageIndex);\n if (!held || edgeDistance(o) < edgeDistance(held)) {\n nearestPerPage.set(o.pageIndex, o);\n }\n }\n const nearest = [...nearestPerPage.values()];\n if (nearest.length < 2) return nearest;\n return nearest.filter((o) =>\n nearest.some(\n (other) =>\n other.pageIndex !== o.pageIndex &&\n Math.abs(other.parts[0].yMin - o.parts[0].yMin) <=\n CHROME_ROW_TOLERANCE_PT\n )\n );\n}\n\n/**\n * The rows that are page numbers: digits alone in a band, and chrome rather\n * than content that flowed there.\n *\n * A footer that is nothing but the field renders as a bare number with no\n * repeating text to claim it, so it needs a rule of its own. The band is a\n * generous fifth of the page, though, and body content reaches into it: a\n * table's numeric row lands there on whatever page the table breaks on,\n * and a numeric cell claimed as chrome loses its own occurrence and reads\n * as never rendered.\n *\n * Two things tell chrome from content, both geometry. A page number stands\n * apart — the page margin separates it from the body, so no unclaimed row\n * lies within a line of it, while a table row has its wrapped label or its\n * neighbouring rows right against it. And a page number recurs: the\n * renderer paints one at the same height on every page, which content that\n * merely flowed into the band on one page does not. A single-page document\n * has nothing to recur against, so there the first test stands alone.\n */\nfunction pageNumberRows(\n pages: readonly PdfTextPage[],\n claimed: ReadonlySet<string>\n): { pageIndex: number; box: { yMin: number; yMax: number } }[] {\n const hits: { pageIndex: number; box: TextRow }[] = [];\n pages.forEach((page, pageIndex) => {\n const rows = groupRows(\n page.words,\n page.words.map((_, i) => i)\n );\n const free = rows.map((row) =>\n row.words.some((i) => !claimed.has(`${pageIndex}:${i}`))\n );\n rows.forEach((row, r) => {\n if (!free[r] || !inChromeBand(page, row)) return;\n if (!row.words.every((i) => PAGE_NUMBER_ROW.test(page.words[i].text)))\n return;\n const line = row.yMax - row.yMin;\n const attached = rows.some(\n (other, o) =>\n o !== r &&\n free[o] &&\n Math.max(other.yMin - row.yMax, row.yMin - other.yMax) < line\n );\n if (!attached) hits.push({ pageIndex, box: row });\n });\n });\n if (pages.length < 2) return hits;\n return hits.filter((hit) =>\n hits.some(\n (other) =>\n other.pageIndex !== hit.pageIndex &&\n Math.abs(other.box.yMin - hit.box.yMin) <= CHROME_ROW_TOLERANCE_PT\n )\n );\n}\n\n/**\n * Of an entry's free occurrences, the one lying most inside its region: the\n * largest share of its words' box within it. An occurrence that runs out of\n * the region still counts by the part inside — text overflowing its box\n * starts in it. Nothing inside, and reading order decides after all.\n */\nfunction insideRegion(\n occurrences: readonly TextOccurrence[],\n region: NonNullable<InventoryEntry['region']>\n): TextOccurrence | undefined {\n let best: { occurrence: TextOccurrence; share: number } | undefined;\n for (const o of occurrences) {\n let inside = 0;\n let total = 0;\n for (const part of o.parts) {\n const w =\n Math.min(part.xMax, region.xMax) - Math.max(part.xMin, region.xMin);\n const h =\n Math.min(part.yMax, region.yMax) - Math.max(part.yMin, region.yMin);\n if (w > 0 && h > 0) inside += w * h;\n total += (part.xMax - part.xMin) * (part.yMax - part.yMin);\n }\n const share = total > 0 ? inside / total : 0;\n if (share > 0 && (!best || share > best.share)) {\n best = { occurrence: o, share };\n }\n }\n return best?.occurrence;\n}\n\n/**\n * Slide text that overflowed its box onto other text. No run of the stream\n * holds it whole: the words it was drawn over share its rows and read in\n * between. Inside the column the box stands in, from its top down, its own\n * words still come in order with only those foreign words between them — so\n * the needle is matched there across whole fragments, skipping the fewest\n * foreign ones, and never consuming a word another entry already claimed.\n * A match that skips more words than it keeps is not the text overprinted\n * but words that happen to spell it, and is refused.\n *\n * The stream range from its first word to its last holds the words it was\n * drawn over, which are another entry's, so the occurrence claims no range:\n * `claims` holds the range of each word it kept, to be claimed one by one.\n */\nfunction overprintedOccurrence(\n index: StreamIndex,\n page: number,\n region: NonNullable<InventoryEntry['region']>,\n needle: string,\n claimed: (offset: number) => boolean\n):\n | { occurrence: TextOccurrence; claims: { at: number; end: number }[] }\n | undefined {\n const words = index.pages[page]?.words;\n if (!words) return undefined;\n const fragmentOf = new Map<number, number>();\n index.refs.forEach((ref, i) => {\n if (ref.pageIndex !== page || index.fragments[i] === '') return;\n const w = words[ref.word];\n const centre = (w.xMin + w.xMax) / 2;\n if (centre < region.xMin || centre > region.xMax) return;\n if (w.yMax < region.yMin) return;\n fragmentOf.set(ref.word, i);\n });\n const sequence = groupRows(words, [...fragmentOf.keys()]).flatMap((row) =>\n [...row.words].sort((a, b) => words[a].xMin - words[b].xMin)\n );\n interface Path {\n skips: number;\n words: number[];\n }\n let reach = new Map<number, Path>();\n let best: Path | undefined;\n for (const word of sequence) {\n const i = fragmentOf.get(word) as number;\n const text = index.fragments[i];\n const usable = !claimed(index.positions[i]);\n const next = new Map<number, Path>();\n const offer = (consumed: number, path: Path) => {\n const held = next.get(consumed);\n if (!held || path.skips < held.skips) next.set(consumed, path);\n };\n // Overflow runs out of the box, never into it: the text starts inside.\n const inside =\n words[word].yMin + words[word].yMax <= 2 * region.yMax &&\n words[word].yMin + words[word].yMax >= 2 * region.yMin;\n if (usable && inside && needle.startsWith(text)) {\n offer(text.length, { skips: 0, words: [word] });\n }\n for (const [consumed, path] of reach) {\n if (usable && needle.startsWith(text, consumed)) {\n offer(consumed + text.length, {\n skips: path.skips,\n words: [...path.words, word],\n });\n }\n offer(consumed, { skips: path.skips + 1, words: path.words });\n }\n const done = next.get(needle.length);\n if (done && (!best || done.skips < best.skips)) best = done;\n next.delete(needle.length);\n reach = next;\n }\n if (!best || best.skips > best.words.length) return undefined;\n const first = fragmentOf.get(best.words[0]) as number;\n const part: OccurrencePart = {\n pageIndex: page,\n words: best.words,\n xMin: Math.min(...best.words.map((w) => words[w].xMin)),\n yMin: Math.min(...best.words.map((w) => words[w].yMin)),\n xMax: Math.max(...best.words.map((w) => words[w].xMax)),\n yMax: Math.max(...best.words.map((w) => words[w].yMax)),\n };\n const at = index.positions[first];\n return {\n occurrence: {\n at,\n end: at,\n pageIndex: page,\n endPageIndex: page,\n parts: [part],\n },\n claims: best.words.map((w) => {\n const i = fragmentOf.get(w) as number;\n return {\n at: index.positions[i],\n end: index.positions[i] + index.fragments[i].length,\n };\n }),\n };\n}\n\nexport interface InventoryAssignment<T extends InventoryEntry> {\n matches: InventoryMatch<T>[];\n /** `pageIndex:word` keys of every word chrome claimed — whole rows. */\n chromeWords: ReadonlySet<string>;\n}\n\n/**\n * Attribute rendered occurrences to inventory entries.\n *\n * Repeating entries go first and claim their occurrences inside the page\n * bands, one row each; rows of nothing but digits in those bands — page\n * numbers — go with them. The rest are visited in document order over a\n * stream without those words; each claims the first unclaimed occurrence at\n * or after the previous claim, which is how \"Total\" in the second table\n * finds the second \"Total\". An entry with no occurrence is `missing` —\n * fully clipped, or never set — unless it is optional, and one whose every\n * occurrence was already claimed is `ambiguous`.\n *\n * A claim holds the whole range it matched, not just where it starts: a\n * rendered word belongs to one authored string, so \"Revenue\" inside a title\n * another entry already owns is not free for a tracker that says \"Revenue\".\n * An entry that names its page looks only there, and one that names its\n * region takes the occurrence lying most inside it before reading order is\n * consulted at all.\n */\nexport function assignInventory<T extends InventoryEntry>(\n pages: readonly PdfTextPage[],\n inventory: readonly T[]\n): InventoryAssignment<T> {\n const results = new Map<T, InventoryMatch<T>>();\n const chromeWords = new Set<string>();\n const claimRow = (\n pageIndex: number,\n box: { yMin: number; yMax: number }\n ): void => {\n pages[pageIndex].words.forEach((w, i) => {\n if (sameRow(w, box)) chromeWords.add(`${pageIndex}:${i}`);\n });\n };\n\n const repeating = inventory.filter((entry) => entry.repeats);\n if (repeating.length > 0) {\n const full = indexDocument(pages);\n for (const entry of repeating) {\n const segments = needleSegments(entry.text);\n const needle = segments.join('');\n if (needle.length < MIN_NEEDLE_LENGTH) {\n results.set(entry, {\n entry,\n needle,\n status: 'skipped',\n occurrences: [],\n });\n continue;\n }\n const inBand = findOccurrences(full, segments).filter(\n (o) =>\n o.pageIndex === o.endPageIndex &&\n inChromeBand(pages[o.pageIndex], o.parts[0])\n );\n const occurrences = chromeOccurrences(inBand, pages);\n for (const o of occurrences) claimRow(o.pageIndex, o.parts[0]);\n results.set(entry, {\n entry,\n needle,\n status: occurrences.length > 0 ? 'mapped' : 'missing',\n occurrences,\n });\n }\n }\n for (const row of pageNumberRows(pages, chromeWords))\n claimRow(row.pageIndex, row.box);\n\n const body = indexDocument(pages, chromeWords);\n /** Every stream range an entry has claimed, and the entry holding it. */\n const claims: { at: number; end: number; entry: T }[] = [];\n const claimsOver = (o: TextOccurrence) =>\n claims.filter((claim) => o.at < claim.end && claim.at < o.end);\n let cursor = 0;\n for (const entry of inventory) {\n if (entry.repeats) continue;\n const segments = needleSegments(entry.text);\n const needle = segments.join('');\n if (needle.length < MIN_NEEDLE_LENGTH) {\n results.set(entry, { entry, needle, status: 'skipped', occurrences: [] });\n continue;\n }\n let all = findOccurrences(body, segments).filter(\n (o) =>\n (entry.page === undefined ||\n (o.pageIndex === entry.page && o.endPageIndex === entry.page)) &&\n (!entry.region || touchesRegion(o.parts, entry.region))\n );\n let partial: InventoryMatch<T>['partial'];\n /** The words an overprinted match kept, when that is the match. */\n let sparse: { at: number; end: number }[] | undefined;\n if (\n all.length === 0 &&\n segments.length === 1 &&\n entry.page !== undefined &&\n entry.region\n ) {\n const overprinted = overprintedOccurrence(\n body,\n entry.page,\n entry.region,\n needle,\n (offset) =>\n claims.some((claim) => claim.at <= offset && offset < claim.end)\n );\n if (overprinted) {\n all = [overprinted.occurrence];\n sparse = overprinted.claims;\n }\n }\n // An optional entry is skipped when absent, so its prefix — the costliest\n // search here — is never worth looking for.\n if (all.length === 0 && segments.length === 1 && !entry.optional) {\n const prefix = longestRenderedPrefix(\n body,\n needle,\n entry.page,\n entry.region\n );\n if (prefix) {\n all = [occurrence(body, prefix.at, prefix.at + prefix.length)];\n partial = { matchedChars: prefix.length, totalChars: needle.length };\n }\n }\n if (all.length === 0) {\n results.set(entry, {\n entry,\n needle,\n status: entry.optional ? 'skipped' : 'missing',\n occurrences: [],\n });\n continue;\n }\n let free = all.filter((o) => claimsOver(o).length === 0);\n if (free.length === 0 && !entry.optional) {\n // Every occurrence is taken. One taken by an optional entry — a\n // contents line for a heading the page shows only once — is the\n // author's text, not the field's: the optional claim is released and\n // the entry that was actually written keeps its occurrence.\n const held = all.find((o) => {\n const over = claimsOver(o);\n return over.length > 0 && over.every((claim) => claim.entry.optional);\n });\n if (held) {\n for (const claim of claimsOver(held)) {\n claims.splice(claims.indexOf(claim), 1);\n results.set(claim.entry, {\n entry: claim.entry,\n needle: results.get(claim.entry)?.needle ?? '',\n status: 'skipped',\n occurrences: [],\n });\n }\n free = [held];\n }\n }\n if (free.length === 0) {\n results.set(entry, {\n entry,\n needle,\n status: 'ambiguous',\n occurrences: all,\n });\n continue;\n }\n const chosen =\n (entry.region && insideRegion(free, entry.region)) ??\n free.find((o) => o.at >= cursor) ??\n free[0];\n for (const range of sparse ?? [chosen])\n claims.push({ at: range.at, end: range.end, entry });\n // Rotated text sits past every page's upright text: taking it says\n // nothing about how far reading has got.\n if (chosen.at < body.rotatedFrom) cursor = chosen.at;\n results.set(entry, {\n entry,\n needle,\n status: 'mapped',\n occurrences: [chosen],\n ...(partial && { partial }),\n });\n }\n\n return {\n matches: inventory.map((entry) => results.get(entry) as InventoryMatch<T>),\n chromeWords,\n };\n}\n","/**\n * The authored side of the rendered pass (#344): the text inventory and the\n * requested font families, read off a prepared document's quality facts.\n *\n * `jto_preview` and the ground-truth harness both hand the pass this, so the\n * mapping a measurement scores is the mapping the tool ships. Every pointer\n * in it has already been through the block source maps, and lands on a slot\n * the author can patch.\n */\n\nimport type { QualityFact } from '@json-to-office/quality';\nimport type { FormatName } from './format-adapter';\nimport type { RenderedTextEntry, RequestedFont } from './rendered-analysis';\n\ntype Rec = Record<string, unknown>;\n\n/** The text inventory a format's facts expose, as the analysis reads it. */\nexport function renderedInventoryFromFacts(\n format: FormatName,\n facts: readonly QualityFact[]\n): RenderedTextEntry[] {\n const entries: RenderedTextEntry[] = [];\n for (const fact of facts as readonly (QualityFact & Rec)[]) {\n if (format === 'docx' && fact.kind === 'docx/text') {\n const frame = fact.frame as\n | { widthPt?: number; heightPt?: number }\n | undefined;\n entries.push({\n path: fact.path,\n text: String(fact.text),\n role: fact.role as RenderedTextEntry['role'],\n ...(typeof fact.level === 'number' && { level: fact.level }),\n ...(fact.repeats === true && { repeats: true }),\n ...(fact.optional === true && { optional: true }),\n ...(frame && {\n box: {\n ...(frame.widthPt !== undefined && { widthPt: frame.widthPt }),\n ...(frame.heightPt !== undefined && { heightPt: frame.heightPt }),\n },\n }),\n });\n } else if (format === 'pptx' && fact.kind === 'pptx/text') {\n // A hidden slide is left out of the export: its text is on no page,\n // and looking for it would only claim a visible slide's words.\n if (fact.slideHidden === true) continue;\n // Text set at an angle comes out of the PDF as per-glyph fragments in\n // no reading order, and its box no longer bounds it on the page's\n // axes: it claims an occurrence when one matches, is never reported\n // missing, and is held to no box.\n const turned =\n typeof fact.rotationDeg === 'number' &&\n ((fact.rotationDeg % 360) + 360) % 360 !== 0;\n if (turned) {\n entries.push({\n path: fact.path,\n text: String(fact.text),\n role: 'slide-text',\n optional: true,\n ...(typeof fact.page === 'number' && { page: fact.page }),\n });\n continue;\n }\n const { boxXPt: x, boxYPt: y } = fact;\n const widthPt = fact.boxWidthPt;\n const heightPt = fact.boxHeightPt;\n entries.push({\n path: fact.path,\n text: String(fact.text),\n role: 'slide-text',\n // A slide is its own page and its boxes are where the deck put\n // them, so the text is looked for there before anywhere else.\n ...(typeof fact.page === 'number' && { page: fact.page }),\n ...(typeof x === 'number' &&\n typeof y === 'number' &&\n typeof widthPt === 'number' &&\n typeof heightPt === 'number' && {\n region: {\n xMin: x,\n yMin: y,\n xMax: x + widthPt,\n yMax: y + heightPt,\n },\n }),\n ...((typeof widthPt === 'number' || typeof heightPt === 'number') && {\n box: {\n ...(typeof widthPt === 'number' && { widthPt }),\n ...(typeof heightPt === 'number' && { heightPt }),\n },\n }),\n });\n }\n }\n return entries;\n}\n\n/**\n * Families the pass should find in the PDF: every family the document's\n * text uses (a `font-family` fact, with its pointer) plus every family the\n * render resolved from a declared source. A theme family nothing on the page\n * uses — the default mono face in a report without code — is never embedded\n * and must not read as substituted.\n */\nexport function requestedFontsFromFacts(\n format: FormatName,\n facts: readonly QualityFact[],\n resolvedFonts: readonly { family: string; declared: boolean }[]\n): RequestedFont[] {\n const kind = format === 'docx' ? 'docx/font-family' : 'pptx/font-family';\n const declared = new Set(\n resolvedFonts\n .filter((font) => font.declared)\n .map((font) => font.family.trim().toLowerCase())\n );\n const seen = new Set<string>();\n const requested: RequestedFont[] = [];\n const add = (family: unknown, path?: string) => {\n if (typeof family !== 'string' || family.trim() === '') return;\n const key = family.trim().toLowerCase();\n if (seen.has(key)) return;\n seen.add(key);\n requested.push({\n family: family.trim(),\n ...(path && { path }),\n ...(declared.has(key) && { declared: true }),\n });\n };\n for (const fact of facts as readonly (QualityFact & Rec)[]) {\n if (fact.kind === kind) add(fact.family, fact.path);\n }\n for (const font of resolvedFonts) if (font.declared) add(font.family);\n return requested;\n}\n","/**\n * The block boundary matrix (#343, report portion): every JSON block\n * definition a playground template embeds, invoked at the edges of its own\n * slot schema — minimum and maximum cardinality, every string at its word\n * budget, every figure at its widest — on each bundled theme, with the\n * design fonts and with the fallback faces LibreOffice substitutes when a\n * host lacks them, on A4 and on Letter.\n *\n * The generator is pure and reads nothing but the template it is handed:\n * a definition gains coverage by being embedded, never by being listed here.\n * What the suites do with a case — the static rules under the archetype's\n * profile, the rendered pass over the LibreOffice PDF — is theirs; this\n * module only says what a boundary document is.\n *\n * Two shapes come out. A *block case* is a small report — cover, running\n * head, section opener — with one block at an edge in the body, so a\n * finding names the block that caused it. A *report case* is the template's\n * own document with every invocation at that edge, so the blocks meet each\n * other the way they do in a real report. Both carry their definitions and\n * dependencies inline, so nothing is resolved at render time.\n */\n\nimport {\n blockDependencies,\n readBlockDefinitions,\n type BlockSlot,\n type JsonBlockDefinition,\n} from '@json-to-office/shared';\n\ntype Rec = Record<string, unknown>;\n\nexport type MatrixEdge = 'min' | 'max';\nexport type MatrixFont = 'design' | 'fallback';\nexport type MatrixCanvas = 'A4' | 'LETTER';\n\nexport interface BlockMatrixDefinition {\n name: string;\n /** The template the definition is embedded in, as the catalog names it. */\n template: string;\n /** Where inside that template: `/props/blocks/<name>`. */\n pointer: string;\n definition: JsonBlockDefinition;\n /** The template's own first invocation, when it has one: the nominal fill. */\n example?: Rec;\n}\n\nexport interface BlockMatrixCase {\n id: string;\n /** The block at its edge, or `report` for the whole template at that edge. */\n block: string;\n edge: MatrixEdge;\n theme: string;\n font: MatrixFont;\n canvas: MatrixCanvas;\n document: Rec;\n}\n\nexport interface BlockMatrixOptions {\n themes: readonly string[];\n fonts?: readonly MatrixFont[];\n edges?: readonly MatrixEdge[];\n canvases?: readonly MatrixCanvas[];\n /** Whether the whole-template report cases are generated. Default true. */\n report?: boolean;\n /** Whether the per-block cases are generated. Default true. */\n blocks?: boolean;\n /**\n * Slot roles the profile under test requires present, so `min` keeps them:\n * the client-report profile's `takeaway` and `source`.\n */\n requiredRoles?: readonly string[];\n}\n\nexport interface CaseConditions {\n font?: MatrixFont;\n canvas?: MatrixCanvas;\n requiredRoles?: readonly string[];\n}\n\n/**\n * The face every LibreOffice ships with, bundled inside the application on\n * each platform: what a design font degrades to when the host lacks it, and\n * wider than Arial or Calibri, so a fit at these metrics is a fit anywhere.\n * Only the roles a report paints are overridden: a family declared for a\n * role nothing uses is never embedded, and the rendered pass would report\n * it as substituted.\n */\nexport const FALLBACK_FONTS = {\n heading: { family: 'DejaVu Sans' },\n body: { family: 'DejaVu Sans' },\n} as const;\n\n/** A 4x2 PNG: an image with an aspect ratio, and no bytes outside the process. */\nexport const MATRIX_IMAGE =\n 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAACCAYAAABytg0kAAAAFElEQVR42mNk+M9QzwAFjDAGACPuA/8fMSCgAAAAAElFTkSuQmCC';\n\n/**\n * Words of the length a report actually uses, so a budget of N words costs\n * what N report words cost — \"a b c\" at the limit proves nothing.\n */\nconst WORDS = [\n 'performance',\n 'delivery',\n 'retention',\n 'operating',\n 'programme',\n 'contracted',\n 'recommendation',\n 'quarter',\n 'measurement',\n 'ownership',\n 'renewal',\n 'segment',\n 'margin',\n 'baseline',\n 'procurement',\n 'integration',\n];\n\n/**\n * `count` words from the lexicon, starting at `seed`. The first word is the\n * seed's ordinal spelt as a word-length token (`item7`), so no two strings\n * from different seeds share a run of words: the rendered pass matches\n * authored strings against the page text, and two slots that read the\n * same would claim each other's occurrences.\n */\nfunction words(count: number, seed: number, sentence: boolean): string {\n if (count <= 0) return '';\n const out: string[] = [`Item${seed}`];\n for (let i = 1; i < count; i += 1) out.push(WORDS[(seed + i) % WORDS.length]);\n return sentence ? `${out.join(' ')}.` : out.join(' ');\n}\n\n/**\n * The widest number of a given length: a true minus, thousands separators\n * and one decimal, every glyph at tabular width. `−1,234,567.0` for 12.\n */\nexport function widestNumber(length: number, seed = 0): string {\n if (length <= 0) return '';\n if (length === 1) return '9';\n if (length < 4) return `−${'9'.repeat(length - 1)}`;\n const group = (digits: number): string =>\n Array.from({ length: digits }, (_, i) => String(((i + seed) % 9) + 1))\n .join('')\n .replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',');\n // The most digits whose grouped form still leaves room for `−` and `.0`;\n // the decimals then grow to the exact length when a comma boundary\n // makes the next digit cost two.\n let digits = 1;\n while (group(digits + 1).length + 3 <= length) digits += 1;\n const integer = group(digits);\n return `−${integer}.${'0'.repeat(length - integer.length - 2)}`;\n}\n\nconst isRecord = (v: unknown): v is Rec =>\n typeof v === 'object' && v !== null && !Array.isArray(v);\n\n/** Slot names whose strings are figures, not prose. */\nconst NUMERIC_SLOTS = new Set(['value', 'delta', 'cells']);\n\nfunction stringValue(\n slot: BlockSlot,\n name: string,\n edge: MatrixEdge,\n seed: number\n): string {\n if (slot.enum && slot.enum.length > 0) {\n const values = slot.enum.map(String);\n return edge === 'max'\n ? values.reduce((a, b) => (b.length > a.length ? b : a))\n : values[0];\n }\n if (slot.maxLength !== undefined && NUMERIC_SLOTS.has(name)) {\n return widestNumber(\n edge === 'max' ? slot.maxLength : Math.min(3, slot.maxLength),\n seed\n );\n }\n if (slot.maxLength !== undefined && name === 'unit') {\n return edge === 'max'\n ? ` €${'m'.repeat(Math.max(0, slot.maxLength - 2))}`.slice(\n 0,\n slot.maxLength\n )\n : '%';\n }\n if (slot.maxLength !== undefined) {\n const target =\n edge === 'max' ? slot.maxLength : Math.max(1, slot.minLength ?? 1);\n // Whole words up to the length: a word cut in two is not a word a\n // report would print, and the rendered pass would go looking for it.\n let text = `Item${seed}`.slice(0, target);\n let i = 0;\n for (;;) {\n const next = WORDS[(seed + i) % WORDS.length];\n i += 1;\n if (text.length + 1 + next.length > target) {\n if (i > WORDS.length) break;\n continue;\n }\n text += ` ${next}`;\n }\n return text;\n }\n const budget = slot.maxWords ?? 12;\n const count = edge === 'max' ? budget : Math.max(1, Math.min(2, budget));\n const sentence = !slot.oneLine && slot.role !== 'source';\n if (slot.role === 'source') return `Source: ${words(count - 1, seed, false)}`;\n if (name === 'number')\n return edge === 'max' ? words(count, seed, false) : '01';\n return words(count, seed, sentence);\n}\n\nfunction componentValue(name: string, edge: MatrixEdge, seed: number): Rec {\n if (name === 'chart') {\n const categories = edge === 'max' ? 8 : 2;\n const series = edge === 'max' ? 3 : 1;\n return {\n name: 'chart',\n props: {\n type: 'column',\n valAxisTitle:\n edge === 'max' ? `${words(3, seed, false)} (€m)` : 'Revenue (€m)',\n ...(edge === 'max' && { catAxisTitle: words(3, seed + 3, false) }),\n chartColors: ['primary', 'secondary', 'accent'].slice(0, series),\n data: Array.from({ length: series }, (_, s) => ({\n name: words(edge === 'max' ? 3 : 1, seed + s, false),\n // Two words per category is where LibreOffice's native chart\n // still keeps rotated labels clear of the axis title; at three\n // the title is drawn over them, at any category count.\n labels: Array.from({ length: categories }, (_, c) =>\n edge === 'max' ? words(2, seed + c, false) : `Q${c + 1}`\n ),\n values: Array.from(\n { length: categories },\n (_, c) => 1.2 * (c + 1) + s\n ),\n })),\n },\n };\n }\n return {\n name: 'image',\n props: {\n base64: MATRIX_IMAGE,\n width: edge === 'max' ? (name === 'logo' ? '40%' : '100%') : '25%',\n alt: words(6, seed, false),\n },\n };\n}\n\n/** A slot's value at an edge; `undefined` means \"leave it out\". */\nexport function boundarySlotValue(\n slot: BlockSlot,\n name: string,\n edge: MatrixEdge,\n seed = 0\n): unknown {\n switch (slot.type) {\n case 'string':\n return stringValue(slot, name, edge, seed);\n case 'number':\n case 'integer':\n return edge === 'max' ? slot.maximum ?? 9999 : slot.minimum ?? 0;\n case 'boolean':\n return edge === 'max' ? true : slot.default ?? true;\n case 'component':\n return componentValue(name, edge, seed);\n case 'object': {\n const out: Rec = {};\n for (const [key, property] of Object.entries(slot.properties ?? {})) {\n if (edge === 'min' && !property.required) continue;\n const value = boundarySlotValue(property, key, edge, seed + key.length);\n if (value !== undefined) out[key] = value;\n }\n return out;\n }\n case 'array': {\n const count =\n edge === 'max'\n ? slot.maxItems ?? 3\n : slot.minItems ?? (slot.required ? 1 : 0);\n const item = slot.items ?? { type: 'string' };\n return Array.from({ length: count }, (_, i) =>\n boundarySlotValue(item, name, edge, seed + i)\n );\n }\n default:\n return undefined;\n }\n}\n\n/**\n * One invocation of a definition with every slot at the edge: optional slots\n * left out at `min` (so defaults are what renders), every slot at `max`.\n * A slot whose role the profile requires — a source under every figure —\n * is never optional to that profile, so `requiredRoles` keeps it at `min`.\n * Columns whose cells must count the rows do; nothing else knows a block.\n */\nexport function boundaryInvocation(\n name: string,\n definition: JsonBlockDefinition,\n edge: MatrixEdge,\n seed = 0,\n requiredRoles: readonly string[] = []\n): Rec {\n const slots: Rec = {};\n let offset = seed;\n for (const [slotName, slot] of Object.entries(definition.slots)) {\n offset += slotName.length;\n const required =\n slot.required === true ||\n (slot.role !== undefined && requiredRoles.includes(slot.role));\n if (edge === 'min' && !required) continue;\n const value = boundarySlotValue(slot, slotName, edge, offset);\n if (value !== undefined) slots[slotName] = value;\n }\n const labels = slots.labels;\n const columns = slots.columns;\n if (Array.isArray(labels) && Array.isArray(columns)) {\n const cells = definition.slots.columns?.items?.properties?.cells;\n for (const column of columns) {\n if (isRecord(column) && Array.isArray(column.cells) && cells?.items) {\n column.cells = labels.map((_, i) =>\n boundarySlotValue(cells.items as BlockSlot, 'cells', edge, offset + i)\n );\n }\n }\n }\n return { name: 'block', props: { ref: name, slots } };\n}\n\n/**\n * The same invocation one past the edge: the first budgeted string a word\n * over, or the first bounded array an item over. What a slot violation\n * looks like, so a suite can prove it is reported at the authored pointer.\n */\nexport function overBudgetInvocation(\n name: string,\n definition: JsonBlockDefinition,\n seed = 0\n): { invocation: Rec; slot: string; kind: 'words' | 'items' } | undefined {\n const invocation = boundaryInvocation(name, definition, 'max', seed);\n const slots = (invocation.props as Rec).slots as Rec;\n for (const [slotName, slot] of Object.entries(definition.slots)) {\n if (slot.type === 'string' && slot.maxWords !== undefined) {\n slots[slotName] = `${words(slot.maxWords + 1, seed, !slot.oneLine)}`;\n return { invocation, slot: slotName, kind: 'words' };\n }\n }\n for (const [slotName, slot] of Object.entries(definition.slots)) {\n if (\n slot.type === 'array' &&\n slot.maxItems !== undefined &&\n Array.isArray(slots[slotName])\n ) {\n const items = slots[slotName] as unknown[];\n slots[slotName] = [...items, items[0]];\n return { invocation, slot: slotName, kind: 'items' };\n }\n }\n return undefined;\n}\n\n/** Every definition a template embeds, with the template's own example of it. */\nexport function enumerateBlockDefinitions(\n template: unknown,\n templateName: string\n): BlockMatrixDefinition[] {\n const definitions = readBlockDefinitions(template);\n const examples = new Map<string, Rec>();\n const walk = (node: unknown): void => {\n if (Array.isArray(node)) {\n node.forEach(walk);\n return;\n }\n if (!isRecord(node)) return;\n const props = node.props;\n if (\n node.name === 'block' &&\n isRecord(props) &&\n typeof props.ref === 'string' &&\n !examples.has(props.ref)\n ) {\n examples.set(props.ref, isRecord(props.slots) ? props.slots : {});\n }\n walk(node.children);\n };\n walk(isRecord(template) ? template.children : undefined);\n return Object.entries(definitions).map(([name, definition]) => ({\n name,\n template: templateName,\n pointer: `/props/blocks/${name.replace(/~/g, '~0').replace(/\\//g, '~1')}`,\n definition,\n ...(examples.has(name) && { example: examples.get(name) }),\n }));\n}\n\n/** The block names a report's chrome is made of, when the template has them. */\nconst CHROME = ['cover', 'running-head', 'section-opener'] as const;\n\nfunction nominal(\n entry: BlockMatrixDefinition | undefined,\n name: string,\n definitions: Record<string, JsonBlockDefinition>\n): Rec | undefined {\n if (!entry) return undefined;\n if (entry.example) {\n return {\n name: 'block',\n props: { ref: name, slots: structuredClone(entry.example) },\n };\n }\n return boundaryInvocation(name, definitions[name], 'min');\n}\n\n/** The template's own image slot values point at files; the matrix inlines. */\nfunction inlineImages(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(inlineImages);\n if (!isRecord(value)) return value;\n if (\n value.name === 'image' &&\n isRecord(value.props) &&\n typeof value.props.path === 'string'\n ) {\n const props: Rec = { ...value.props, base64: MATRIX_IMAGE };\n delete props.path;\n return { ...value, props };\n }\n if (value.name === 'highcharts') return componentValue('chart', 'min', 0);\n return Object.fromEntries(\n Object.entries(value).map(([k, v]) => [k, inlineImages(v)])\n );\n}\n\nfunction withDefinitions(\n definitions: Record<string, JsonBlockDefinition>,\n names: Iterable<string>\n): Record<string, JsonBlockDefinition> {\n const out: Record<string, JsonBlockDefinition> = {};\n for (const name of names) {\n if (!(name in definitions)) continue;\n out[name] = definitions[name];\n for (const dependency of blockDependencies(definitions, name)) {\n if (dependency in definitions) out[dependency] = definitions[dependency];\n }\n }\n return out;\n}\n\nfunction conditions(\n props: Rec,\n children: unknown[],\n theme: string,\n font: MatrixFont,\n canvas: MatrixCanvas\n): Rec {\n // Every section takes the canvas, keeping whatever else its own page\n // override says (a template section can state margins of its own).\n const sections = children.map((child) => {\n if (!isRecord(child) || child.name !== 'section') return child;\n const props = isRecord(child.props) ? child.props : {};\n const page = isRecord(props.page) ? props.page : {};\n return { ...child, props: { ...props, page: { ...page, size: canvas } } };\n });\n // A native chart is drawn by the office-open renderer only.\n const chart = JSON.stringify(sections).includes('\"name\":\"chart\"');\n return {\n name: 'docx',\n ...(chart && { renderer: 'office-open' }),\n props: {\n ...props,\n theme,\n ...(font === 'fallback' && { themeOverrides: { fonts: FALLBACK_FONTS } }),\n },\n children: sections,\n };\n}\n\n// A seed no slot offset reaches (offsets are sums of slot-name lengths):\n// body copy must not read as the prefix of any slot's text, or the matcher\n// could map a dropped title onto the paragraph after it.\nconst BODY_COPY = words(40, 1000, true);\n\n/**\n * A small report with one block at its edge: the chrome at the template's\n * nominal values (or at the edge, when the chrome block is the one under\n * test), the block in the first body section, body copy after it.\n */\nexport function blockCaseDocument(\n entries: readonly BlockMatrixDefinition[],\n name: string,\n edge: MatrixEdge,\n theme: string,\n { font = 'design', canvas = 'A4', requiredRoles = [] }: CaseConditions = {}\n): Rec {\n const definitions = Object.fromEntries(\n entries.map((e) => [e.name, e.definition])\n );\n const byName = new Map(entries.map((e) => [e.name, e]));\n const under = (chrome: string): Rec | undefined =>\n chrome === name\n ? boundaryInvocation(name, definitions[name], edge, 0, requiredRoles)\n : (inlineImages(nominal(byName.get(chrome), chrome, definitions)) as\n | Rec\n | undefined);\n const cover = under('cover');\n const runningHead = under('running-head');\n const opener = under('section-opener');\n const body: unknown[] = [];\n if (!CHROME.includes(name as (typeof CHROME)[number])) {\n // A notes block collects sources: give it a figure to collect from.\n if (name === 'footnotes' && byName.has('figure')) {\n body.push(\n inlineImages(nominal(byName.get('figure'), 'figure', definitions))\n );\n }\n body.push(\n boundaryInvocation(name, definitions[name], edge, 0, requiredRoles)\n );\n }\n body.push({ name: 'paragraph', props: { text: BODY_COPY } });\n const used = new Set<string>([\n name,\n ...CHROME,\n ...(name === 'footnotes' ? ['figure'] : []),\n ]);\n const children: unknown[] = [];\n if (cover) children.push({ name: 'section', children: [cover] });\n children.push({\n name: 'section',\n children: [runningHead, opener, ...body].filter((c) => c !== undefined),\n });\n return conditions(\n {\n metadata: {\n title: 'Boundary report',\n author: 'jto-ops block matrix',\n company: 'Example client',\n date: 'September 2026',\n },\n blocks: withDefinitions(definitions, used),\n },\n children,\n theme,\n font,\n canvas\n );\n}\n\n/** The whole template with every invocation at the edge. */\nexport function reportCaseDocument(\n template: unknown,\n edge: MatrixEdge,\n theme: string,\n { font = 'design', canvas = 'A4', requiredRoles = [] }: CaseConditions = {}\n): Rec {\n if (!isRecord(template) || !isRecord(template.props))\n throw new Error('A template is a document with props.');\n const definitions = readBlockDefinitions(template);\n let seed = 0;\n const rewrite = (node: unknown): unknown => {\n if (Array.isArray(node)) return node.map(rewrite);\n if (!isRecord(node)) return node;\n const props = node.props;\n if (\n node.name === 'block' &&\n isRecord(props) &&\n typeof props.ref === 'string' &&\n definitions[props.ref]\n ) {\n seed += 7;\n return boundaryInvocation(\n props.ref,\n definitions[props.ref],\n edge,\n seed,\n requiredRoles\n );\n }\n if (\n node.name === 'paragraph' &&\n isRecord(props) &&\n typeof props.text === 'string'\n ) {\n return {\n ...node,\n props: { ...props, text: edge === 'max' ? BODY_COPY : props.text },\n };\n }\n return Array.isArray(node.children)\n ? { ...node, children: rewrite(node.children) }\n : node;\n };\n const props: Rec = structuredClone(template.props);\n delete props.theme;\n delete props.themeOverrides;\n return conditions(\n props,\n rewrite(template.children) as unknown[],\n theme,\n font,\n canvas\n );\n}\n\n/** Every case the options span, in a stable order. */\nexport function generateBlockMatrix(\n template: unknown,\n templateName: string,\n options: BlockMatrixOptions\n): BlockMatrixCase[] {\n const entries = enumerateBlockDefinitions(template, templateName);\n const fonts = options.fonts ?? ['design', 'fallback'];\n const edges = options.edges ?? ['min', 'max'];\n const canvases = options.canvases ?? ['A4'];\n const cases: BlockMatrixCase[] = [];\n for (const theme of options.themes)\n for (const font of fonts)\n for (const canvas of canvases)\n for (const edge of edges) {\n if (options.blocks !== false)\n for (const entry of entries)\n cases.push({\n id: `${entry.name}@${edge}/${theme}/${font}/${canvas}`,\n block: entry.name,\n edge,\n theme,\n font,\n canvas,\n document: blockCaseDocument(entries, entry.name, edge, theme, {\n font,\n canvas,\n requiredRoles: options.requiredRoles,\n }),\n });\n if (options.report !== false)\n cases.push({\n id: `report@${edge}/${theme}/${font}/${canvas}`,\n block: 'report',\n edge,\n theme,\n font,\n canvas,\n document: reportCaseDocument(template, edge, theme, {\n font,\n canvas,\n requiredRoles: options.requiredRoles,\n }),\n });\n }\n return cases;\n}\n"],"mappings":"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","names":["path","fs","fs","path","fs","path","synthesizeFamilyName","rewriteFontFamilyName","path","fs","synthesizeFamilyName","rewriteFontFamilyName","fs","path","synthesizeFamilyName","rewriteFontFamilyName","path","fs","synthesizeFamilyName","rewriteFontFamilyName","resolve","path","fs","cached","execFile","path","words","resolve","execFile","fs","os","path","pdftoppmCandidates","run","resolve","pdftoppmPromise","resolvePdftoppm","execFile","path","run","resolve","words","path","cached","words","worst","occurrence","path","path","digits","_","props"]}
1
+ {"version":3,"sources":["../src/format-adapter.ts","../src/pptx-rasterizer.ts","../src/font-staging/noop-stager.ts","../src/font-staging/fontconfig-stager.ts","../src/font-staging/types.ts","../src/font-staging/windows-stager.ts","../src/font-staging/macos-stager.ts","../src/diagnostics.ts","../src/font-staging/index.ts","../src/pdf-text-geometry.ts","../src/pdf-page-ink.ts","../src/pdf-fonts.ts","../src/rendered-analysis.ts","../src/rendered-rules.ts","../src/rendered-text-match.ts","../src/rendered-inventory.ts","../src/block-matrix.ts","../src/matrix-inventory.ts"],"sourcesContent":["import * as path from 'path';\nimport * as fs from 'fs';\n\nimport type {\n ServicesConfig,\n FontRuntimeOpts,\n PptxRasterizer,\n PptxBatchRasterizer,\n GenerationWarning,\n RendererStatus,\n} from '@json-to-office/shared';\nimport type {\n PreparedDocument,\n QualityAnalysis,\n QualityPolicy,\n QualityProfile,\n} from '@json-to-office/quality';\nimport { validatePresentationDocument } from '@json-to-office/shared-pptx';\nimport { validate as validateDocx } from '@json-to-office/shared-docx';\nimport {\n createLibreOfficePptxRasterizer,\n createLibreOfficePptxBatchRasterizer,\n} from './pptx-rasterizer.js';\nimport { emitDiagnostic } from './diagnostics.js';\n\n/** Forward structured warnings collected during generation to the host's sink. */\nfunction emitGenerationWarnings(warnings: GenerationWarning[]): void {\n for (const warning of warnings) {\n emitDiagnostic(\n `${warning.component}: ${warning.message}`,\n warning.severity === 'info' ? 'info' : 'warning'\n );\n }\n}\n\n/**\n * Normalize a core's warning array into the single client/CLI-facing shape.\n *\n * DOCX cores already emit `GenerationWarning`; PPTX cores emit\n * `PipelineWarning = {code, message, component?, slide?}` — no `severity` and\n * an optional `component`. Left raw, a PipelineWarning renders as an empty\n * component chip in the playground's WarningsPanel, so both shapes funnel\n * through here and come out self-describing.\n */\nfunction toGenerationWarnings(\n raw: readonly any[] | null | undefined\n): GenerationWarning[] {\n return (raw ?? []).map((w) => ({\n component: w?.component ?? 'pptx',\n message: String(w?.message ?? ''),\n severity: (w?.severity === 'info' ? 'info' : 'warning') as\n | 'warning'\n | 'info',\n context: {\n ...(w?.context && typeof w.context === 'object' ? w.context : {}),\n ...(w?.code !== undefined && { code: w.code }),\n ...(w?.slide !== undefined && { slide: w.slide }),\n },\n }));\n}\n\nfunction preparedThemeLabel(prepared: PreparedDocument): string | undefined {\n const label = prepared.metadata?.themeLabel;\n return typeof label === 'string' ? label : undefined;\n}\n\n/**\n * The document a prepared model was built from, and the warnings its\n * preparation already reported.\n *\n * Symbols, so neither crosses a serialization boundary: `PreparedDocument` is\n * a serializable contract, and a model that arrives without its source is\n * re-prepared rather than rendered in place of the document it was handed.\n */\nconst PREPARED_SOURCE = Symbol('jto.prepared.source');\nconst PREPARED_WARNINGS = Symbol('jto.prepared.warnings');\n\ninterface PreparedInternals {\n [PREPARED_SOURCE]?: unknown;\n [PREPARED_WARNINGS]?: readonly GenerationWarning[];\n}\n\n/** Bind a prepared model to the document and the warnings it came from. */\nfunction stampPrepared<T extends PreparedDocument>(\n prepared: T,\n source: unknown,\n warnings: readonly GenerationWarning[]\n): T {\n return Object.assign(prepared, {\n [PREPARED_SOURCE]: source,\n [PREPARED_WARNINGS]: warnings,\n });\n}\n\n/**\n * A prepared model, but only for the document it was prepared from.\n *\n * `generateBuffer(document)` is a per-document API while `prepared` is fixed\n * when the generator is built, so any other document would be validated as\n * itself and then rendered as the prepared one.\n */\nfunction preparedFor<T extends PreparedDocument>(\n prepared: T | undefined,\n document: unknown\n): T | undefined {\n if (!prepared) return undefined;\n const source = (prepared as PreparedInternals)[PREPARED_SOURCE];\n return source !== undefined && source === document ? prepared : undefined;\n}\n\n/** Warning identity, for the prepare/render overlap only. */\nfunction warningKey(warning: GenerationWarning): string {\n return `${warning.component}|${warning.context?.code ?? ''}|${warning.message}`;\n}\n\n/**\n * Drop render warnings this model's preparation already reported.\n *\n * Preparation repeats work the render does again — pptx block, layout and grid\n * resolution above all — so reusing a prepared model would report those\n * warnings twice. Deliberately narrow: nothing is deduplicated except against\n * what preparing this very model emitted.\n */\nfunction withoutPreparedWarnings(\n warnings: GenerationWarning[],\n prepared: PreparedDocument | undefined\n): GenerationWarning[] {\n const emitted = (prepared as PreparedInternals | undefined)?.[\n PREPARED_WARNINGS\n ];\n if (!emitted || emitted.length === 0) return warnings;\n const seen = new Set(emitted.map(warningKey));\n return warnings.filter((warning) => !seen.has(warningKey(warning)));\n}\n\n/**\n * The renderer the document itself names. Core generation resolves\n * `options.renderer ?? document.renderer`, so preparation that reads only the\n * option stamps the default onto the model and misjudges a profile targeted\n * at the backend the document actually renders with.\n */\nfunction documentRenderer(document: unknown): string | undefined {\n const renderer = (document as { renderer?: unknown } | null | undefined)\n ?.renderer;\n return typeof renderer === 'string' ? renderer : undefined;\n}\n\nconst UNSAFE_KEYS = new Set(['__proto__', 'constructor', 'prototype']);\nfunction safeThemeKey(name: string | undefined): string {\n return name && !UNSAFE_KEYS.has(name) ? name : 'custom';\n}\n\n/** Key the theme named by `--theme`/`--theme-path` is registered under. */\nconst CLI_THEME_KEY = 'jto-cli-theme';\n\n/**\n * Point a document at the explicitly requested theme. The JSON path selects a\n * theme by name off `props.theme`, so `--theme`/`--theme-path` is applied by\n * registering the resolved theme under a reserved key and rewriting the\n * reference — an explicit theme wins over the document's own `props.theme`.\n * With no theme requested the document is passed through untouched.\n */\nfunction withRequestedTheme(\n document: any,\n theme: any | undefined,\n customThemes: Record<string, any> | undefined\n): { document: any; customThemes: Record<string, any> | undefined } {\n if (!theme || typeof document !== 'object' || document === null) {\n return { document, customThemes };\n }\n return {\n document: {\n ...document,\n props: { ...document.props, theme: CLI_THEME_KEY },\n },\n customThemes: { ...customThemes, [CLI_THEME_KEY]: theme },\n };\n}\n\nexport type FormatName = 'docx' | 'pptx';\n\nfunction buildServicesFromEnv(): ServicesConfig | undefined {\n const serverUrl = process.env.HIGHCHARTS_SERVER_URL;\n const apiKey = process.env.HIGHCHARTS_API_KEY;\n const apiKeyHeader = process.env.HIGHCHARTS_API_KEY_HEADER ?? 'x-api-key';\n // A public export server receives every chart's data; the switch that\n // permits it is deliberate and separate from naming the URL.\n const allowRemote = /^(1|true|yes)$/i.test(\n process.env.HIGHCHARTS_ALLOW_REMOTE ?? ''\n );\n\n if (!serverUrl && !apiKey && !allowRemote) return undefined;\n\n return {\n highcharts: {\n serverUrl,\n ...(apiKey && { headers: { [apiKeyHeader]: apiKey } }),\n ...(allowRemote && { allowRemote }),\n },\n };\n}\n\n// Lazily-constructed LibreOffice rasterizers, shared across docx generations.\n// Constructing them is cheap (no binaries touched); they only spawn\n// LibreOffice when a document actually contains a `visual` component. Single\n// and batch share the same content-addressed disk cache.\nlet cachedRasterizer: PptxRasterizer | undefined;\nfunction getPptxRasterizer(): PptxRasterizer {\n if (!cachedRasterizer) {\n cachedRasterizer = createLibreOfficePptxRasterizer();\n }\n return cachedRasterizer;\n}\nlet cachedBatchRasterizer: PptxBatchRasterizer | undefined;\nfunction getPptxBatchRasterizer(): PptxBatchRasterizer {\n if (!cachedBatchRasterizer) {\n cachedBatchRasterizer = createLibreOfficePptxBatchRasterizer();\n }\n return cachedBatchRasterizer;\n}\n\n/**\n * Services for docx generation: highcharts (from env) plus the pptx rasterizer\n * that backs `visual` components. An explicit HIGHCHARTS-style override is not\n * needed for pptx — a running rasterization server can be pointed at via\n * `services.pptx.serverUrl`, but the default is the in-process LibreOffice\n * renderer.\n */\n/**\n * Services a caller asked for win over the environment, service by service:\n * a test that names an unreachable export server must reach it, whatever\n * `HIGHCHARTS_SERVER_URL` says.\n */\nfunction withRequestedServices(\n base: ServicesConfig | undefined,\n requested: ServicesConfig | undefined\n): ServicesConfig | undefined {\n if (!requested) return base;\n return {\n ...base,\n ...requested,\n ...(base?.highcharts || requested.highcharts\n ? { highcharts: { ...base?.highcharts, ...requested.highcharts } }\n : {}),\n };\n}\n\nfunction buildDocxServices(): ServicesConfig {\n const base = buildServicesFromEnv() ?? {};\n const serverUrl = process.env.JTO_PPTX_RASTERIZER_URL?.trim();\n const apiKey =\n process.env.JTO_PPTX_RASTERIZER_API_KEY || process.env.HIGHCHARTS_API_KEY;\n const apiKeyHeader =\n process.env.JTO_PPTX_RASTERIZER_API_KEY_HEADER ||\n process.env.HIGHCHARTS_API_KEY_HEADER ||\n 'x-api-key';\n return {\n ...base,\n pptx: serverUrl\n ? {\n serverUrl,\n ...(apiKey && { headers: { [apiKeyHeader]: apiKey } }),\n }\n : {\n render: getPptxRasterizer(),\n renderBatch: getPptxBatchRasterizer(),\n },\n };\n}\n\n/** Minimal builder shape shared by DOCX and PPTX generators */\ninterface GeneratorBuilder {\n addComponent(component: any): GeneratorBuilder;\n validate(document: any): {\n valid: boolean;\n errors?: { path: string; message: string }[];\n };\n generateBuffer(\n document: any,\n options?: {\n deterministic?: boolean;\n generatedAt?: string | Date;\n validation?: { allowUnknownFields?: boolean };\n baseDir?: string;\n renderer?: string;\n /** DOCX only; PPTX generators ignore it. */\n svgRasterFallback?: boolean;\n }\n ): Promise<{ buffer: Buffer; warnings: any }>;\n /**\n * Cheap standard-definition path: expansion + normalization only, no\n * rendering (DOCX generators expose it; PPTX ones may not).\n */\n expandStandardDefinition?: (\n document: any,\n options?: { validation?: { allowUnknownFields?: boolean } }\n ) => Promise<{ standardDefinition: any; warnings: any }>;\n}\n\n/**\n * Each format's renderer-id union, read from its core without importing it.\n *\n * `typeof import(...)` is a type query — erased at compile time — so this keeps\n * the ids honest while the cores stay dynamically loaded. `GeneratorOptions`\n * carries a bare string because one options bag serves both formats; these are\n * what it narrows to at each call site, and an id outside the union is rejected\n * by the core's registry with the list of valid ones.\n */\ntype DocxRendererId =\n (typeof import('@json-to-office/core-docx'))['DEFAULT_DOCX_RENDERER_ID'];\ntype PptxRendererId =\n (typeof import('@json-to-office/core-pptx'))['DEFAULT_PPTX_RENDERER_ID'];\n\nexport interface GeneratorOptions {\n theme?: string | any;\n themePath?: string;\n customThemes?: Record<string, any>;\n validation?: {\n strict?: boolean;\n allowUnknownFields?: boolean;\n };\n fonts?: FontRuntimeOpts;\n /**\n * External services for this generation: an export server for\n * `highcharts`, a rasterizer for `visual`. Wins over the environment,\n * service by service.\n */\n services?: ServicesConfig;\n deterministic?: boolean;\n generatedAt?: string | Date;\n /**\n * Directory that relative asset paths in the document resolve against —\n * normally the input document's own directory (#142).\n */\n baseDir?: string;\n /**\n * Backend that turns the compiled document into bytes.\n *\n * Format-specific and validated by the core's renderer registry, which is\n * why this is a bare string here: naming the id union would make this\n * package import both cores statically, and they are loaded on demand.\n * Undefined means the format's default (`docxjs` / `pptxgenjs`).\n */\n renderer?: string;\n /**\n * Rasterize a PNG fallback for each inline SVG. Defaults to true.\n *\n * DOCX only — pptx embeds SVG without a raster twin. Only readers older than\n * Word 2016 draw it, and producing it dominates the render of a document\n * whose artwork is many small SVGs.\n */\n svgRasterFallback?: boolean;\n /**\n * Optional sink for structured generation warnings (FONT_UNRESOLVED and\n * friends). Mirrors core-docx's `JsonGenerationOptions.warnings`, and is the\n * only delivery mechanism that works off the CLI: `emitGenerationWarnings`\n * routes through an AsyncLocalStorage sink that is a no-op on the server.\n *\n * Adapters PUSH into it; they never replace it. Warnings therefore\n * ACCUMULATE across repeated `generateBuffer` calls on one\n * `GeneratorResult` — allocate one array per logical request.\n */\n warnings?: GenerationWarning[];\n /** Design profile and invocation-specific enforcement. */\n quality?: {\n profile?: QualityProfile;\n policy?: QualityPolicy;\n };\n /** Opaque canonical prologue output shared by analysis and rendering. */\n prepared?: PreparedDocument;\n /**\n * Registered code components. Quality preparation expands them the way\n * generation does, so `analyzeQuality` and `prepareDocument` judge what a\n * plugin emits and report it at the invocation the author wrote (#453).\n */\n plugins?: readonly any[];\n}\n\nexport interface GeneratorResult {\n generateBuffer: (document: any) => Promise<Buffer>;\n /**\n * Post-expansion standard JSON tree without any rendering work — no fonts,\n * no layout, no visual rasterization. Present when the underlying generator\n * supports it (plugin-aware DOCX generation does).\n */\n getStandardDefinition?: (config: any) => Promise<any>;\n hasPlugins: boolean;\n pluginNames: string[];\n /**\n * Identity of the theme this generator forces on every document, or\n * undefined when nothing was requested and each document's own `props.theme`\n * decides. Reported by the CLI so the summary names what actually rendered.\n */\n themeLabel?: string;\n}\n\n/** One resolution of `theme`/`themePath`, shared by every consumer of a run. */\ninterface ResolvedThemes {\n /**\n * The theme named by `theme`/`themePath`, or undefined when neither is set\n * or resolves — callers that must not override the document's own\n * `props.theme` depend on that distinction.\n */\n requested: any | undefined;\n /** Themes registered by name for `props.theme` lookups. */\n customThemes: Record<string, any> | undefined;\n /** What to call `requested`; undefined when the document decides. */\n label: string | undefined;\n}\n\nexport interface FormatAdapter {\n name: FormatName;\n extension: string;\n label: string;\n defaultPort: number;\n\n generateBuffer(json: unknown, options: GeneratorOptions): Promise<Buffer>;\n\n createGenerator(\n plugins: any[],\n options: GeneratorOptions\n ): Promise<GeneratorResult>;\n\n parseJson(input: string | object): unknown;\n validateDocument(doc: unknown): { valid: boolean; errors?: any[] };\n\n /**\n * Validate a document that names plugin components.\n *\n * `validateDocument` above knows the standard components and nothing else,\n * so a registered plugin reads to it as `Unknown component \"weather\"` —\n * the same name the schema route offers for completion and the generator\n * expands. The core validators take the registered components, defer those\n * nodes from the standard walk, and check each one's props against the\n * version it resolves to; this is the seam that reaches them.\n *\n * Async because the core that owns them is imported on demand, as\n * `analyzeQuality` does. Callers with no plugins registered should keep\n * using the sync entry point.\n */\n validateDocumentWithPlugins?(\n doc: unknown,\n plugins: any[]\n ): Promise<{ valid: boolean; errors?: any[] }>;\n\n /** Analyze format-specific design quality with profiles, policy, and gate. */\n analyzeQuality?(\n doc: unknown,\n options?: GeneratorOptions\n ): Promise<QualityAnalysis>;\n\n /** Prepare effective values and provenance once for official pipelines. */\n prepareDocument?(\n doc: unknown,\n options?: GeneratorOptions\n ): Promise<PreparedDocument>;\n\n generateSchema(options?: any): any;\n\n getBuiltinThemes(): Record<string, any>;\n /** Full values for ESM hosts; falls back to `getBuiltinThemes` for plugins. */\n getBuiltinThemeValues?(): Promise<Record<string, any>>;\n resolveTheme(options: GeneratorOptions): Promise<any>;\n loadCustomThemes(\n options: GeneratorOptions\n ): Promise<Record<string, any> | undefined>;\n\n /**\n * Renderer ids this format registers, defaults first.\n *\n * Async because the core that owns the registry is imported on demand — the\n * list is read from it rather than repeated here, so the two cannot drift.\n */\n rendererIds(): Promise<readonly string[]>;\n\n /**\n * The same renderers, each with whether its backend loads on this host.\n *\n * `rendererIds` answers \"what is registered\", which is not the same question:\n * a factory only runs when its renderer is selected, so an id says nothing\n * about whether the render behind it will work. Anything that *advertises*\n * renderers should report this instead — otherwise a caller picks one, gets\n * a green light from validation, and fails a call later.\n */\n rendererStatuses(): Promise<readonly RendererStatus[]>;\n\n /** Cumulative visual pre-pass dedupe counters (DOCX only) (#156). */\n getVisualPrepassStats?(): Promise<any>;\n /** Reset per-format cache observability counters (DOCX only). */\n resetCacheStats?(): Promise<void>;\n}\n\nexport class DocxFormatAdapter implements FormatAdapter {\n name: FormatName = 'docx';\n extension = '.docx';\n label = 'document';\n defaultPort = 3003;\n\n async rendererIds(): Promise<readonly string[]> {\n const core = await import('@json-to-office/core-docx');\n return core.docxRendererIds();\n }\n\n async rendererStatuses(): Promise<readonly RendererStatus[]> {\n const core = await import('@json-to-office/core-docx');\n return core.docxRendererStatuses();\n }\n\n async generateBuffer(\n json: unknown,\n options: GeneratorOptions\n ): Promise<Buffer> {\n const core = await import('@json-to-office/core-docx');\n const parsed = typeof json === 'string' ? JSON.parse(json as string) : json;\n const prepared = preparedFor(\n options.prepared?.format === 'docx'\n ? (options.prepared as ReturnType<\n typeof core.prepareDocxQualityDocument\n >)\n : undefined,\n parsed\n );\n let docDefinition: unknown;\n let customThemes: Record<string, any> | undefined;\n if (prepared) {\n docDefinition = prepared.model.authored;\n } else {\n const resolved = await this.resolveThemes(options);\n const normalized = withRequestedTheme(\n parsed,\n resolved.requested,\n resolved.customThemes\n );\n docDefinition = normalized.document;\n customThemes = normalized.customThemes;\n }\n const services = withRequestedServices(\n buildDocxServices(),\n options.services\n );\n // Collect rather than swallow: without a sink, core warnings (an\n // unresolvable `props.theme` among them) never reach the terminal and the\n // render just comes back looking subtly wrong.\n const warnings: GenerationWarning[] = [];\n const buffer = await core.generateBufferFromJson(docDefinition as any, {\n customThemes,\n services,\n fonts: options.fonts,\n validation: {\n allowUnknownFields: options.validation?.allowUnknownFields,\n },\n deterministic: options.deterministic,\n generatedAt: options.generatedAt,\n baseDir: options.baseDir,\n renderer: options.renderer as DocxRendererId | undefined,\n svgRasterFallback: options.svgRasterFallback,\n prepared,\n warnings,\n });\n const emitted = withoutPreparedWarnings(warnings, prepared);\n emitGenerationWarnings(emitted);\n options.warnings?.push(...toGenerationWarnings(emitted));\n return buffer;\n }\n\n async createGenerator(\n plugins: any[],\n options: GeneratorOptions\n ): Promise<GeneratorResult> {\n const core = await import('@json-to-office/core-docx');\n const hasPlugins = plugins.length > 0;\n const pluginNames = plugins.map((p) => p.name);\n const services = withRequestedServices(\n buildDocxServices(),\n options.services\n );\n\n // Resolve once unless the canonical model already carries the result.\n const prepared =\n !hasPlugins && options.prepared?.format === 'docx'\n ? (options.prepared as ReturnType<\n typeof core.prepareDocxQualityDocument\n >)\n : undefined;\n // With plugins the model cannot stand in for theme resolution — the\n // generator is built for the components — but rendering still reuses the\n // expansion it recorded, so the plugins run once for both the quality\n // gate and the bytes.\n const pluginPrepared =\n hasPlugins && options.prepared?.format === 'docx'\n ? (options.prepared as ReturnType<\n typeof core.prepareDocxQualityDocument\n >)\n : undefined;\n const resolved = prepared ? undefined : await this.resolveThemes(options);\n const requestedTheme = resolved?.requested;\n const customThemes = resolved?.customThemes;\n const themeLabel = prepared\n ? preparedThemeLabel(prepared)\n : resolved?.label;\n\n if (!hasPlugins) {\n // A prepared model renders only its own document; any other one falls\n // back to resolving themes here, memoized so a bad `--theme` still\n // warns once per generator.\n let fallbackThemes: Promise<ResolvedThemes> | undefined;\n const themesFor = (): Promise<ResolvedThemes> =>\n (fallbackThemes ??= resolved\n ? Promise.resolve(resolved)\n : this.resolveThemes(options));\n return {\n generateBuffer: async (document: any) => {\n const parsed =\n typeof document === 'string' ? JSON.parse(document) : document;\n const usable = preparedFor(prepared, parsed);\n const themes = usable ? undefined : await themesFor();\n const normalized = usable\n ? { document: usable.model.authored, customThemes: undefined }\n : withRequestedTheme(\n parsed,\n themes?.requested,\n themes?.customThemes\n );\n const warnings: GenerationWarning[] = [];\n const buffer = await core.generateBufferFromJson(\n normalized.document,\n {\n customThemes: normalized.customThemes,\n services,\n fonts: options.fonts,\n validation: {\n allowUnknownFields: options.validation?.allowUnknownFields,\n },\n deterministic: options.deterministic,\n generatedAt: options.generatedAt,\n baseDir: options.baseDir,\n renderer: options.renderer as DocxRendererId | undefined,\n svgRasterFallback: options.svgRasterFallback,\n prepared: usable,\n warnings,\n }\n );\n const emitted = withoutPreparedWarnings(warnings, usable);\n emitGenerationWarnings(emitted);\n options.warnings?.push(...toGenerationWarnings(emitted));\n return buffer;\n },\n hasPlugins: false,\n pluginNames: [],\n themeLabel,\n };\n }\n\n let generator: GeneratorBuilder = core.createDocumentGenerator({\n // Undefined when nothing was requested: a constructor theme beats the\n // generator's own `props.theme` lookup, so forcing one here would render\n // every document in it.\n theme: requestedTheme,\n customThemes: requestedTheme\n ? { ...customThemes, [CLI_THEME_KEY]: requestedTheme }\n : customThemes,\n debug: process.env.DEBUG === 'true',\n services,\n fonts: options.fonts,\n validation: {\n allowUnknownFields: options.validation?.allowUnknownFields,\n },\n deterministic: options.deterministic,\n generatedAt: options.generatedAt,\n baseDir: options.baseDir,\n renderer: options.renderer as DocxRendererId | undefined,\n svgRasterFallback: options.svgRasterFallback,\n });\n\n for (const plugin of plugins) {\n generator = generator.addComponent(plugin);\n }\n\n return {\n generateBuffer: async (document: any) => {\n const parsed =\n typeof document === 'string' ? JSON.parse(document) : document;\n const { document: docDefinition } = withRequestedTheme(\n parsed,\n requestedTheme,\n customThemes\n );\n const usable = preparedFor(pluginPrepared, parsed);\n const result = await generator.generateBuffer(docDefinition, {\n validation: {\n allowUnknownFields: options.validation?.allowUnknownFields,\n },\n deterministic: options.deterministic,\n generatedAt: options.generatedAt,\n baseDir: options.baseDir,\n renderer: options.renderer as DocxRendererId | undefined,\n svgRasterFallback: options.svgRasterFallback,\n ...(usable && { prepared: usable }),\n });\n const emitted = withoutPreparedWarnings(result.warnings ?? [], usable);\n emitGenerationWarnings(emitted);\n options.warnings?.push(...toGenerationWarnings(emitted));\n return result.buffer;\n },\n getStandardDefinition: generator.expandStandardDefinition\n ? async (config: any) => {\n const parsed =\n typeof config === 'string' ? JSON.parse(config) : config;\n const { document: docDefinition } = withRequestedTheme(\n parsed,\n requestedTheme,\n customThemes\n );\n const result = await generator.expandStandardDefinition!(\n docDefinition,\n {\n validation: {\n allowUnknownFields: options.validation?.allowUnknownFields,\n },\n }\n );\n emitGenerationWarnings(result.warnings ?? []);\n return result.standardDefinition;\n }\n : undefined,\n hasPlugins: true,\n pluginNames,\n themeLabel,\n };\n }\n\n parseJson(input: string | object): unknown {\n return typeof input === 'string' ? JSON.parse(input) : input;\n }\n\n validateDocument(doc: unknown): { valid: boolean; errors?: any[] } {\n const result = validateDocx.jsonDocument(doc as object);\n return {\n valid: result.valid,\n ...(result.errors.length > 0 && { errors: result.errors }),\n };\n }\n\n async validateDocumentWithPlugins(\n doc: unknown,\n plugins: any[]\n ): Promise<{ valid: boolean; errors?: any[] }> {\n const core = await import('@json-to-office/core-docx');\n const parsed = typeof doc === 'string' ? JSON.parse(doc) : doc;\n const result = core.validateDocument(parsed as any, plugins);\n const errors = result.errors ?? [];\n return {\n valid: result.valid,\n ...(errors.length > 0 && { errors }),\n };\n }\n\n async analyzeQuality(\n doc: unknown,\n options: GeneratorOptions = {}\n ): Promise<QualityAnalysis> {\n const core = await import('@json-to-office/core-docx');\n const parsed = typeof doc === 'string' ? JSON.parse(doc) : doc;\n let prepared = preparedFor(\n options.prepared?.format === 'docx'\n ? (options.prepared as ReturnType<\n typeof core.prepareDocxQualityDocument\n >)\n : undefined,\n parsed\n );\n if (!prepared) {\n try {\n prepared = (await this.prepareModel(parsed, options, [])) as ReturnType<\n typeof core.prepareDocxQualityDocument\n >;\n } catch {\n // Structural validation owns malformed trees. The core guards its own\n // preparation, so handing it the document instead of a model turns a\n // throw into an analysis that reports the failure.\n }\n }\n return core.analyzeDocxQuality(prepared?.model.authored ?? parsed, {\n ...(prepared && { prepared }),\n renderer: options.renderer ?? documentRenderer(parsed),\n profile: options.quality?.profile,\n policy: options.quality?.policy,\n });\n }\n\n async prepareDocument(\n doc: unknown,\n options: GeneratorOptions = {}\n ): Promise<PreparedDocument> {\n // Preparation resolves the theme context the render then skips, so these\n // warnings reach the host from here or not at all.\n const warnings: GenerationWarning[] = [];\n const prepared = await this.prepareModel(doc, options, warnings);\n emitGenerationWarnings(warnings);\n options.warnings?.push(...toGenerationWarnings(warnings));\n return prepared;\n }\n\n /** Prepare into a caller-owned sink; `prepareDocument` owns the reporting. */\n private async prepareModel(\n doc: unknown,\n options: GeneratorOptions,\n warnings: GenerationWarning[]\n ): Promise<PreparedDocument> {\n const core = await import('@json-to-office/core-docx');\n const parsed = typeof doc === 'string' ? JSON.parse(doc) : doc;\n const resolved = await this.resolveThemes(options);\n const normalized = withRequestedTheme(\n parsed,\n resolved.requested,\n resolved.customThemes\n );\n const prepared =\n options.plugins && options.plugins.length > 0\n ? await (async () => {\n // The generator `createGenerator` builds for these plugins, so\n // the tree analysed is the tree that renders — services and\n // `baseDir` included: a plugin that calls a service or resolves\n // an asset beside the document would otherwise emit one tree here\n // and another at render.\n let generator: any = core.createDocumentGenerator({\n theme: resolved.requested,\n customThemes: resolved.requested\n ? {\n ...resolved.customThemes,\n [CLI_THEME_KEY]: resolved.requested,\n }\n : resolved.customThemes,\n services: withRequestedServices(\n buildDocxServices(),\n options.services\n ),\n baseDir: options.baseDir,\n fonts: options.fonts,\n validation: {\n allowUnknownFields: options.validation?.allowUnknownFields,\n },\n renderer: options.renderer as DocxRendererId | undefined,\n });\n for (const plugin of options.plugins!)\n generator = generator.addComponent(plugin);\n const result = await generator.prepareQuality(normalized.document);\n warnings.push(...(result.warnings ?? []));\n return result.prepared as ReturnType<\n typeof core.prepareDocxQualityDocument\n >;\n })()\n : core.prepareDocxQualityDocument(normalized.document as any, {\n customThemes: normalized.customThemes,\n fonts: options.fonts,\n renderer: options.renderer ?? documentRenderer(parsed),\n warnings,\n });\n return stampPrepared(\n {\n ...prepared,\n metadata: {\n ...prepared.metadata,\n ...(resolved.label && { themeLabel: resolved.label }),\n },\n },\n parsed,\n toGenerationWarnings(warnings)\n );\n }\n\n generateSchema(_options?: any): any {\n // Delegate to shared-docx\n return null;\n }\n\n getBuiltinThemes(): Record<string, any> {\n try {\n const core = require('@json-to-office/core-docx');\n return core.themes || {};\n } catch {\n return {};\n }\n }\n\n async getBuiltinThemeValues(): Promise<Record<string, any>> {\n const core = await import('@json-to-office/core-docx');\n return core.themes || {};\n }\n\n async resolveTheme(options: GeneratorOptions): Promise<any> {\n const core = await import('@json-to-office/core-docx');\n const { requested } = await this.resolveThemes(options);\n return requested ?? (core.themes as any)?.consulting ?? {};\n }\n\n /**\n * Resolve `theme`/`themePath` once for a whole run: `themePath` is read a\n * single time and feeds both the requested theme and the custom-theme\n * registry, so a bad path warns once instead of once per consumer.\n */\n private async resolveThemes(\n options: GeneratorOptions\n ): Promise<ResolvedThemes> {\n const core = await import('@json-to-office/core-docx');\n // Themes passed directly from the client (playground UI) come first.\n const registry: Record<string, any> = { ...options.customThemes };\n\n if (typeof options.theme === 'object' && options.theme !== null) {\n registry[safeThemeKey(options.theme.name)] = options.theme;\n }\n\n let fileTheme: any | undefined;\n if (options.themePath) {\n try {\n if (options.themePath.endsWith('.json')) {\n fileTheme = await core.loadThemeFromFile(options.themePath);\n } else {\n const themePath = path.resolve(process.cwd(), options.themePath);\n const themeModule = await import(themePath);\n fileTheme = themeModule.default || themeModule.theme;\n }\n } catch (error: any) {\n emitDiagnostic(\n `Failed to load theme from ${options.themePath}: ${error.message}`,\n 'warning'\n );\n }\n if (fileTheme) {\n registry[safeThemeKey(fileTheme.name)] = fileTheme;\n }\n }\n\n const customThemes =\n Object.keys(registry).length > 0 ? registry : undefined;\n\n if (fileTheme) {\n return { requested: fileTheme, customThemes, label: options.themePath };\n }\n\n if (typeof options.theme === 'string') {\n const named =\n options.customThemes?.[options.theme] ??\n (core.themes as Record<string, any>)?.[options.theme];\n if (named)\n return { requested: named, customThemes, label: options.theme };\n\n if (options.theme.endsWith('.json') && fs.existsSync(options.theme)) {\n try {\n return {\n requested: await core.loadThemeFromFile(options.theme),\n customThemes,\n label: options.theme,\n };\n } catch {}\n }\n\n try {\n const inline = await core.loadThemeFromJson(options.theme);\n return {\n requested: inline,\n customThemes,\n label: (inline as any)?.name || options.theme,\n };\n } catch {}\n\n emitDiagnostic(\n `Unknown theme \"${options.theme}\"; keeping the document's own theme`,\n 'warning'\n );\n }\n\n if (typeof options.theme === 'object' && options.theme !== null) {\n return {\n requested: options.theme,\n customThemes,\n label: safeThemeKey(options.theme.name),\n };\n }\n\n return { requested: undefined, customThemes, label: undefined };\n }\n\n async loadCustomThemes(\n options: GeneratorOptions\n ): Promise<Record<string, any> | undefined> {\n return (await this.resolveThemes(options)).customThemes;\n }\n\n async getVisualPrepassStats(): Promise<any> {\n try {\n const core = await import('@json-to-office/core-docx');\n return core.getVisualPrepassStats?.() ?? null;\n } catch {\n return null;\n }\n }\n\n async resetCacheStats(): Promise<void> {\n try {\n const core = await import('@json-to-office/core-docx');\n core.resetVisualPrepassStats?.();\n } catch {\n // Resetting observability is best-effort.\n }\n }\n}\n\nexport class PptxFormatAdapter implements FormatAdapter {\n name: FormatName = 'pptx';\n extension = '.pptx';\n label = 'presentation';\n defaultPort = 3004;\n\n async rendererIds(): Promise<readonly string[]> {\n const core = await import('@json-to-office/core-pptx');\n return core.pptxRendererIds();\n }\n\n async rendererStatuses(): Promise<readonly RendererStatus[]> {\n const core = await import('@json-to-office/core-pptx');\n return core.pptxRendererStatuses();\n }\n\n async generateBuffer(\n json: unknown,\n options: GeneratorOptions\n ): Promise<Buffer> {\n const core = await import('@json-to-office/core-pptx');\n const parsed = typeof json === 'string' ? JSON.parse(json as string) : json;\n const prepared = preparedFor(\n options.prepared?.format === 'pptx'\n ? (options.prepared as ReturnType<\n typeof core.preparePptxQualityDocument\n >)\n : undefined,\n parsed\n );\n let docDefinition: unknown;\n let customThemes: Record<string, any> | undefined;\n if (prepared) {\n docDefinition = prepared.model.authored;\n } else {\n const resolved = await this.resolveThemes(options);\n const normalized = withRequestedTheme(\n parsed,\n resolved.requested,\n resolved.customThemes\n );\n docDefinition = normalized.document;\n customThemes = normalized.customThemes;\n }\n const services = withRequestedServices(\n buildServicesFromEnv(),\n options.services\n );\n // The warnings-returning entry point: `generateBufferFromJson` allocates\n // the pipeline's warning array internally and throws it away, so core\n // warnings (FONT_UNRESOLVED among them) never reached the terminal or the\n // server.\n const result = await core.generateBufferWithWarnings(docDefinition as any, {\n customThemes,\n services,\n fonts: options.fonts,\n validation: {\n allowUnknownFields: options.validation?.allowUnknownFields,\n },\n deterministic: options.deterministic,\n generatedAt: options.generatedAt,\n baseDir: options.baseDir,\n renderer: options.renderer as PptxRendererId | undefined,\n prepared,\n });\n const emitted = withoutPreparedWarnings(\n toGenerationWarnings(result.warnings),\n prepared\n );\n emitGenerationWarnings(emitted);\n options.warnings?.push(...emitted);\n return result.buffer;\n }\n\n async createGenerator(\n plugins: any[],\n options: GeneratorOptions\n ): Promise<GeneratorResult> {\n const core = await import('@json-to-office/core-pptx');\n const hasPlugins = plugins.length > 0;\n const pluginNames = plugins.map((p) => p.name);\n const services = withRequestedServices(\n buildServicesFromEnv(),\n options.services\n );\n\n // Resolve once unless the canonical model already carries the result.\n const prepared =\n !hasPlugins && options.prepared?.format === 'pptx'\n ? (options.prepared as ReturnType<\n typeof core.preparePptxQualityDocument\n >)\n : undefined;\n // As in the docx adapter: with plugins the generator is built for the\n // components, but the render still reuses the expansion the prepared\n // model recorded, so the plugins draw once for analysis and bytes both.\n const pluginPrepared =\n hasPlugins && options.prepared?.format === 'pptx'\n ? (options.prepared as ReturnType<\n typeof core.preparePptxQualityDocument\n >)\n : undefined;\n const resolved = prepared ? undefined : await this.resolveThemes(options);\n const requestedTheme = resolved?.requested;\n const customThemes = resolved?.customThemes;\n const themeLabel = prepared\n ? preparedThemeLabel(prepared)\n : resolved?.label;\n\n if (!hasPlugins) {\n // A prepared model renders only its own document; any other one falls\n // back to resolving themes here, memoized so a bad `--theme` still\n // warns once per generator.\n let fallbackThemes: Promise<ResolvedThemes> | undefined;\n const themesFor = (): Promise<ResolvedThemes> =>\n (fallbackThemes ??= resolved\n ? Promise.resolve(resolved)\n : this.resolveThemes(options));\n return {\n generateBuffer: async (document: any) => {\n const parsed =\n typeof document === 'string' ? JSON.parse(document) : document;\n const usable = preparedFor(prepared, parsed);\n const themes = usable ? undefined : await themesFor();\n const normalized = usable\n ? { document: usable.model.authored, customThemes: undefined }\n : withRequestedTheme(\n parsed,\n themes?.requested,\n themes?.customThemes\n );\n const result = await core.generateBufferWithWarnings(\n normalized.document,\n {\n customThemes: normalized.customThemes,\n services,\n fonts: options.fonts,\n validation: {\n allowUnknownFields: options.validation?.allowUnknownFields,\n },\n deterministic: options.deterministic,\n generatedAt: options.generatedAt,\n baseDir: options.baseDir,\n renderer: options.renderer as PptxRendererId | undefined,\n prepared: usable,\n }\n );\n const warnings = withoutPreparedWarnings(\n toGenerationWarnings(result.warnings),\n usable\n );\n emitGenerationWarnings(warnings);\n options.warnings?.push(...warnings);\n return result.buffer;\n },\n hasPlugins: false,\n pluginNames: [],\n themeLabel,\n };\n }\n\n let generator: GeneratorBuilder = core.createPresentationGenerator({\n // Undefined when nothing was requested: a constructor theme beats the\n // generator's own `props.theme` lookup, so forcing one here would render\n // every document in it.\n theme: requestedTheme,\n customThemes: requestedTheme\n ? { ...customThemes, [CLI_THEME_KEY]: requestedTheme }\n : customThemes,\n debug: process.env.DEBUG === 'true',\n services,\n fonts: options.fonts,\n validation: {\n allowUnknownFields: options.validation?.allowUnknownFields,\n },\n deterministic: options.deterministic,\n generatedAt: options.generatedAt,\n baseDir: options.baseDir,\n renderer: options.renderer as PptxRendererId | undefined,\n });\n\n for (const plugin of plugins) {\n generator = generator.addComponent(plugin);\n }\n\n return {\n generateBuffer: async (document: any) => {\n const parsed =\n typeof document === 'string' ? JSON.parse(document) : document;\n const { document: docDefinition } = withRequestedTheme(\n parsed,\n requestedTheme,\n customThemes\n );\n const usable = preparedFor(pluginPrepared, parsed);\n const result = await generator.generateBuffer(docDefinition, {\n validation: {\n allowUnknownFields: options.validation?.allowUnknownFields,\n },\n deterministic: options.deterministic,\n generatedAt: options.generatedAt,\n baseDir: options.baseDir,\n renderer: options.renderer as PptxRendererId | undefined,\n ...(usable && { prepared: usable }),\n });\n const normalized = withoutPreparedWarnings(\n toGenerationWarnings(result.warnings),\n usable\n );\n emitGenerationWarnings(normalized);\n options.warnings?.push(...normalized);\n return result.buffer;\n },\n hasPlugins: true,\n pluginNames,\n themeLabel,\n };\n }\n\n parseJson(input: string | object): unknown {\n return typeof input === 'string' ? JSON.parse(input) : input;\n }\n\n validateDocument(doc: unknown): { valid: boolean; errors?: any[] } {\n const result = validatePresentationDocument(doc);\n return {\n valid: result.valid,\n ...(result.errors.length > 0 && { errors: result.errors }),\n };\n }\n\n async validateDocumentWithPlugins(\n doc: unknown,\n plugins: any[]\n ): Promise<{ valid: boolean; errors?: any[] }> {\n const core = await import('@json-to-office/core-pptx');\n const parsed = typeof doc === 'string' ? JSON.parse(doc) : doc;\n const result = core.validatePresentation(parsed as any, plugins);\n return {\n valid: result.valid,\n ...(result.errors.length > 0 && { errors: result.errors }),\n };\n }\n\n async analyzeQuality(\n doc: unknown,\n options: GeneratorOptions = {}\n ): Promise<QualityAnalysis> {\n const core = await import('@json-to-office/core-pptx');\n const parsed = typeof doc === 'string' ? JSON.parse(doc) : doc;\n let prepared = preparedFor(\n options.prepared?.format === 'pptx'\n ? (options.prepared as ReturnType<\n typeof core.preparePptxQualityDocument\n >)\n : undefined,\n parsed\n );\n if (!prepared) {\n try {\n prepared = (await this.prepareModel(parsed, options, [])) as ReturnType<\n typeof core.preparePptxQualityDocument\n >;\n } catch {\n // Structural validation owns malformed trees. The core guards its own\n // preparation, so handing it the document instead of a model turns a\n // throw into an analysis that reports the failure.\n }\n }\n return core.analyzePptxQuality(prepared?.model.authored ?? parsed, {\n ...(prepared && { prepared }),\n renderer: options.renderer ?? documentRenderer(parsed),\n profile: options.quality?.profile,\n policy: options.quality?.policy,\n });\n }\n\n async prepareDocument(\n doc: unknown,\n options: GeneratorOptions = {}\n ): Promise<PreparedDocument> {\n // Preparation resolves the theme context the render then skips, so these\n // warnings reach the host from here or not at all.\n const warnings: any[] = [];\n const prepared = await this.prepareModel(doc, options, warnings);\n const normalized = toGenerationWarnings(warnings);\n emitGenerationWarnings(normalized);\n options.warnings?.push(...normalized);\n return prepared;\n }\n\n /** Prepare into a caller-owned sink; `prepareDocument` owns the reporting. */\n private async prepareModel(\n doc: unknown,\n options: GeneratorOptions,\n warnings: any[]\n ): Promise<PreparedDocument> {\n const core = await import('@json-to-office/core-pptx');\n const parsed = typeof doc === 'string' ? JSON.parse(doc) : doc;\n const resolved = await this.resolveThemes(options);\n const normalized = withRequestedTheme(\n parsed,\n resolved.requested,\n resolved.customThemes\n );\n const services = withRequestedServices(\n buildServicesFromEnv(),\n options.services\n );\n const prepared =\n options.plugins && options.plugins.length > 0\n ? await (async () => {\n // The generator `createGenerator` builds for these plugins, so\n // the tree analysed is the tree that renders.\n let generator: any = core.createPresentationGenerator({\n theme: resolved.requested,\n customThemes: resolved.requested\n ? {\n ...resolved.customThemes,\n [CLI_THEME_KEY]: resolved.requested,\n }\n : resolved.customThemes,\n services,\n baseDir: options.baseDir,\n fonts: options.fonts,\n validation: {\n allowUnknownFields: options.validation?.allowUnknownFields,\n },\n renderer: options.renderer as PptxRendererId | undefined,\n });\n for (const plugin of options.plugins!)\n generator = generator.addComponent(plugin);\n const result = await generator.prepareQuality(normalized.document);\n warnings.push(...(result.warnings ?? []));\n return result.prepared as ReturnType<\n typeof core.preparePptxQualityDocument\n >;\n })()\n : core.preparePptxQualityDocument(normalized.document as any, {\n customThemes: normalized.customThemes,\n fonts: options.fonts,\n services,\n renderer: options.renderer ?? documentRenderer(parsed),\n warnings,\n });\n return stampPrepared(\n {\n ...prepared,\n metadata: {\n ...prepared.metadata,\n ...(resolved.label && { themeLabel: resolved.label }),\n },\n },\n parsed,\n toGenerationWarnings(warnings)\n );\n }\n\n generateSchema(_options?: any): any {\n return null;\n }\n\n getBuiltinThemes(): Record<string, any> {\n try {\n const core = require('@json-to-office/core-pptx');\n return core.pptxThemes || {};\n } catch {\n return {};\n }\n }\n\n async getBuiltinThemeValues(): Promise<Record<string, any>> {\n const core = await import('@json-to-office/core-pptx');\n return core.pptxThemes || {};\n }\n\n async resolveTheme(options: GeneratorOptions): Promise<any> {\n const core = await import('@json-to-office/core-pptx');\n const themes = (core as any).pptxThemes || {};\n const { requested } = await this.resolveThemes(options);\n return requested ?? themes.consulting ?? {};\n }\n\n /**\n * Resolve `theme`/`themePath` once for a whole run: `themePath` is read a\n * single time and feeds both the requested theme and the custom-theme\n * registry, so a bad path warns once instead of once per consumer.\n */\n private async resolveThemes(\n options: GeneratorOptions\n ): Promise<ResolvedThemes> {\n const core = await import('@json-to-office/core-pptx');\n const themes = (core as any).pptxThemes || {};\n // Themes passed directly from the client (playground UI) come first.\n const registry: Record<string, any> = { ...options.customThemes };\n\n if (typeof options.theme === 'object' && options.theme !== null) {\n registry[safeThemeKey(options.theme.name)] = options.theme;\n }\n\n let fileTheme: any | undefined;\n if (options.themePath) {\n try {\n if (options.themePath.endsWith('.json')) {\n const content = fs.readFileSync(\n path.resolve(process.cwd(), options.themePath),\n 'utf-8'\n );\n // Validated, not merely parsed. The DOCX branch has always gone\n // through `loadThemeFromFile`; this one used to hand whatever JSON\n // it found straight to the compiler, which reads\n // `theme.defaults.fontSize` unguarded — so a theme with the wrong\n // shape surfaced as a TypeError in the IR rather than as a\n // diagnostic naming the bad field.\n const shared = await import('@json-to-office/shared-pptx');\n const checked = shared.validatePptxTheme(JSON.parse(content));\n if (!checked.valid) {\n const detail = checked.errors\n .slice(0, 3)\n .map((error: { path?: string; message: string }) =>\n error.path ? `${error.path}: ${error.message}` : error.message\n )\n .join('; ');\n throw new Error(\n `not a valid pptx theme — ${detail}${\n checked.errors.length > 3\n ? ` (and ${checked.errors.length - 3} more)`\n : ''\n }`\n );\n }\n fileTheme = checked.data;\n } else {\n const themePath = path.resolve(process.cwd(), options.themePath);\n const themeModule = await import(themePath);\n fileTheme = themeModule.default || themeModule.theme;\n }\n } catch (error: any) {\n emitDiagnostic(\n `Failed to load theme from ${options.themePath}: ${error.message}`,\n 'warning'\n );\n }\n if (fileTheme) {\n registry[safeThemeKey(fileTheme.name)] = fileTheme;\n }\n }\n\n const customThemes =\n Object.keys(registry).length > 0 ? registry : undefined;\n\n if (fileTheme) {\n return { requested: fileTheme, customThemes, label: options.themePath };\n }\n\n if (typeof options.theme === 'string') {\n // Deliberately not getPptxTheme(): it answers every unknown name with\n // the default theme, which would silently swap a typo'd `--theme` in\n // over the document's own.\n const named =\n options.customThemes?.[options.theme] ?? themes[options.theme];\n if (named)\n return { requested: named, customThemes, label: options.theme };\n\n if (options.theme.endsWith('.json') && fs.existsSync(options.theme)) {\n try {\n const content = fs.readFileSync(\n path.resolve(process.cwd(), options.theme),\n 'utf-8'\n );\n return {\n requested: JSON.parse(content),\n customThemes,\n label: options.theme,\n };\n } catch {}\n }\n\n emitDiagnostic(\n `Unknown theme \"${options.theme}\"; keeping the document's own theme`,\n 'warning'\n );\n }\n\n if (typeof options.theme === 'object' && options.theme !== null) {\n return {\n requested: options.theme,\n customThemes,\n label: safeThemeKey(options.theme.name),\n };\n }\n\n return { requested: undefined, customThemes, label: undefined };\n }\n\n async loadCustomThemes(\n options: GeneratorOptions\n ): Promise<Record<string, any> | undefined> {\n return (await this.resolveThemes(options)).customThemes;\n }\n}\n\nexport function createAdapter(format: FormatName): FormatAdapter {\n switch (format) {\n case 'docx':\n return new DocxFormatAdapter();\n case 'pptx':\n return new PptxFormatAdapter();\n default:\n throw new Error(`Unknown format: ${format}`);\n }\n}\n","/**\n * PPTX rasterizer — the concrete service backing docx `visual` components.\n *\n * Pipeline: presentation JSON → (core-pptx) .pptx → (LibreOffice) PDF →\n * (poppler/pdftoppm) PNG. Returns a base64 data URI plus the natural pixel\n * dimensions. Results are content-addressed and cached on disk so repeated\n * builds of an unchanged visual skip the (multi-second) LibreOffice run.\n *\n * Single and batch rasterization share one engine (#153). A batch keeps one\n * .pptx per slide and converts them all in a single `soffice` launch — the\n * launch is the dominant cost, and per-file conversion keeps slides fully\n * independent: each has its own PDF and PNG (no page↔slide index mapping),\n * its own dpi, and a cache key identical to the single-slide path, so both\n * paths share the same disk cache.\n *\n * A request may carry `fonts`: base64 font faces that are staged for the\n * soffice launches (via the shared FontStager pipeline) so the slide renders\n * with the document's real families instead of whatever the host happens to\n * have installed. Those fonts are part of the disk-cache key — the same\n * slide is genuinely different pixels with and without them, and the cache\n * is shared process-wide across callers.\n *\n * Every engine run works against a wall-clock deadline (one batch-scaled\n * soffice window plus one pdftoppm window) so a wedged conversion fails the\n * remaining slides quickly instead of holding the caller — and its\n * concurrency slot — for minutes.\n *\n * This is injected via `services.pptx.render` / `services.pptx.renderBatch`;\n * the published engine packages never depend on these binaries.\n */\n\nimport { execFile } from 'node:child_process';\nimport { promises as fs } from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport crypto from 'node:crypto';\nimport {\n DEFAULT_VISUAL_DPI,\n type PptxRasterizer,\n type PptxBatchRasterizer,\n type PptxRasterizeRequest,\n type PptxRasterizeResult,\n type PptxRasterizeBatchSlideResult,\n type PptxRasterizeFailureStage,\n type RasterizeFontFace,\n} from '@json-to-office/shared';\nimport { fromRasterizeFontFaces } from '@json-to-office/shared/fonts/node';\nimport { getFontStager } from './font-staging/index.js';\nimport type { FontStageHandle } from './font-staging/index.js';\n\nconst SOFFICE_TIMEOUT_MS = 60000;\n/** Extra soffice budget per additional slide in a batch launch. */\nconst SOFFICE_BATCH_EXTRA_PER_SLIDE_MS = 15000;\n/** Hard ceiling for one batch soffice launch. */\nconst SOFFICE_BATCH_TIMEOUT_CAP_MS = 300000;\n/**\n * Max isolated single-file launches after a batch launch left PDFs missing.\n * Covers the realistic case (one poisoned slide crashed the batch; its\n * successors are fine) without letting a broken environment turn one request\n * into dozens of sequential 60s timeouts.\n */\nconst MAX_ISOLATED_RETRIES = 3;\nconst PDFTOPPM_TIMEOUT_MS = 30000;\nconst PROBE_TIMEOUT_MS = 5000;\nconst MAX_BUFFER = 64 * 1024 * 1024;\n\nfunction exec(\n binary: string,\n args: string[],\n timeoutMs: number,\n /**\n * Extra env for the child, merged over `process.env`. Carries a font\n * stager's `envOverrides` (FONTCONFIG_FILE / JTO_FONT_PATHS /\n * SAL_DISABLE_SKIA). Only the soffice CONVERSION launches get it: the\n * memoized `--version` probe must not have its resolution polluted, and\n * pdftoppm has no use for it.\n */\n env?: Record<string, string>\n): Promise<void> {\n return new Promise((resolve, reject) => {\n execFile(\n binary,\n args,\n {\n timeout: timeoutMs,\n maxBuffer: MAX_BUFFER,\n windowsHide: true,\n env: env ? { ...process.env, ...env } : process.env,\n },\n (error) => (error ? reject(error) : resolve())\n );\n });\n}\n\nasync function binaryWorks(binary: string): Promise<boolean> {\n if (binary.includes(path.sep)) {\n try {\n await fs.access(binary);\n } catch {\n return false;\n }\n }\n try {\n await exec(binary, ['--version'], PROBE_TIMEOUT_MS);\n return true;\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code;\n // A non-ENOENT failure still means the binary exists (e.g. bad flag).\n return code !== 'ENOENT' && code !== 'EACCES';\n }\n}\n\nfunction sofficeCandidates(): string[] {\n const candidates: string[] = [];\n const configured = process.env.LIBREOFFICE_PATH?.trim();\n if (configured) candidates.push(configured);\n if (process.platform === 'darwin') {\n candidates.push('/Applications/LibreOffice.app/Contents/MacOS/soffice');\n } else if (process.platform === 'win32') {\n candidates.push('C:\\\\Program Files\\\\LibreOffice\\\\program\\\\soffice.exe');\n candidates.push(\n 'C:\\\\Program Files (x86)\\\\LibreOffice\\\\program\\\\soffice.exe'\n );\n }\n candidates.push('soffice', 'libreoffice');\n return [...new Set(candidates)];\n}\n\nfunction pdftoppmCandidates(): string[] {\n const candidates: string[] = [];\n const configured = process.env.PDFTOPPM_PATH?.trim();\n if (configured) candidates.push(configured);\n candidates.push('pdftoppm');\n return [...new Set(candidates)];\n}\n\nasync function resolveBinary(\n candidates: string[],\n label: string,\n install: string\n): Promise<string> {\n for (const candidate of candidates) {\n if (await binaryWorks(candidate)) return candidate;\n }\n throw new Error(\n `Visual rasterization needs ${label}, which was not found. ${install} ` +\n `(searched: ${candidates.join(', ')}).`\n );\n}\n\n// Memoize resolved binary paths per process — they don't change at runtime, so\n// re-probing (spawning `--version` for every cache-miss rasterize) is wasted\n// work. A failed resolution is NOT cached, so a later call retries.\nlet sofficePromise: Promise<string> | undefined;\nlet pdftoppmPromise: Promise<string> | undefined;\nfunction resolveSoffice(): Promise<string> {\n if (!sofficePromise) {\n sofficePromise = resolveBinary(\n sofficeCandidates(),\n 'LibreOffice (soffice)',\n 'Install LibreOffice or set LIBREOFFICE_PATH.'\n ).catch((error) => {\n sofficePromise = undefined;\n throw error;\n });\n }\n return sofficePromise;\n}\nfunction resolvePdftoppm(): Promise<string> {\n if (!pdftoppmPromise) {\n pdftoppmPromise = resolveBinary(\n pdftoppmCandidates(),\n 'pdftoppm (poppler)',\n 'Install poppler-utils or set PDFTOPPM_PATH.'\n ).catch((error) => {\n pdftoppmPromise = undefined;\n throw error;\n });\n }\n return pdftoppmPromise;\n}\n\n/**\n * Process-wide rasterizer disk-cache counters (#156).\n */\nexport interface RasterizerCacheStats {\n /** Slides served from the content-addressed PNG disk cache. */\n diskHits: number;\n /** Unique slides that missed the disk cache and needed the engine. */\n diskMisses: number;\n /** diskHits / (diskHits + diskMisses), 0 when no lookups. */\n hitRate: number;\n /** Requests resolved by batch-internal dedupe (duplicate slides). */\n dedupedRequests: number;\n /** Slides successfully rendered by the engine (LibreOffice + pdftoppm). */\n rendered: number;\n /** Slides that failed at any engine stage. */\n failed: number;\n /** PNG files currently in the disk cache directories. */\n entries: number;\n /** Total bytes of those PNG files. */\n bytes: number;\n}\n\nconst rasterizerCounters = {\n diskHits: 0,\n diskMisses: 0,\n dedupedRequests: 0,\n rendered: 0,\n failed: 0,\n};\n\n/** Cache directories any engine run has used (for the disk scan). */\nconst knownCacheDirs = new Set<string>();\n\n/**\n * Get rasterizer cache statistics: process-lifetime counters plus a live\n * scan of the disk cache directories (default dir included, so entries from\n * previous processes are visible too).\n */\nexport async function getRasterizerCacheStats(): Promise<RasterizerCacheStats> {\n const dirs = new Set(knownCacheDirs);\n const defaultDir = resolveCacheDir();\n if (defaultDir) dirs.add(defaultDir);\n\n let entries = 0;\n let bytes = 0;\n for (const dir of dirs) {\n try {\n const files = await fs.readdir(dir);\n for (const file of files) {\n if (!file.endsWith('.png')) continue;\n try {\n const stat = await fs.stat(path.join(dir, file));\n entries++;\n bytes += stat.size;\n } catch {}\n }\n } catch {\n // Missing dir — nothing cached there.\n }\n }\n\n const lookups = rasterizerCounters.diskHits + rasterizerCounters.diskMisses;\n return {\n ...rasterizerCounters,\n hitRate: lookups > 0 ? rasterizerCounters.diskHits / lookups : 0,\n entries,\n bytes,\n };\n}\n\n/**\n * Delete every cached PNG in the known cache directories (default dir\n * included) and reset the counters. Backs \"Clear all caches\" (#156) — the\n * disk cache used to survive it.\n */\nexport async function clearRasterizerCache(): Promise<void> {\n const dirs = new Set(knownCacheDirs);\n const defaultDir = resolveCacheDir();\n if (defaultDir) dirs.add(defaultDir);\n\n for (const dir of dirs) {\n try {\n const files = await fs.readdir(dir);\n await Promise.all(\n files\n .filter((file) => file.endsWith('.png'))\n .map((file) => fs.rm(path.join(dir, file), { force: true }))\n );\n } catch {}\n }\n\n rasterizerCounters.diskHits = 0;\n rasterizerCounters.diskMisses = 0;\n rasterizerCounters.dedupedRequests = 0;\n rasterizerCounters.rendered = 0;\n rasterizerCounters.failed = 0;\n}\n\nconst PNG_SIGNATURE = Buffer.from([\n 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,\n]);\n\n/**\n * Validate a PNG buffer and read its width/height from the IHDR chunk. Returns\n * null for anything that isn't a complete PNG (empty/truncated/corrupt) so the\n * caller can re-render or error rather than emit a 0×0 / broken image. pdftoppm\n * always outputs PNG, so a PNG-only check is sufficient here.\n */\nfunction parsePngSize(png: Buffer): { width: number; height: number } | null {\n // 8-byte signature, then IHDR: length(4) + \"IHDR\"(4) + width(4) + height(4)\n if (png.length < 24) return null;\n if (!png.subarray(0, 8).equals(PNG_SIGNATURE)) return null;\n if (png.toString('ascii', 12, 16) !== 'IHDR') return null;\n const width = png.readUInt32BE(16);\n const height = png.readUInt32BE(20);\n if (width <= 0 || height <= 0) return null;\n return { width, height };\n}\n\nlet tmpCounter = 0;\n/**\n * Write the cache file atomically (temp file + rename) so a concurrent reader\n * never observes a half-written PNG. Best-effort: failures are swallowed (the\n * cache is an optimization), but a partial temp file is cleaned up.\n */\nasync function writeCacheAtomic(\n cacheDir: string,\n cachePath: string,\n png: Buffer\n): Promise<void> {\n const tmp = `${cachePath}.tmp-${process.pid}-${tmpCounter++}`;\n try {\n await fs.mkdir(cacheDir, { recursive: true });\n await fs.writeFile(tmp, png);\n await fs.rename(tmp, cachePath);\n } catch {\n await fs.rm(tmp, { force: true }).catch(() => {});\n }\n}\n\n/**\n * Identity of a font set for cache-keying. Order-insensitive (the per-face\n * digests are sorted) and content-addressed (hashes the decoded bytes, not\n * the base64 blob) so the same faces supplied in a different order, or\n * re-fetched to identical bytes, still hit the same cache entry.\n */\nexport function fontsDigest(\n fonts?: readonly RasterizeFontFace[]\n): string | undefined {\n if (!fonts || fonts.length === 0) return undefined;\n const parts = fonts\n .map(\n (f) =>\n `${f.family}|${f.weight}|${f.italic ? 'i' : 'r'}|` +\n crypto\n .createHash('sha256')\n // DECODED bytes, not the base64 text. `fromRasterizeFontFaces`\n // decodes before staging, and Node's base64 decoder is lenient:\n // missing padding, embedded newlines, and stray characters all\n // decode to the same bytes. Hashing the text would give two\n // spellings of one font two different cache keys — a silent\n // cache-miss multiplier on a shared, restart-surviving render\n // server cache.\n .update(Buffer.from(f.data, 'base64'))\n .digest('hex')\n )\n .sort();\n return crypto.createHash('sha256').update(parts.join('\\n')).digest('hex');\n}\n\nexport function cacheKey(request: {\n presentation: unknown;\n dpi: number;\n baseDir?: string;\n fontsKey?: string;\n}): string {\n return (\n crypto\n .createHash('sha256')\n // baseDir joins the key: the same relative asset path means different\n // pixels under different base directories (#142).\n //\n // fontsKey joins the key for the same reason: the same slide renders\n // DIFFERENT pixels with and without the document's fonts staged. This\n // cache is process-wide, shared by /rasterize and /rasterize/batch,\n // shared by every caller of the hosted render server, and it survives\n // restarts — keying without fonts would serve one document's\n // Inter-rendered PNG to a document that never asked for Inter.\n .update(\n JSON.stringify({\n p: request.presentation,\n dpi: request.dpi,\n base: request.baseDir,\n f: request.fontsKey ?? null,\n })\n )\n .digest('hex')\n );\n}\n\nfunction toDataUri(png: Buffer): string {\n return `data:image/png;base64,${png.toString('base64')}`;\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nasync function fileExists(filePath: string): Promise<boolean> {\n try {\n await fs.access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\n/** Engine-internal result: the wire shape plus the original error object. */\ntype EngineSlideResult = PptxRasterizeBatchSlideResult & { cause?: unknown };\n\n/** One unique unit of work: a slide deck plus every request index it serves. */\ninterface SlideJob {\n presentation: unknown;\n dpi: number;\n /** Request indexes resolved by this job (batch-internal dedup). */\n indexes: number[];\n cachePath: string | null;\n pptxPath: string;\n pdfPath: string;\n pngPrefix: string;\n}\n\nconst sofficeArgs = (\n profileDir: string,\n outDir: string,\n files: string[]\n): string[] => [\n '--headless',\n '--norestore',\n '--nolockcheck',\n '--nodefault',\n `-env:UserInstallation=file://${profileDir.replace(/\\\\/g, '/')}`,\n '--convert-to',\n 'pdf:impress_pdf_Export',\n '--outdir',\n outDir,\n ...files,\n];\n\n/**\n * Rasterize N independent single-slide presentations, amortizing the soffice\n * launch across every cache miss. Returns per-slide results (index-aligned);\n * only environment-level failures (missing binaries) throw.\n */\nasync function rasterizeSlidesWithEngine(\n slides: Array<{ presentation: unknown; dpi: number }>,\n baseDir: string | undefined,\n cacheDir: string | null,\n fonts?: readonly RasterizeFontFace[]\n): Promise<EngineSlideResult[]> {\n const results: EngineSlideResult[] = new Array(slides.length);\n if (cacheDir) knownCacheDirs.add(cacheDir);\n // Request-level, so every slide in this run shares one font set — which is\n // exactly why it can join the per-slide cache key without being per-slide\n // data.\n const fontsKey = fontsDigest(fonts);\n\n // 1. Dedupe by content-addressed key: identical slides build, convert, and\n // hit the cache exactly once, then fan out to every requesting index.\n const jobsByKey = new Map<string, SlideJob>();\n for (let i = 0; i < slides.length; i++) {\n const slide = slides[i];\n const key = cacheKey({\n presentation: slide.presentation,\n dpi: slide.dpi,\n baseDir,\n fontsKey,\n });\n const existing = jobsByKey.get(key);\n if (existing) {\n existing.indexes.push(i);\n } else {\n jobsByKey.set(key, {\n presentation: slide.presentation,\n dpi: slide.dpi,\n indexes: [i],\n cachePath: cacheDir ? path.join(cacheDir, `${key}.png`) : null,\n pptxPath: '',\n pdfPath: '',\n pngPrefix: '',\n });\n }\n }\n\n rasterizerCounters.dedupedRequests += slides.length - jobsByKey.size;\n\n const fail = (\n job: SlideJob,\n stage: PptxRasterizeFailureStage,\n error: string,\n cause?: unknown\n ) => {\n rasterizerCounters.failed++;\n for (const i of job.indexes)\n results[i] = { ok: false, error, stage, cause };\n };\n const succeed = (job: SlideJob, result: PptxRasterizeResult) => {\n for (const i of job.indexes) results[i] = { ok: true, ...result };\n };\n\n // 2. Resolve cache hits for the unique jobs in parallel.\n const uncached: SlideJob[] = [];\n await Promise.all(\n [...jobsByKey.values()].map(async (job) => {\n if (job.cachePath) {\n const cached = await fs.readFile(job.cachePath).catch(() => null);\n if (cached) {\n const size = parsePngSize(cached);\n if (size) {\n rasterizerCounters.diskHits++;\n succeed(job, { base64DataUri: toDataUri(cached), ...size });\n return;\n }\n // Corrupt/partial cache file (e.g. a killed prior render) — discard\n // it and re-render rather than embed a broken image.\n await fs.rm(job.cachePath, { force: true }).catch(() => {});\n }\n rasterizerCounters.diskMisses++;\n }\n uncached.push(job);\n })\n );\n if (uncached.length === 0) return results;\n\n // 3. JSON → .pptx (in-process, pure JS). A build failure is a per-slide\n // content error; the remaining slides still convert.\n const corePptx = await import('@json-to-office/core-pptx');\n const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'jto-visual-'));\n // Declared out here so the `finally` can always close it, however the try\n // exits. Assigned only after we know a soffice launch is actually needed.\n let stageHandle: FontStageHandle | null = null;\n try {\n const built: SlideJob[] = [];\n for (let j = 0; j < uncached.length; j++) {\n const job = uncached[j];\n job.pptxPath = path.join(tempDir, `slide-${j}.pptx`);\n job.pdfPath = path.join(tempDir, `slide-${j}.pdf`);\n job.pngPrefix = path.join(tempDir, `slide-${j}`);\n try {\n const pptxBuffer = await corePptx.generateBufferFromJson(\n job.presentation as any,\n { baseDir }\n );\n await fs.writeFile(job.pptxPath, pptxBuffer);\n built.push(job);\n } catch (error) {\n fail(job, 'build', errorMessage(error), error);\n }\n }\n if (built.length === 0) return results;\n\n // Stage the request's fonts for the soffice launches. Deliberately AFTER\n // the cache probe and the build loop: a request whose slides all hit the\n // disk cache, or whose decks all failed to build, launches no soffice at\n // all and must not pay for writing N font files (and, on macOS, four\n // profile trees).\n //\n // profileDirs MUST enumerate the isolated-retry profiles too. They are\n // distinct UserInstallation directories, and the macOS stager's macro is\n // only reachable from a profile it was seeded into — miss them and a\n // mid-batch soffice crash yields fontless PNGs for the salvaged slides.\n const profileDirs = [\n path.join(tempDir, 'profile'),\n ...Array.from({ length: MAX_ISOLATED_RETRIES }, (_, r) =>\n path.join(tempDir, `profile-retry-${r}`)\n ),\n ];\n const stageFonts = fonts?.length ? fromRasterizeFontFaces(fonts) : [];\n if (stageFonts.length > 0) {\n stageHandle = await getFontStager().stage(stageFonts, tempDir, {\n profileDirs,\n });\n }\n const sofficeEnv = stageHandle?.envOverrides;\n\n const [soffice, pdftoppm] = await Promise.all([\n resolveSoffice(),\n resolvePdftoppm(),\n ]);\n\n // 4. .pptx → PDF: ONE soffice launch converts every deck (the launch is\n // the multi-second cost being amortized). The timeout scales with the\n // slide count so a large batch is not misread as a hang, and the whole\n // engine run gets a deadline of one batch window + one pdftoppm window\n // per slide — bounded work no matter how conversions misbehave. The\n // PNG phase is sequential over every slide, so its share must scale\n // with the slide count like the soffice window does; otherwise a slow\n // soffice launch drains the budget and slides whose PDFs converted\n // fine fail spuriously. An HTTP client that gives up sooner falls\n // back to per-visual calls on its own.\n const batchTimeoutMs = Math.min(\n SOFFICE_TIMEOUT_MS +\n SOFFICE_BATCH_EXTRA_PER_SLIDE_MS * (built.length - 1),\n SOFFICE_BATCH_TIMEOUT_CAP_MS\n );\n const deadlineAt =\n Date.now() + batchTimeoutMs + PDFTOPPM_TIMEOUT_MS * built.length;\n const remainingMs = () => deadlineAt - Date.now();\n\n let batchError: unknown;\n try {\n await exec(\n soffice,\n sofficeArgs(\n profileDirs[0],\n tempDir,\n built.map((job) => job.pptxPath)\n ),\n batchTimeoutMs,\n sofficeEnv\n );\n } catch (error) {\n batchError = error;\n }\n\n // 5. Per-slide PDF check. soffice reports batch conversion coarsely (it\n // can skip a file or die mid-run), so the PDFs on disk are the truth.\n // A missing PDF gets an isolated single-file launch — salvaging slides\n // left unprocessed by a mid-batch crash and pinning the error on the\n // slide that caused it — but only within MAX_ISOLATED_RETRIES and the\n // deadline, and not when nothing at all converted (an environmental\n // failure that a retry would only repeat).\n const converted: SlideJob[] = [];\n const missing: SlideJob[] = [];\n for (const job of built) {\n ((await fileExists(job.pdfPath)) ? converted : missing).push(job);\n }\n\n let retriesLeft =\n missing.length > 0 &&\n built.length > 1 &&\n (batchError === undefined || converted.length > 0)\n ? MAX_ISOLATED_RETRIES\n : 0;\n for (const [r, job] of missing.entries()) {\n const retryBudget = Math.min(SOFFICE_TIMEOUT_MS, remainingMs());\n if (retriesLeft > 0 && retryBudget > 1000) {\n retriesLeft--;\n let retryError: unknown;\n try {\n await exec(\n soffice,\n // Same directories seeded in `profileDirs` above (index 0 is the\n // batch profile, so retry `r` is `profileDirs[r + 1]`); the\n // literal is kept as the fallback for an out-of-range retry.\n sofficeArgs(\n profileDirs[r + 1] ?? path.join(tempDir, `profile-retry-${r}`),\n tempDir,\n [job.pptxPath]\n ),\n retryBudget,\n sofficeEnv\n );\n } catch (error) {\n retryError = error;\n }\n if (await fileExists(job.pdfPath)) {\n converted.push(job);\n continue;\n }\n batchError ??= retryError;\n }\n const cause = batchError;\n fail(\n job,\n 'convert',\n `LibreOffice failed to convert the slide to PDF${\n cause ? `: ${errorMessage(cause)}` : '.'\n }`,\n cause\n );\n }\n\n // 6. PDF → PNG at each slide's own dpi (single page, no page suffix).\n // pdftoppm is cheap per file; each conversion still respects the\n // engine deadline so a pathological PDF cannot stack 30s timeouts.\n for (const job of converted) {\n const budget = Math.min(PDFTOPPM_TIMEOUT_MS, remainingMs());\n if (budget <= 1000) {\n fail(\n job,\n 'rasterize',\n 'Rasterization deadline exceeded before this slide could be converted to PNG.'\n );\n continue;\n }\n try {\n await exec(\n pdftoppm,\n [\n '-r',\n String(job.dpi),\n '-png',\n '-singlefile',\n job.pdfPath,\n job.pngPrefix,\n ],\n budget\n );\n const png = await fs.readFile(`${job.pngPrefix}.png`);\n const size = parsePngSize(png);\n if (!size) {\n throw new Error(\n 'Rasterization produced an invalid PNG (empty or truncated output from pdftoppm).'\n );\n }\n if (job.cachePath) {\n await writeCacheAtomic(cacheDir!, job.cachePath, png);\n }\n rasterizerCounters.rendered++;\n succeed(job, { base64DataUri: toDataUri(png), ...size });\n } catch (error) {\n fail(job, 'rasterize', errorMessage(error), error);\n }\n }\n\n return results;\n } finally {\n // Order matters: FontconfigStager.cleanup() restores write permission on\n // the staged fonts dir (stage() freezes it to 0o555), and rm cannot\n // unlink inside a non-writable directory. Reversed, the whole temp tree\n // leaks — silently, because the rm error is swallowed.\n if (stageHandle) await stageHandle.cleanup().catch(() => {});\n await fs.rm(tempDir, { recursive: true, force: true }).catch(() => {});\n }\n}\n\nfunction resolveCacheDir(options?: {\n cacheDir?: string | null;\n}): string | null {\n return options?.cacheDir === undefined\n ? path.join(os.tmpdir(), 'jto-visual-cache')\n : options.cacheDir;\n}\n\n/**\n * Build a LibreOffice-backed pptx rasterizer.\n *\n * @param options.cacheDir - directory for the content-addressed PNG cache\n * (default: <tmp>/jto-visual-cache). Pass `null` to disable caching.\n */\nexport function createLibreOfficePptxRasterizer(options?: {\n cacheDir?: string | null;\n}): PptxRasterizer {\n const cacheDir = resolveCacheDir(options);\n\n return async function rasterize(\n request: PptxRasterizeRequest\n ): Promise<PptxRasterizeResult> {\n const [result] = await rasterizeSlidesWithEngine(\n [{ presentation: request.presentation, dpi: request.dpi }],\n request.baseDir,\n cacheDir,\n request.fonts\n );\n if (!result) throw new Error('Rasterization produced no result.');\n if (!result.ok) {\n const error = new Error(result.error) as Error & { cause?: unknown };\n // Preserve the original failure (exec exit code/signal, fs error) for\n // programmatic consumers — parity with the pre-batch implementation\n // that threw the underlying error directly.\n if (result.cause !== undefined) error.cause = result.cause;\n throw error;\n }\n return {\n base64DataUri: result.base64DataUri,\n width: result.width,\n height: result.height,\n };\n };\n}\n\n/**\n * Build a LibreOffice-backed BATCH pptx rasterizer (#153): many independent\n * single-slide presentations, one soffice launch, per-slide results. Shares\n * the content-addressed disk cache with the single-slide rasterizer — the\n * per-slide cache key is identical on both paths.\n */\nexport function createLibreOfficePptxBatchRasterizer(options?: {\n cacheDir?: string | null;\n}): PptxBatchRasterizer {\n const cacheDir = resolveCacheDir(options);\n\n return async function rasterizeBatch(request) {\n const engineResults = await rasterizeSlidesWithEngine(\n request.slides.map((slide) => ({\n presentation: slide.presentation,\n dpi: slide.dpi ?? DEFAULT_VISUAL_DPI,\n })),\n request.baseDir,\n cacheDir,\n request.fonts\n );\n // Strip the engine-internal `cause` (non-serializable) from the results.\n return {\n results: engineResults.map((result) =>\n result.ok\n ? result\n : { ok: false, error: result.error, stage: result.stage }\n ),\n };\n };\n}\n","import type { FontStager, FontStageHandle, FontStageOptions } from './types';\n\nexport class NoopFontStager implements FontStager {\n // Signature matches FontStager; every parameter (including `options`) is\n // deliberately ignored on platforms with no staging mechanism.\n async stage(\n _fonts?: unknown,\n _tempDir?: string,\n _options?: FontStageOptions\n ): Promise<FontStageHandle> {\n return {\n envOverrides: {},\n cleanup: async () => {},\n };\n }\n}\n","/**\n * Linux + macOS: use fontconfig to expose staged TTFs to LibreOffice.\n *\n * Writes each resolved font to `<tempDir>/fonts/` and a minimal\n * fontconfig.xml that includes that dir plus the system font config.\n * LibreOffice honors the per-invocation FONTCONFIG_FILE env var.\n *\n * The caller removes the whole tempDir in its own finally block; `cleanup()`\n * only has to undo the read-only freeze stage() puts on the fonts dir so\n * that recursive rm can actually unlink.\n */\n\nimport { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport type { ResolvedFont } from '@json-to-office/shared';\nimport {\n synthesizeFamilyName,\n rewriteFontFamilyName,\n} from '@json-to-office/shared';\nimport type { FontStager, FontStageHandle, FontStageOptions } from './types';\nimport { nextStagingId, safeFilenamePart } from './types';\n\nconst SYSTEM_FONTS_CONF_CANDIDATES = [\n '/etc/fonts/fonts.conf',\n '/opt/homebrew/etc/fonts/fonts.conf',\n '/usr/local/etc/fonts/fonts.conf',\n];\n\nexport class FontconfigStager implements FontStager {\n async stage(\n fonts: ResolvedFont[],\n tempDir: string,\n // Ignored: fontconfig discovery is driven by FONTCONFIG_FILE, not by the\n // soffice UserInstallation profile.\n _options?: FontStageOptions\n ): Promise<FontStageHandle> {\n const id = nextStagingId();\n const fontsDir = path.join(tempDir, 'fonts');\n await fs.mkdir(fontsDir, { recursive: true });\n\n let serial = 0;\n for (const r of fonts) {\n if (r.sources.length === 0) continue;\n for (const s of r.sources) {\n serial += 1;\n const suffix = s.italic ? 'i' : 'r';\n // Rewrite `name` table so fontconfig indexes the file under the\n // synthetic sub-family (\"Inter Light\"), matching the doc's\n // `rFonts`/`fontFace` references after synthesizeFamilyName.\n //\n // The unrewritten branch (RIBBI: the run rides bold/italic toggles\n // on the base family) leans on the bytes already declaring\n // `r.family`. That is FontRegistry's `stampResolvedFamily` — do not\n // read the skip as \"this file is fine by construction\".\n const synth = synthesizeFamilyName(r.family, s.weight, s.italic);\n const data =\n synth.family === r.family\n ? s.data\n : rewriteFontFamilyName(s.data, synth.family);\n const name = `${safeFilenamePart(synth.family)}-${s.weight}${suffix}-${id}-${serial}.ttf`;\n await fs.writeFile(path.join(fontsDir, name), data);\n }\n }\n\n // Freeze the fonts dir read-only after staging so a misbehaving\n // fontconfig run (or a concurrent soffice spawn) can't corrupt the\n // file contents we just wrote. fc-cache writes its own indexes into\n // `cacheDir` (next step), which stays writable. The whole tree gets\n // rm'd by the converter in finally, so mode matters only during the\n // conversion window.\n await fs.chmod(fontsDir, 0o555).catch(() => {\n // Some filesystems (e.g. certain Windows-mounted shares under WSL)\n // refuse chmod — ignore and proceed. The defensive-in-depth case\n // still wins on native Linux/macOS.\n });\n const includeLines = await this.pickSystemIncludes();\n // Redirect fontconfig's scan cache into tempDir. Without this, fontconfig\n // writes cache entries for `fontsDir` into the user's ~/.cache/fontconfig\n // and leaves them behind after tempDir is rm'd. Per-invocation isolation\n // also prevents two concurrent conversions from racing on the same\n // fontconfig cache directory.\n const cacheDir = path.join(tempDir, 'fc-cache');\n await fs.mkdir(cacheDir, { recursive: true });\n const configPath = path.join(tempDir, 'fontconfig.xml');\n const configXml = [\n '<?xml version=\"1.0\"?>',\n '<!DOCTYPE fontconfig SYSTEM \"fonts.dtd\">',\n '<fontconfig>',\n ` <dir>${escapeXml(fontsDir)}</dir>`,\n ` <cachedir>${escapeXml(cacheDir)}</cachedir>`,\n ...includeLines,\n '</fontconfig>',\n '',\n ].join('\\n');\n await fs.writeFile(configPath, configXml, 'utf8');\n\n return {\n envOverrides: {\n FONTCONFIG_FILE: configPath,\n XDG_CACHE_HOME: cacheDir,\n },\n cleanup: async () => {\n // Restore write permission before the caller's recursive rm. stage()\n // froze fontsDir to 0o555, and you cannot unlink entries inside a\n // non-writable directory: `fs.rm(tempDir, { recursive: true })` fails\n // with EACCES and leaks the whole temp tree (both callers swallow\n // that error, so it was silent). Removing the files themselves is\n // still the caller's job.\n await fs.chmod(fontsDir, 0o755).catch(() => {});\n },\n };\n }\n\n private async pickSystemIncludes(): Promise<string[]> {\n for (const candidate of SYSTEM_FONTS_CONF_CANDIDATES) {\n try {\n await fs.access(candidate);\n return [\n ` <include ignore_missing=\"yes\">${escapeXml(candidate)}</include>`,\n ];\n } catch {\n /* try next */\n }\n }\n // Fall back to the conventional path; fontconfig will fail softly.\n return [` <include ignore_missing=\"yes\">/etc/fonts/fonts.conf</include>`];\n }\n}\n\nfunction escapeXml(s: string): string {\n return s\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;');\n}\n","/**\n * Make resolved fonts visible to the LibreOffice child process for the\n * duration of one PDF conversion, then clean up.\n *\n * Linux/macOS: fontconfig + FONTCONFIG_FILE env var.\n * Windows: GDI session registration via koffi (AddFontResourceW).\n *\n * The caller calls `stage(fonts, tempDir)` before spawning soffice, merges\n * `envOverrides` into the child process env, waits for conversion, then\n * awaits `cleanup()` regardless of success or failure.\n *\n * Two consumers today: the playground's LibreOffice PDF-preview converter\n * (`@json-to-office/jto`) and the pptx rasterizer that backs docx `visual`\n * components (`../pptx-rasterizer.ts`).\n */\n\nimport type { ResolvedFont } from '@json-to-office/shared';\n\nexport interface FontStageHandle {\n /** Merged into the child process env. Empty object if nothing to stage. */\n envOverrides: Record<string, string>;\n /** Always call in a finally block. Safe to call multiple times (idempotent). */\n cleanup(): Promise<void>;\n}\n\nexport interface FontStageOptions {\n /**\n * UserInstallation profile directories the soffice launch(es) will use.\n * The macOS stager seeds its OnStartApp Python macro into EACH of them —\n * a launch with an unseeded profile registers no fonts and silently falls\n * back to system faces. Absent → `<tempDir>/user-profile`, the\n * LibreOfficeConverterService convention.\n */\n profileDirs?: string[];\n}\n\nexport interface FontStager {\n stage(\n fonts: ResolvedFont[],\n tempDir: string,\n options?: FontStageOptions\n ): Promise<FontStageHandle>;\n}\n\n/** Per-process monotonic counter to disambiguate concurrent conversions. */\nlet counter = 0;\nexport function nextStagingId(): string {\n counter += 1;\n return `${process.pid}-${counter}`;\n}\n\n/** Sanitize a font family for use in a filename. */\nexport function safeFilenamePart(s: string): string {\n return s.replace(/[^a-zA-Z0-9._-]/g, '_').slice(0, 48);\n}\n","/**\n * Windows: register staged TTFs with GDI via AddFontResourceW so the soffice\n * child process finds them at startup. Forces LibreOffice onto the GDI\n * backend via SAL_DISABLE_SKIA=1 — Skia/DirectWrite doesn't reliably pick\n * up GDI-registered fonts on recent LO builds.\n *\n * Scope, precisely: `AddFontResourceW` adds to the **session** font table, not\n * a private per-process one. That is deliberate and unavoidable here — the\n * private variant (`AddFontResourceExW` with `FR_PRIVATE`) is visible only to\n * the registering process, and the process that has to see these fonts is the\n * `soffice` CHILD. Node stays alive for the full conversion so the fonts\n * persist until cleanup, and GDI releases them on process exit if Node\n * crashes, so nothing leaks past the process.\n *\n * KNOWN LIMITATION — concurrent conversions on one Windows host share that\n * session table. Two conversions staging different bytes under the same\n * synthesized family (say two documents that each embed their own \"Inter\")\n * register two faces claiming one name, and which one GDI hands to soffice is\n * then order-dependent. Staged FILES never collide — each carries a\n * pid-plus-counter suffix — so this is a resolution ambiguity, not corruption.\n *\n * Not fixed here because the only correct fix is a host-wide lease held from\n * stage() through cleanup(), which serializes every Windows conversion; that\n * is a real throughput cost for a risk the deployed images do not carry (both\n * production containers are Linux/fontconfig, where staging is per-process via\n * FONTCONFIG_FILE and cannot collide). It bites a Windows host running\n * concurrent conversions — worth a lease if that becomes a supported topology.\n */\n\nimport { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport type { ResolvedFont } from '@json-to-office/shared';\nimport {\n synthesizeFamilyName,\n rewriteFontFamilyName,\n} from '@json-to-office/shared';\nimport type { FontStager, FontStageHandle, FontStageOptions } from './types';\nimport { nextStagingId, safeFilenamePart } from './types';\n\ntype KoffiLib = {\n func: (sig: string) => (...args: unknown[]) => number | boolean;\n};\ntype KoffiModule = {\n load: (libName: string) => KoffiLib;\n};\n\n// Lazy-load koffi so Linux/macOS don't incur the FFI init cost.\nlet cachedBindings: {\n addFont: (pathW: string) => number;\n removeFont: (pathW: string) => boolean;\n} | null = null;\n\nasync function getGdiBindings() {\n if (cachedBindings) return cachedBindings;\n const koffi = (await import('koffi')) as unknown as {\n default?: KoffiModule;\n } & KoffiModule;\n const mod = koffi.default ?? koffi;\n const gdi32 = mod.load('gdi32.dll');\n cachedBindings = {\n addFont: gdi32.func('int __stdcall AddFontResourceW(str16)') as (\n path: string\n ) => number,\n removeFont: gdi32.func('bool __stdcall RemoveFontResourceW(str16)') as (\n path: string\n ) => boolean,\n };\n return cachedBindings;\n}\n\nexport class WindowsFontStager implements FontStager {\n async stage(\n fonts: ResolvedFont[],\n tempDir: string,\n // Ignored: GDI registration is session-wide, not scoped to any one\n // LibreOffice UserInstallation profile, so the retry profiles need no\n // separate seeding the way the macOS Core Text stager's do.\n _options?: FontStageOptions\n ): Promise<FontStageHandle> {\n const id = nextStagingId();\n const fontsDir = path.join(tempDir, 'fonts');\n await fs.mkdir(fontsDir, { recursive: true });\n\n const stagedPaths: string[] = [];\n let serial = 0;\n for (const r of fonts) {\n if (r.sources.length === 0) continue;\n for (const s of r.sources) {\n serial += 1;\n const suffix = s.italic ? 'i' : 'r';\n // Rewrite the TTF's internal family name so GDI registers the\n // file under the synthetic sub-family the doc references.\n //\n // The unrewritten branch (RIBBI: the run rides bold/italic toggles\n // on the base family) leans on the bytes already declaring\n // `r.family`. That is FontRegistry's `stampResolvedFamily` — do not\n // read the skip as \"this file is fine by construction\".\n const synth = synthesizeFamilyName(r.family, s.weight, s.italic);\n const data =\n synth.family === r.family\n ? s.data\n : rewriteFontFamilyName(s.data, synth.family);\n const name = `${safeFilenamePart(synth.family)}-${s.weight}${suffix}-${id}-${serial}.ttf`;\n const fullPath = path.join(fontsDir, name);\n await fs.writeFile(fullPath, data);\n stagedPaths.push(fullPath);\n }\n }\n\n if (stagedPaths.length === 0) {\n return { envOverrides: {}, cleanup: async () => {} };\n }\n\n const { addFont, removeFont } = await getGdiBindings();\n const registered: string[] = [];\n for (const p of stagedPaths) {\n const added = addFont(p);\n if (added > 0) registered.push(p);\n }\n\n let cleaned = false;\n return {\n envOverrides: {\n // Force GDI backend so the freshly-registered fonts are visible.\n // Skia on Windows uses DirectWrite which does not reliably see\n // fonts added via AddFontResourceW.\n SAL_DISABLE_SKIA: '1',\n },\n cleanup: async () => {\n if (cleaned) return;\n cleaned = true;\n for (const p of registered) {\n try {\n removeFont(p);\n } catch {\n // Swallow — GDI will drop it on process exit anyway.\n }\n }\n },\n };\n }\n}\n","/**\n * macOS: make staged fonts visible to the soffice child process by\n * registering them *inside* soffice via a Python macro bound to the\n * `OnStartApp` event.\n *\n * Why this works on macOS 26. Apple tightened `CTFontManagerScopeSession`\n * and `kCTFontManagerScopePersistent` to require signed+notarized callers\n * (unsigned Node processes get `paramErr -50`), and `Process` scope only\n * registers fonts for the calling process — so the Node server can't\n * register fonts the soffice child sees. But Process scope DOES work from\n * inside soffice. LibreOffice for macOS bundles Python 3.12 with `ctypes`,\n * and UNO lets us bind a Python macro to application-start events. We seed\n * a per-invocation UserInstallation profile with:\n *\n * - `user/Scripts/python/JtoFontRegister.py` — a ~20-line macro that\n * reads `JTO_FONT_PATHS` from the process env and calls\n * `CTFontManagerRegisterFontsForURL(url, kScopeProcess, NULL)` via\n * ctypes for each path (Process = 1 in Core Text's scope enum).\n * - `user/registrymodifications.xcu` — binds the macro to `OnStartApp`\n * and sets `MacroSecurityLevel=0` for this ephemeral profile only.\n *\n * SECURITY INVARIANT — macro execution scope. The seeded profile disables\n * soffice's macro-security prompt (`MacroSecurityLevel=0`) so our\n * OnStartApp macro runs without a dialog. This profile MUST ONLY be used\n * to open files this server just generated (i.e., well-formed outputs\n * from `@json-to-office/core-*`). Piping user-supplied .docx/.pptx/.odt\n * through a soffice invocation that uses this stager would execute any\n * embedded VBA/Basic macros silently — an RCE primitive. If a future\n * code path ever converts user-supplied documents (e.g. \"PDF-ify my\n * upload\"), it must build a separate converter that does NOT share this\n * profile, or call soffice with `--safe-mode` / a default profile.\n *\n * The pptx rasterizer (`./pptx-rasterizer.ts`) is the second consumer and\n * upholds the same invariant: it only ever opens .pptx files this process\n * just built from `@json-to-office/core-pptx` out of the request's own\n * presentation JSON — never a user-supplied binary document.\n *\n * PROFILE DIRS. The macro is only reachable from the UserInstallation\n * profile it was seeded into, so `options.profileDirs` must list EVERY\n * profile the caller will launch soffice with. The converter uses exactly\n * one (`<tempDir>/user-profile`, the default here); the rasterizer uses a\n * batch profile plus one per isolated retry, and a launch against an\n * unseeded profile registers nothing and silently falls back.\n *\n * Flow: the converter spawns\n * `soffice --headless -env:UserInstallation=file://<tempDir>/user-profile ...`\n * with env `{ JTO_FONT_PATHS: \"<ttf1>:<ttf2>:...\" }`. LO boots, reads our\n * seeded profile, fires `OnStartApp`, runs our macro, registers each\n * staged TTF at Process scope in its own process. Font enumeration then\n * resolves the synthetic family names (`Inter Bold`, `Source Code Pro\n * Medium`, …) against those Process-scope registrations. The PDF export\n * ships the correct glyphs.\n *\n * Elegant side-effects:\n * - Nothing outside the converter's `tempDir` is touched. The user's\n * real `~/Library/Fonts` and `~/Library/Application Support/LibreOffice`\n * stay untouched.\n * - Cleanup is the converter's `fs.rm(tempDir, …)` — no orphan sweep\n * needed. If Node crashes mid-conversion, the per-invocation tempDir\n * is reaped by macOS tmpreaper (or the OS's next boot cleanup).\n * - No DYLD injection, no notarized helper, no filesystem-scan races.\n *\n * Failure mode: if LibreOffice can't run the macro for any reason\n * (macro-security policy applied at bootstrap before our XCU loads,\n * Python not present in a minimal LO build, …) the soffice process still\n * runs and produces a PDF — just with system-fallback fonts, exactly the\n * pre-fix behavior. Non-catastrophic. Diagnose via stderr: the macro\n * writes failures to stderr via `print(..., file=sys.stderr)`.\n */\n\nimport { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport type { ResolvedFont } from '@json-to-office/shared';\nimport {\n synthesizeFamilyName,\n rewriteFontFamilyName,\n} from '@json-to-office/shared';\nimport type { FontStager, FontStageHandle, FontStageOptions } from './types';\nimport { nextStagingId, safeFilenamePart } from './types';\nimport { emitDiagnostic } from '../diagnostics.js';\n\n/**\n * Python macro source. Written verbatim into\n * `<profile>/user/Scripts/python/JtoFontRegister.py`. Kept inline (rather\n * than a separate .py asset) so this TypeScript module is self-contained\n * and survives bundler reshuffles — no runtime file-system lookup for a\n * static asset.\n */\nconst PYTHON_MACRO = `# Auto-generated by @json-to-office/jto. Runs inside soffice on OnStartApp\n# to make staged fonts visible to LibreOffice's font enumeration. macOS 26\n# blocks Session/Persistent CT registration for unsigned callers, but\n# Process scope still works from inside the target process — which is\n# exactly where this macro runs.\nimport os\nimport sys\nimport ctypes\nimport ctypes.util\n\n\ndef _log(msg):\n # soffice swallows Python stdout in headless mode; stderr surfaces to\n # the parent's pipe. The stager doesn't read this today but the\n # converter logs stderr on failure, which is how we'll debug.\n sys.stderr.write(\"[jto-font-register] \" + msg + \"\\\\n\")\n\n\ndef register(*_args):\n paths_env = os.environ.get(\"JTO_FONT_PATHS\", \"\")\n if not paths_env:\n return\n try:\n cf = ctypes.CDLL(ctypes.util.find_library(\"CoreFoundation\"))\n ct = ctypes.CDLL(ctypes.util.find_library(\"CoreText\"))\n except Exception as e:\n _log(\"failed to load CoreFoundation/CoreText: \" + repr(e))\n return\n cf.CFURLCreateFromFileSystemRepresentation.argtypes = [\n ctypes.c_void_p, ctypes.c_char_p, ctypes.c_long, ctypes.c_bool,\n ]\n cf.CFURLCreateFromFileSystemRepresentation.restype = ctypes.c_void_p\n cf.CFRelease.argtypes = [ctypes.c_void_p]\n ct.CTFontManagerRegisterFontsForURL.argtypes = [\n ctypes.c_void_p, ctypes.c_uint32, ctypes.c_void_p,\n ]\n ct.CTFontManagerRegisterFontsForURL.restype = ctypes.c_bool\n\n kCTFontManagerScopeProcess = 1\n registered = 0\n for p in paths_env.split(os.pathsep):\n if not p:\n continue\n try:\n b = p.encode(\"utf-8\")\n url = cf.CFURLCreateFromFileSystemRepresentation(\n None, b, len(b), False\n )\n if not url:\n _log(\"CFURL failed for \" + p)\n continue\n ok = ct.CTFontManagerRegisterFontsForURL(\n url, kCTFontManagerScopeProcess, None\n )\n cf.CFRelease(url)\n if ok:\n registered += 1\n else:\n _log(\"CT register returned false for \" + p)\n except Exception as e:\n _log(\"exception registering \" + p + \": \" + repr(e))\n _log(\"registered \" + str(registered) + \" font(s) at Process scope\")\n\n\n# Expose under \"register\" (event-binding URL) and module-level run so\n# command-line vnd.sun.star.script invocation works either way.\ng_exportedScripts = (register,)\n`;\n\n/**\n * XCU that LO merges into its Bootstrap registry on startup. Two keys:\n *\n * 1. `/org.openoffice.Office.Events/ApplicationEvents/Bindings/OnStartApp`\n * → fires our Python macro before any document is loaded, so font\n * registration completes before LO enumerates fonts for rendering.\n *\n * 2. `/org.openoffice.Office.Common/Security/Scripting/MacroSecurityLevel`\n * → 0 (allow all). This only applies to the ephemeral UserInstallation\n * profile we create under `<tempDir>/user-profile`; the user's real\n * LibreOffice profile config is untouched. Scope of the security\n * relaxation is bounded by the per-invocation profile's lifetime.\n */\nconst REGISTRY_MOD_XCU = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<oor:items xmlns:oor=\"http://openoffice.org/2001/registry\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\">\n <item oor:path=\"/org.openoffice.Office.Events/ApplicationEvents/Bindings\">\n <node oor:name=\"OnStartApp\" oor:op=\"replace\">\n <prop oor:name=\"BindingURL\" oor:type=\"xs:string\">\n <value>vnd.sun.star.script:JtoFontRegister.py$register?language=Python&amp;location=user</value>\n </prop>\n </node>\n </item>\n <item oor:path=\"/org.openoffice.Office.Common/Security/Scripting\">\n <prop oor:name=\"MacroSecurityLevel\" oor:op=\"fuse\">\n <value>0</value>\n </prop>\n </item>\n</oor:items>\n`;\n\nexport class MacOSCoreTextStager implements FontStager {\n async stage(\n fonts: ResolvedFont[],\n tempDir: string,\n options?: FontStageOptions\n ): Promise<FontStageHandle> {\n const embeddable = fonts.filter((r) => r.sources.length > 0);\n if (embeddable.length === 0) {\n return { envOverrides: {}, cleanup: async () => {} };\n }\n\n // Fonts go to a subdir of the converter's tempDir. No ~/Library writes.\n const fontsDir = path.join(tempDir, 'fonts');\n await fs.mkdir(fontsDir, { recursive: true });\n\n const id = nextStagingId();\n const fontPaths: string[] = [];\n const staged: {\n family: string;\n weight: number;\n italic: boolean;\n path: string;\n }[] = [];\n let serial = 0;\n for (const r of embeddable) {\n for (const s of r.sources) {\n serial += 1;\n const suffix = s.italic ? 'i' : 'r';\n // Rewrite the TTF's internal family name to match the synthetic\n // sub-family the doc references (e.g. \"Inter Light\"). Without\n // this, Core Text indexes the staged file as \"Inter\" and\n // LibreOffice can't resolve `rFonts w:ascii=\"Inter Light\"`.\n //\n // The unrewritten branch (RIBBI: the run rides bold/italic toggles\n // on the base family) leans on the bytes already declaring\n // `r.family`. That is FontRegistry's `stampResolvedFamily` — do not\n // read the skip as \"this file is fine by construction\".\n const synth = synthesizeFamilyName(r.family, s.weight, s.italic);\n const data =\n synth.family === r.family\n ? s.data\n : rewriteFontFamilyName(s.data, synth.family);\n const name = `${safeFilenamePart(synth.family)}-${s.weight}${suffix}-${id}-${serial}.ttf`;\n const full = path.join(fontsDir, name);\n await fs.writeFile(full, data);\n fontPaths.push(full);\n staged.push({\n family: synth.family,\n weight: s.weight,\n italic: s.italic,\n path: full,\n });\n }\n }\n\n if (process.env.JTO_DEBUG_FONTS === '1') {\n // Through the sink, never a stream: a host may own stdout as a\n // protocol channel (the MCP server does), so this package writes\n // nowhere on its own. Install a sink to see it.\n emitDiagnostic(\n '[jto macos-stager] staged ' +\n staged.length +\n ' font(s) for CT Process-scope registration; JTO_FONT_PATHS has ' +\n fontPaths.length +\n ' entries\\n' +\n staged\n .map(\n (s) =>\n ` ${s.family} (w=${s.weight}${s.italic ? ' italic' : ''}) → ${path.basename(s.path)}`\n )\n .join('\\n')\n );\n }\n\n // Seed EVERY per-invocation UserInstallation profile the caller will\n // launch with, so LO reads the macro + event binding from the exact\n // path its `-env:UserInstallation=` points at. The converter passes\n // nothing and gets `<tempDir>/user-profile`; the rasterizer passes its\n // batch profile plus every isolated-retry profile.\n const profileDirs = options?.profileDirs?.length\n ? options.profileDirs\n : [path.join(tempDir, 'user-profile')];\n for (const profileDir of profileDirs) {\n const profileUser = path.join(profileDir, 'user');\n const scriptsDir = path.join(profileUser, 'Scripts', 'python');\n await fs.mkdir(scriptsDir, { recursive: true });\n await fs.writeFile(\n path.join(scriptsDir, 'JtoFontRegister.py'),\n PYTHON_MACRO\n );\n await fs.writeFile(\n path.join(profileUser, 'registrymodifications.xcu'),\n REGISTRY_MOD_XCU\n );\n }\n\n return {\n envOverrides: {\n // Colon-separated list of staged TTF paths (`:` matches Python's\n // `os.pathsep` on macOS). The macro reads this at OnStartApp.\n JTO_FONT_PATHS: fontPaths.join(':'),\n // Force LibreOffice's Core Graphics backend. Skia on macOS can\n // skip Core Text's freshly-registered fonts in some builds.\n SAL_DISABLE_SKIA: '1',\n },\n cleanup: async () => {\n // No-op: the converter's `fs.rm(tempDir, { recursive: true })` in\n // its own finally block sweeps everything we wrote here.\n },\n };\n }\n}\n","import { AsyncLocalStorage } from 'node:async_hooks';\n\nexport type DiagnosticTone =\n | 'default'\n | 'info'\n | 'success'\n | 'warning'\n | 'error'\n | 'muted';\n\nexport type DiagnosticSink = (text: string, tone?: DiagnosticTone) => void;\n\nconst sinks = new AsyncLocalStorage<DiagnosticSink>();\n\n/**\n * Scope a sink to one operation. The CLI installs an Ink-backed one per\n * task; the MCP server installs one that collects into its response.\n */\nexport function runWithDiagnosticSink<T>(\n sink: DiagnosticSink,\n callback: () => T\n): T {\n return sinks.run(sink, callback);\n}\n\n/**\n * This package writes to no stream of its own — a host may own stdout as a\n * protocol channel — so with no sink installed the message is dropped.\n */\nexport function emitDiagnostic(\n text: string,\n tone: DiagnosticTone = 'muted'\n): void {\n sinks.getStore()?.(text, tone);\n}\n\n/**\n * Plain-text sink for hosts with no UI of their own. stderr, not stdout,\n * so it stays safe to install alongside a stdout protocol stream.\n */\nexport const stderrDiagnosticSink: DiagnosticSink = (text) => {\n process.stderr.write(`${text}\\n`);\n};\n","/**\n * Factory entry point for the font staging pipeline shared by every\n * LibreOffice launch in the toolchain: the playground's PDF preview\n * converter (`@json-to-office/jto`) and the pptx rasterizer that backs docx\n * `visual` components (`../pptx-rasterizer.ts`).\n */\n\nimport type { FontStager } from './types';\nimport { NoopFontStager } from './noop-stager';\nimport { FontconfigStager } from './fontconfig-stager';\nimport { WindowsFontStager } from './windows-stager';\nimport { MacOSCoreTextStager } from './macos-stager';\n\nexport type { FontStager, FontStageHandle, FontStageOptions } from './types';\n\nconst cached = new Map<NodeJS.Platform, FontStager>();\n\nexport function getFontStager(\n platform: NodeJS.Platform = process.platform\n): FontStager {\n const hit = cached.get(platform);\n if (hit) return hit;\n let stager: FontStager;\n switch (platform) {\n case 'win32':\n stager = new WindowsFontStager();\n break;\n case 'darwin':\n // LibreOffice-for-macOS uses Core Text for font enumeration and does\n // not honor FONTCONFIG_FILE reliably. macOS 26 further blocks\n // Session/Persistent CT registration for unsigned callers, and\n // Process scope only works inside the target process. We register\n // fonts from *inside* soffice via a Python UNO macro bound to\n // OnStartApp, seeded into a per-invocation UserInstallation profile.\n // The user's ~/Library/Fonts is never touched. See macos-stager.ts.\n stager = new MacOSCoreTextStager();\n break;\n case 'linux':\n case 'freebsd':\n case 'openbsd':\n stager = new FontconfigStager();\n break;\n default:\n stager = new NoopFontStager();\n }\n cached.set(platform, stager);\n return stager;\n}\n\nexport {\n NoopFontStager,\n FontconfigStager,\n WindowsFontStager,\n MacOSCoreTextStager,\n};\n","/**\n * Text geometry from a rendered PDF — the ground truth the quality\n * estimators are guessing at (#216 follow-up).\n *\n * The pptx preview pipeline already produces a PDF (soffice → pdftoppm) and\n * uses it purely as a bitmap source. That PDF records the exact position of\n * every glyph as laid out by LibreOffice — the same engine the quality rules\n * try to predict. `pdftotext -bbox` (poppler, already a rasterizer\n * dependency alongside pdftoppm) dumps per-word bounding boxes; this module\n * parses them into slide-space points so callers can compare a rule's\n * estimate against what the renderer actually did.\n *\n * Coordinates: PDF points (1/72 in), origin at the page's top-left corner,\n * y increasing downward — the same frame as authored inches × 72. A PDF page\n * rendered from a slide has the slide's dimensions, so word boxes compare\n * directly against authored shape geometry with no transform.\n *\n * Consumers: the quality ground-truth harness (estimator calibration) today;\n * a `rendered`-certainty analysis pass tomorrow.\n */\n\nimport { execFile } from 'child_process';\nimport type { PdfPageInk } from './pdf-page-ink.js';\nimport * as path from 'path';\n\n/** One word as laid out on the page, in PDF points, top-left origin. */\nexport interface PdfTextWord {\n text: string;\n xMin: number;\n yMin: number;\n xMax: number;\n yMax: number;\n}\n\n/**\n * One line as poppler's layout analysis grouped it (`-bbox-layout` only):\n * its box plus the indices into `words` of the words on it.\n */\nexport interface PdfTextLine {\n xMin: number;\n yMin: number;\n xMax: number;\n yMax: number;\n words: number[];\n}\n\n/** One PDF page: its size in points plus every word poppler segmented. */\nexport interface PdfTextPage {\n widthPt: number;\n heightPt: number;\n /** Every word in stream order, whichever mode produced the page. */\n words: PdfTextWord[];\n /** Line grouping; empty for plain `-bbox` output, which groups nothing. */\n lines: PdfTextLine[];\n /**\n * Which raster rows carry ink, when the host sampled the page (see\n * `pdf-page-ink.ts`). Absent, the rules that need it stay silent.\n */\n ink?: PdfPageInk;\n}\n\nconst ENTITIES: Readonly<Record<string, string>> = {\n '&amp;': '&',\n '&lt;': '<',\n '&gt;': '>',\n '&quot;': '\"',\n '&apos;': \"'\",\n '&#34;': '\"',\n '&#39;': \"'\",\n};\n\nfunction decodeEntities(value: string): string {\n return value.replace(\n /&(?:amp|lt|gt|quot|apos|#34|#39);/g,\n (entity) => ENTITIES[entity] ?? entity\n );\n}\n\nconst PAGE_PATTERN =\n /<page\\s+width=\"([\\d.]+)\"\\s+height=\"([\\d.]+)\">([\\s\\S]*?)<\\/page>/g;\n// A line opens or closes; words between the two belong to it. Plain -bbox\n// output has no lines at all, so the same walk yields an empty grouping.\nconst LINE_OR_WORD_PATTERN =\n /<line\\s+xMin=\"(-?[\\d.]+)\"\\s+yMin=\"(-?[\\d.]+)\"\\s+xMax=\"(-?[\\d.]+)\"\\s+yMax=\"(-?[\\d.]+)\">|<\\/line>|<word\\s+xMin=\"(-?[\\d.]+)\"\\s+yMin=\"(-?[\\d.]+)\"\\s+xMax=\"(-?[\\d.]+)\"\\s+yMax=\"(-?[\\d.]+)\">([\\s\\S]*?)<\\/word>/g;\n\n/**\n * Parse `pdftotext -bbox` or `-bbox-layout` output (XHTML with `<page>`,\n * optionally `<line>`, and `<word>` elements). Pure — feed it a captured\n * document for tests, or the runner's stdout.\n */\nexport function parsePdfTextBbox(bboxXml: string): PdfTextPage[] {\n const pages: PdfTextPage[] = [];\n for (const pageMatch of bboxXml.matchAll(PAGE_PATTERN)) {\n const words: PdfTextWord[] = [];\n const lines: PdfTextLine[] = [];\n let open: PdfTextLine | undefined;\n for (const token of pageMatch[3].matchAll(LINE_OR_WORD_PATTERN)) {\n if (token[0] === '</line>') {\n if (open) lines.push(open);\n open = undefined;\n } else if (token[1] !== undefined) {\n open = {\n xMin: Number(token[1]),\n yMin: Number(token[2]),\n xMax: Number(token[3]),\n yMax: Number(token[4]),\n words: [],\n };\n } else {\n open?.words.push(words.length);\n words.push({\n xMin: Number(token[5]),\n yMin: Number(token[6]),\n xMax: Number(token[7]),\n yMax: Number(token[8]),\n text: decodeEntities(token[9]),\n });\n }\n }\n pages.push({\n widthPt: Number(pageMatch[1]),\n heightPt: Number(pageMatch[2]),\n words,\n lines,\n });\n }\n return pages;\n}\n\nfunction pdftotextCandidates(): string[] {\n const candidates: string[] = [];\n const configured = process.env.PDFTOTEXT_PATH?.trim();\n if (configured) candidates.push(configured);\n candidates.push('pdftotext');\n return [...new Set(candidates)];\n}\n\nasync function run(\n binary: string,\n args: string[],\n timeoutMs: number\n): Promise<string> {\n return new Promise((resolve, reject) => {\n execFile(\n binary,\n args,\n { timeout: timeoutMs, maxBuffer: 64 * 1024 * 1024 },\n (error, stdout) => {\n if (error) reject(error);\n else resolve(stdout);\n }\n );\n });\n}\n\n// Same memoization shape as the rasterizer's soffice/pdftoppm resolution:\n// success is cached per process, failure retries on the next call.\nlet pdftotextPromise: Promise<string> | undefined;\nasync function resolvePdftotext(): Promise<string> {\n if (!pdftotextPromise) {\n pdftotextPromise = (async () => {\n for (const candidate of pdftotextCandidates()) {\n if (candidate.includes(path.sep)) {\n try {\n await run(candidate, ['-v'], 10_000);\n return candidate;\n } catch {\n continue;\n }\n }\n try {\n await run(candidate, ['-v'], 10_000);\n return candidate;\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code;\n // pdftotext -v exits 0 on modern poppler; a non-ENOENT failure\n // still means the binary exists.\n if (code !== 'ENOENT' && code !== 'EACCES') return candidate;\n }\n }\n throw new Error(\n 'Text geometry extraction needs pdftotext (poppler), which was not ' +\n 'found. Install poppler-utils or set PDFTOTEXT_PATH ' +\n `(searched: ${pdftotextCandidates().join(', ')}).`\n );\n })().catch((error) => {\n pdftotextPromise = undefined;\n throw error;\n });\n }\n return pdftotextPromise;\n}\n\n/** True when a `pdftotext` binary is reachable — lets harnesses skip early. */\nexport async function pdftotextAvailable(): Promise<boolean> {\n try {\n await resolvePdftotext();\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Extract per-word text geometry from a PDF on disk. One pdftotext spawn,\n * output streamed through stdout — nothing else touches the filesystem.\n * `layout: true` asks poppler for its line grouping too (`-bbox-layout`).\n */\nexport async function extractPdfTextGeometry(\n pdfPath: string,\n options: { timeoutMs?: number; layout?: boolean } = {}\n): Promise<PdfTextPage[]> {\n const binary = await resolvePdftotext();\n const stdout = await run(\n binary,\n [options.layout ? '-bbox-layout' : '-bbox', pdfPath, '-'],\n options.timeoutMs ?? 60_000\n );\n return parsePdfTextBbox(stdout);\n}\n","/**\n * Where the ink is on a rendered page, row by row.\n *\n * Text geometry says where the words are; it says nothing about a chart, a\n * table rule or a full-bleed image, and a page that holds only those would\n * read as blank. A low-resolution grayscale raster of the same PDF answers\n * the other question — how far down the page anything at all was painted —\n * and that is what the page-fill rule needs. `pdftoppm -gray` writes one\n * PGM (P5) file per page: an ASCII header, then one byte per pixel, so\n * reading it needs no image library.\n */\n\nimport { execFile } from 'child_process';\nimport { promises as fs } from 'fs';\nimport os from 'os';\nimport path from 'path';\n\n/** One page's ink profile, as cached beside its text geometry. */\nexport interface PdfPageInk {\n /** Raster rows the page was sampled into; row `r` spans `heightPt * r / rows` to `heightPt * (r + 1) / rows`. */\n rows: number;\n /** Row indices, ascending, where at least one pixel is darker than the ink threshold. */\n inked: number[];\n}\n\nexport interface Pgm {\n width: number;\n height: number;\n maxValue: number;\n /** Row-major, one value per pixel, `width * height` long. */\n pixels: Uint8Array;\n}\n\n/** Pixels at or below this value count as ink: dark enough to be text, a rule or a chart bar, not anti-aliasing haze. */\nexport const INK_THRESHOLD = 200;\n\n/** Dots per inch the fill measurement rasterizes at: an A4 page is ~280 rows, enough to place content to within 3pt. */\nexport const INK_DPI = 24;\n\n/** Parse a binary PGM (`P5`) as pdftoppm writes it: header tokens, one whitespace byte, then the pixels. */\nexport function parsePgm(buffer: Buffer): Pgm {\n const header = /^P5\\s+(?:#[^\\n]*\\n\\s*)*(\\d+)\\s+(\\d+)\\s+(\\d+)\\s/.exec(\n buffer.subarray(0, 64).toString('latin1')\n );\n if (!header) throw new Error('Not a binary PGM (P5) image.');\n const width = Number(header[1]);\n const height = Number(header[2]);\n const maxValue = Number(header[3]);\n if (maxValue > 255) throw new Error('16-bit PGM is not supported.');\n const start = header[0].length;\n // Check the Buffer's own length, not the view: a small Buffer lives in\n // Node's shared pool, where a longer view would silently read a neighbour.\n if (buffer.length - start < width * height) {\n throw new Error('PGM shorter than its declared size.');\n }\n const pixels = new Uint8Array(\n buffer.buffer,\n buffer.byteOffset + start,\n width * height\n );\n return { width, height, maxValue, pixels };\n}\n\n/** Rows carrying at least one pixel at or below `threshold`, ascending. */\nexport function inkedRows(pgm: Pgm, threshold = INK_THRESHOLD): number[] {\n const rows: number[] = [];\n for (let row = 0; row < pgm.height; row += 1) {\n const start = row * pgm.width;\n const end = start + pgm.width;\n for (let i = start; i < end; i += 1) {\n if (pgm.pixels[i] <= threshold) {\n rows.push(row);\n break;\n }\n }\n }\n return rows;\n}\n\nexport function pageInkFromPgm(buffer: Buffer, threshold?: number): PdfPageInk {\n const pgm = parsePgm(buffer);\n return { rows: pgm.height, inked: inkedRows(pgm, threshold) };\n}\n\nfunction pdftoppmCandidates(): string[] {\n const configured = process.env.PDFTOPPM_PATH?.trim();\n return [...new Set([...(configured ? [configured] : []), 'pdftoppm'])];\n}\n\nasync function run(\n binary: string,\n args: string[],\n timeoutMs: number,\n signal?: AbortSignal\n) {\n return new Promise<void>((resolve, reject) => {\n execFile(\n binary,\n args,\n {\n timeout: timeoutMs,\n maxBuffer: 16 * 1024 * 1024,\n windowsHide: true,\n ...(signal && { signal }),\n },\n (error) => (error ? reject(error) : resolve())\n );\n });\n}\n\n// Same memoization shape as the pdftotext resolution beside this file:\n// success is cached per process, failure retries on the next call.\nlet pdftoppmPromise: Promise<string> | undefined;\nasync function resolvePdftoppm(): Promise<string> {\n if (!pdftoppmPromise) {\n pdftoppmPromise = (async () => {\n for (const candidate of pdftoppmCandidates()) {\n try {\n await run(candidate, ['-v'], 10_000);\n return candidate;\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code;\n // pdftoppm -v prints to stderr and exits 0 on modern poppler; a\n // non-ENOENT failure still means the binary exists.\n if (code !== 'ENOENT' && code !== 'EACCES') return candidate;\n }\n }\n throw new Error(\n 'Page ink extraction needs pdftoppm (poppler), which was not found. ' +\n `Install poppler-utils or set PDFTOPPM_PATH (searched: ${pdftoppmCandidates().join(', ')}).`\n );\n })().catch((error) => {\n pdftoppmPromise = undefined;\n throw error;\n });\n }\n return pdftoppmPromise;\n}\n\nexport interface ExtractPdfPageInkOptions {\n dpi?: number;\n threshold?: number;\n /** A resolved pdftoppm path, when the caller already found one. */\n binary?: string;\n timeoutMs?: number;\n /** Aborting kills the pdftoppm process and rejects. */\n signal?: AbortSignal;\n}\n\n/**\n * Rasterize every page of a PDF in grayscale at a low resolution and read\n * back which rows carry ink. One pdftoppm spawn into a private temp dir,\n * removed before returning; the result is small enough to cache with the\n * text geometry.\n */\nexport async function extractPdfPageInk(\n pdfPath: string,\n options: ExtractPdfPageInkOptions = {}\n): Promise<PdfPageInk[]> {\n const binary = options.binary ?? (await resolvePdftoppm());\n const dpi = options.dpi ?? INK_DPI;\n const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'jto-page-ink-'));\n try {\n const prefix = path.join(dir, 'page');\n await run(\n binary,\n ['-gray', '-r', String(dpi), pdfPath, prefix],\n options.timeoutMs ?? 60_000,\n options.signal\n );\n const files = (await fs.readdir(dir))\n .filter((name) => /^page-\\d+\\.pgm$/.test(name))\n .sort(\n (a, b) =>\n Number(/\\d+/.exec(a)?.[0] ?? 0) - Number(/\\d+/.exec(b)?.[0] ?? 0)\n );\n const pages: PdfPageInk[] = [];\n for (const file of files) {\n pages.push(\n pageInkFromPgm(\n await fs.readFile(path.join(dir, file)),\n options.threshold\n )\n );\n }\n return pages;\n } finally {\n await fs.rm(dir, { recursive: true, force: true });\n }\n}\n","/**\n * Font names from a rendered PDF — the other half of substitution detection.\n *\n * `pdftotext -bbox` says where every word landed but not which face drew it.\n * `pdffonts` (poppler, next to pdftotext) lists every font the PDF embeds or\n * references; LibreOffice embeds the face it actually used, so a requested\n * family that never appears in that list was substituted on the way.\n *\n * Names are PostScript-style: a subset tag (`BAAAAA+`), the family with\n * spaces removed, and a style suffix (`-Bold`, `Medium-Regular`). Comparison\n * therefore happens on a folded form — lowercase alphanumerics only — where\n * \"Space Grotesk\" and `CAAAAA+SpaceGrotesk-Regular` meet as a prefix match.\n */\n\nimport { execFile } from 'child_process';\nimport * as path from 'path';\n\n/** One font row as `pdffonts` prints it. */\nexport interface PdfFontInfo {\n /** The name as printed, subset tag included. */\n name: string;\n /** The name without its subset tag: `DMSans-Regular`. */\n baseName: string;\n type: string;\n embedded: boolean;\n}\n\nconst ROW_PATTERN =\n /^(\\S+)\\s+(.+?)\\s+(\\S+)\\s+(yes|no)\\s+(yes|no)\\s+(yes|no)\\s+/;\n\n/**\n * Parse `pdffonts` output. Pure; the header and rule lines are skipped and\n * a row that does not fit the column layout is ignored rather than guessed.\n */\nexport function parsePdfFonts(stdout: string): PdfFontInfo[] {\n const fonts: PdfFontInfo[] = [];\n for (const line of stdout.split(/\\r?\\n/)) {\n if (line.startsWith('name ') || line.startsWith('---') || !line.trim()) {\n continue;\n }\n const match = ROW_PATTERN.exec(line);\n if (!match) continue;\n const name = match[1];\n fonts.push({\n name,\n baseName: name.replace(/^[A-Z]{6}\\+/, ''),\n type: match[2].trim(),\n embedded: match[4] === 'yes',\n });\n }\n return fonts;\n}\n\n/** Lowercase alphanumerics only: the form family names are compared in. */\nexport function foldFontName(value: string): string {\n return value.toLowerCase().replace(/[^a-z0-9]/g, '');\n}\n\n/**\n * Whether a requested family is present among the rendered fonts. A family\n * matches a PDF font whose folded base name starts with the folded family\n * — `dmsans` against `dmsanslightregular` — so style suffixes never split\n * one family into several.\n */\nexport function familyRendered(\n family: string,\n fonts: readonly PdfFontInfo[]\n): boolean {\n const folded = foldFontName(family);\n if (folded === '') return true;\n return fonts.some((font) => foldFontName(font.baseName).startsWith(folded));\n}\n\n/** Where to look for `pdffonts`, most explicit first. */\nfunction pdffontsCandidates(): string[] {\n const candidates: string[] = [];\n const configured = process.env.PDFFONTS_PATH?.trim();\n if (configured) candidates.push(configured);\n // Poppler ships its tools side by side; a configured pdftotext locates them.\n const sibling = process.env.PDFTOTEXT_PATH?.trim();\n if (sibling) {\n candidates.push(\n path.join(\n path.dirname(sibling),\n process.platform === 'win32' ? 'pdffonts.exe' : 'pdffonts'\n )\n );\n }\n candidates.push('pdffonts');\n return [...new Set(candidates)];\n}\n\n/** One spawn, stdout captured; nothing here touches the filesystem. */\nasync function run(\n binary: string,\n args: string[],\n timeoutMs: number\n): Promise<string> {\n return new Promise((resolve, reject) => {\n execFile(\n binary,\n args,\n { timeout: timeoutMs, maxBuffer: 16 * 1024 * 1024 },\n (error, stdout) => {\n if (error) reject(error);\n else resolve(stdout);\n }\n );\n });\n}\n\nlet pdffontsPromise: Promise<string> | undefined;\n/** The binary, memoized: success is cached, failure retries next call. */\nasync function resolvePdffonts(): Promise<string> {\n if (!pdffontsPromise) {\n pdffontsPromise = (async () => {\n for (const candidate of pdffontsCandidates()) {\n try {\n await run(candidate, ['-v'], 10_000);\n return candidate;\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code;\n if (code !== 'ENOENT' && code !== 'EACCES') return candidate;\n }\n }\n throw new Error(\n 'Font inspection needs pdffonts (poppler), which was not found. ' +\n 'Install poppler-utils or set PDFFONTS_PATH ' +\n `(searched: ${pdffontsCandidates().join(', ')}).`\n );\n })().catch((error) => {\n pdffontsPromise = undefined;\n throw error;\n });\n }\n return pdffontsPromise;\n}\n\n/** True when a `pdffonts` binary is reachable. */\nexport async function pdffontsAvailable(): Promise<boolean> {\n try {\n await resolvePdffonts();\n return true;\n } catch {\n return false;\n }\n}\n\n/** The fonts a PDF on disk carries. One spawn, stdout only. */\nexport async function extractPdfFonts(\n pdfPath: string,\n options: { timeoutMs?: number } = {}\n): Promise<PdfFontInfo[]> {\n const binary = await resolvePdffonts();\n const stdout = await run(binary, [pdfPath], options.timeoutMs ?? 30_000);\n return parsePdfFonts(stdout);\n}\n","/**\n * The rendered-certainty pass (#344): findings from what LibreOffice\n * actually laid out, mapped back to the pointers that authored them.\n *\n * Static rules predict; this pass measures. It reads per-word geometry from\n * the preview PDF, the fonts the PDF embeds, and the document's authored\n * text inventory, and reports the defects only ink can show: text past the\n * page or its frame, words drawn over each other, text that never rendered\n * (fully clipped, or set in a place poppler cannot read), faces substituted\n * on the way, pages with nothing on them, headings stranded at a page foot\n * and paragraphs split into a lone line.\n *\n * Every finding carries `certainty: 'rendered'` and an explicit mapping\n * status. A defect whose words match no inventory entry is still reported,\n * at the document root, as `unmapped` — an unmapped finding is a mapping\n * gap to close, never a defect to hide.\n *\n * Pure: geometry and fonts are extracted by the caller (see\n * `extractPdfTextGeometry`, `extractPdfFonts`), so every rule here is\n * testable from captured fixtures with no converter on the host.\n *\n * The checks draft findings; the quality engine turns them into\n * diagnostics. Each check is one rule of `RENDERED_QUALITY_RULES`, so a\n * profile can switch one off or move its severity, a policy can suppress\n * one at a pointer and a gate can make one blocking — the same levers every\n * static rule answers to, applied to the pass that measures.\n */\n\nimport {\n QualityEngine,\n type QualityAnalysis,\n type QualityAnalyzeOptions,\n type QualityDiagnostic,\n type QualityFact,\n type QualityRuleFinding,\n} from '@json-to-office/quality';\nimport type { PdfFontInfo } from './pdf-fonts';\nimport { familyRendered } from './pdf-fonts';\nimport type { PdfTextPage, PdfTextWord } from './pdf-text-geometry';\nimport { RENDERED_QUALITY_RULES, type RenderedRuleId } from './rendered-rules';\nimport {\n assignInventory,\n CHROME_BAND,\n type InventoryEntry,\n type InventoryMatch,\n type MappingStatus,\n type OccurrencePart,\n} from './rendered-text-match';\n\n/** One authored string the pass can match rendered words back to. */\nexport interface RenderedTextEntry extends InventoryEntry {\n role:\n | 'heading'\n | 'body'\n | 'list-item'\n | 'table-header'\n | 'table-cell'\n | 'statistic'\n | 'caption'\n | 'toc-entry'\n | 'chrome'\n | 'slide-text';\n level?: number;\n /** A declared box the text must fit: a docx frame, a pptx text box. */\n box?: { widthPt?: number; heightPt?: number };\n}\n\nexport interface RequestedFont {\n family: string;\n /** Where the family was requested, when the document names it. */\n path?: string;\n /**\n * The document supplied a source for the family (a file, a URL), so the\n * preview should have had it and a substitution is the document's defect.\n * Without one the family is a host font — Calibri on a Mac without Office\n * — and the substitution says something about the preview, not the file.\n */\n declared?: boolean;\n}\n\nexport interface RenderedAnalysisInput {\n format: 'docx' | 'pptx';\n /** Renderer identity, for a profile that declares renderer targets. */\n renderer?: string;\n pages: readonly PdfTextPage[];\n inventory: readonly RenderedTextEntry[];\n /** Fonts the PDF carries; `undefined` when the host could not inspect them. */\n fonts?: readonly PdfFontInfo[];\n requestedFonts?: readonly RequestedFont[];\n}\n\n/**\n * Below this share of the body area painted, a page that is neither the\n * cover nor the last is under-filled. Judges called a page \"two-thirds\n * blank\" a defect and a page that ends at its midpoint acceptable, so the\n * line sits between.\n */\nexport const MINIMUM_PAGE_FILL = 0.5;\n\n/**\n * The last page may end wherever the text ends; but a last page that holds\n * only the tail — the notes, a source line, one orphaned paragraph — is a\n * stub, and the judge names it as one. Below this share it is reported.\n */\nexport const MINIMUM_LAST_PAGE_FILL = 0.25;\n\n/** How a finding reached its pointer — always on `context.mapping`. */\nexport type RenderedMapping = 'mapped' | 'ambiguous' | 'unmapped';\n\nexport interface RenderedAnalysisSummary {\n pages: number;\n words: number;\n /** Inventory entries by mapping outcome. */\n inventory: Record<MappingStatus, number>;\n /** Findings by mapping outcome, after suppressions. */\n findings: Record<RenderedMapping, number>;\n fonts?: { requested: number; substituted: number };\n /** Findings a policy suppression removed. */\n suppressed: number;\n /** Whether a policy gate made any finding blocking. */\n blocked: boolean;\n /** Whether a policy `maxDiagnostics` budget cut the findings returned. */\n truncated: boolean;\n /** The quality profile the pass ran under, when one applied. */\n profileId?: string;\n}\n\nexport interface RenderedAnalysis {\n /** The pass's diagnostics, as the quality engine finalised them. */\n findings: readonly QualityDiagnostic[];\n summary: RenderedAnalysisSummary;\n /** The engine's own account: evaluated rules, rule errors, truncation. */\n analysis: QualityAnalysis;\n}\n\n/**\n * The checks' output before the engine: a rule finding with its rule id.\n * Code, category and default severity are the rule's, declared once in\n * `RENDERED_QUALITY_RULES`; a draft states a severity only where one rule\n * varies it per finding.\n */\nexport interface RenderedFindingDraft extends QualityRuleFinding {\n ruleId: RenderedRuleId;\n path: string;\n}\n\nexport interface RenderedDraft {\n drafts: readonly RenderedFindingDraft[];\n /** The same drafts partitioned by rule, for the pack's `evaluate`. */\n byRule: ReadonlyMap<RenderedRuleId, readonly RenderedFindingDraft[]>;\n /** Inventory entries by mapping outcome. */\n inventory: Record<MappingStatus, number>;\n fonts?: { requested: number; substituted: number };\n}\n\n/** What the rules read: the whole input of the pass, as one fact. */\nexport interface RenderedGeometryFact extends QualityFact {\n kind: 'rendered/geometry';\n input: RenderedAnalysisInput;\n}\n\nexport function isRenderedGeometryFact(\n fact: QualityFact\n): fact is RenderedGeometryFact {\n return fact.kind === 'rendered/geometry';\n}\n\nconst drafts = new WeakMap<RenderedGeometryFact, RenderedDraft>();\n\n/** The pass's drafts for a fact, computed once and shared by every rule. */\nexport function renderedDraftFor(fact: RenderedGeometryFact): RenderedDraft {\n let draft = drafts.get(fact);\n if (!draft) {\n draft = draftRenderedFindings(fact.input);\n drafts.set(fact, draft);\n }\n return draft;\n}\n\n/** Spill below this is sub-visual: renderer rounding, descender fuzz. */\nexport const VISIBLE_SPILL_PT = 2;\n/** Two words overlap when their intersection covers this much of the smaller. */\nconst OVERLAP_FRACTION = 0.3;\n/**\n * The share of a word box, at its top and at its bottom, that is font metric\n * rather than ink. Poppler sizes a word by its face's ascent and descent —\n * 1.1 to 1.6 times the type size across the bundled and stock faces — while\n * capitals start well below the ascent and figures sit on the baseline. On\n * the stock deck templates, display titles set tight run their word boxes up\n * to 16% of their height past boxes they visibly fit, and 31% into the line\n * under them; a label under a large figure sits inside the figure's descent.\n * Text that truly overflows runs 70% of a line or more. Spill and overlap are\n * judged on the band that is left, the ink.\n */\nexport const WORD_BOX_SLACK = 0.2;\n\n/** A word's vertical extent with its ascent and descent slack taken off. */\nfunction inkBand(word: PdfTextWord): { yMin: number; yMax: number } {\n const slack = WORD_BOX_SLACK * Math.max(0, word.yMax - word.yMin);\n return { yMin: word.yMin + slack, yMax: word.yMax - slack };\n}\n\ninterface FindingDraft {\n ruleId: RenderedRuleId;\n /** Only for a rule whose severity varies per finding. */\n severity?: QualityRuleFinding['severity'];\n message: string;\n path: string;\n mapping: RenderedMapping;\n page?: number;\n suggestion?: string;\n relatedPaths?: string[];\n evidence?: QualityDiagnostic['evidence'];\n context?: Record<string, unknown>;\n}\n\n/**\n * A draft as the engine reads it. Every rendered finding carries its\n * mapping status and page on `context`, so one place decides that rather\n * than eight rules; severity and blocking are the engine's to settle.\n */\nfunction finding(draft: FindingDraft): RenderedFindingDraft {\n const { mapping, page, context, ...rest } = draft;\n return {\n ...rest,\n context: {\n ...context,\n mapping,\n ...(page !== undefined && { page }),\n },\n };\n}\n\nfunction round(value: number): number {\n return Math.round(value * 10) / 10;\n}\n\n/** Ink the two words share, in square points; 0 when they miss. */\nfunction intersects(a: PdfTextWord, b: PdfTextWord): number {\n const inkA = inkBand(a);\n const inkB = inkBand(b);\n const w = Math.min(a.xMax, b.xMax) - Math.max(a.xMin, b.xMin);\n const h = Math.min(inkA.yMax, inkB.yMax) - Math.max(inkA.yMin, inkB.yMin);\n return w > 0 && h > 0 ? w * h : 0;\n}\n\nfunction area(w: PdfTextWord): number {\n const ink = inkBand(w);\n return Math.max(0, w.xMax - w.xMin) * Math.max(0, ink.yMax - ink.yMin);\n}\n\n/** Text as a message can quote it: whitespace collapsed, length capped. */\nfunction excerpt(text: string, max = 60): string {\n const flat = text.replace(/\\s+/g, ' ').trim();\n return flat.length > max ? `${flat.slice(0, max - 1)}…` : flat;\n}\n\n/** Word → the inventory entry whose match covers it. */\nfunction wordOwners(\n matches: readonly InventoryMatch<RenderedTextEntry>[]\n): Map<string, InventoryMatch<RenderedTextEntry>> {\n const owners = new Map<string, InventoryMatch<RenderedTextEntry>>();\n for (const match of matches) {\n if (match.status !== 'mapped') continue;\n for (const o of match.occurrences) {\n for (const part of o.parts) {\n for (const w of part.words) owners.set(`${part.pageIndex}:${w}`, match);\n }\n }\n }\n return owners;\n}\n\n/** Line index for every word of a page, when poppler grouped lines. */\nconst lineMaps = new WeakMap<PdfTextPage, Map<number, number>>();\nfunction lineOfWord(page: PdfTextPage): Map<number, number> {\n const cached = lineMaps.get(page);\n if (cached) return cached;\n const lines = new Map<number, number>();\n page.lines.forEach((line, index) => {\n for (const w of line.words) lines.set(w, index);\n });\n lineMaps.set(page, lines);\n return lines;\n}\n\n/** The box around every part of an occurrence. */\nfunction unionBox(parts: readonly OccurrencePart[]) {\n return parts.reduce(\n (box, part) => ({\n xMin: Math.min(box.xMin, part.xMin),\n yMin: Math.min(box.yMin, part.yMin),\n xMax: Math.max(box.xMax, part.xMax),\n yMax: Math.max(box.yMax, part.yMax),\n }),\n { xMin: Infinity, yMin: Infinity, xMax: -Infinity, yMax: -Infinity }\n );\n}\n\n/**\n * Run every check and return its drafts, ungated. `analyzeRenderedDocument`\n * is the entry point; the rule pack reads this through `renderedDraftFor`,\n * once per fact, so eight rules share one matching pass.\n */\nfunction draftRenderedFindings(input: RenderedAnalysisInput): RenderedDraft {\n const { pages, format } = input;\n const { matches, chromeWords } = assignInventory(pages, input.inventory);\n const owners = wordOwners(matches);\n const findings: RenderedFindingDraft[] = [];\n\n const ownerOf = (pageIndex: number, word: number) =>\n owners.get(`${pageIndex}:${word}`);\n\n // -- Clip: a word past the page edge. Reported once per entry, with the\n // count, so a paragraph running off the page is one finding, not sixty.\n const clipped = new Map<\n string,\n {\n match?: InventoryMatch<RenderedTextEntry>;\n words: PdfTextWord[];\n page: number;\n maxPt: number;\n }\n >();\n pages.forEach((page, pageIndex) => {\n page.words.forEach((word, index) => {\n const beyond = Math.max(\n -word.xMin,\n word.xMax - page.widthPt,\n -word.yMin,\n word.yMax - page.heightPt\n );\n if (beyond <= VISIBLE_SPILL_PT) return;\n const owner = ownerOf(pageIndex, index);\n const key = owner ? `entry:${owner.entry.path}` : `page:${pageIndex}`;\n const bucket = clipped.get(key) ?? {\n match: owner,\n words: [],\n page: pageIndex + 1,\n maxPt: 0,\n };\n bucket.words.push(word);\n bucket.maxPt = Math.max(bucket.maxPt, beyond);\n clipped.set(key, bucket);\n });\n });\n for (const bucket of clipped.values()) {\n const sample = excerpt(bucket.words.map((w) => w.text).join(' '));\n findings.push(\n finding({\n ruleId: 'rendered/clip',\n mapping: bucket.match ? 'mapped' : 'unmapped',\n page: bucket.page,\n path: bucket.match?.entry.path ?? '',\n message: `${bucket.words.length} word${bucket.words.length === 1 ? '' : 's'} rendered past the page edge on page ${bucket.page} by up to ${round(bucket.maxPt)} pt: \"${sample}\".`,\n suggestion:\n 'Shorten the text, allow it to wrap, or widen the column or frame that holds it; text outside the page is cut off in print and in Word.',\n evidence: {\n summary: 'Word boxes beyond the page bounds',\n actual: round(bucket.maxPt),\n expected: 0,\n unit: 'pt',\n values: { words: bucket.words.length },\n },\n })\n );\n }\n\n // -- Truncation: only the head of an entry rendered. The tail went past\n // a frame, a box or the page, wherever poppler stopped seeing it.\n for (const match of matches) {\n if (!match.partial || match.status !== 'mapped') continue;\n const o = match.occurrences[0];\n const shown = Math.round(\n (100 * match.partial.matchedChars) / match.partial.totalChars\n );\n findings.push(\n finding({\n ruleId: 'rendered/clip',\n mapping: 'mapped',\n page: o.endPageIndex + 1,\n path: match.entry.path,\n message: `\"${excerpt(match.entry.text)}\" is cut off: about ${shown}% of it rendered on page ${o.endPageIndex + 1} and the rest is nowhere on the page.`,\n suggestion:\n 'Shorten the text or enlarge the frame, box or cell that holds it; what did not render here will not print either.',\n evidence: {\n summary: 'Share of the authored text found in the PDF',\n actual: shown,\n expected: 100,\n unit: '%',\n values: match.partial,\n },\n context: { kind: 'truncated' },\n })\n );\n }\n\n // -- Spill: a matched entry drawn larger than the box it declared. Where\n // the entry says where its box is — a slide text box — the ink is held to\n // the box's own edges, top and bottom; a box that only states its size is\n // held to that size.\n for (const match of matches) {\n const box = match.entry.box;\n const region = match.entry.region;\n if (!box || match.status !== 'mapped') continue;\n for (const o of match.occurrences) {\n const boxHeight = box.heightPt;\n const byEdges = region !== undefined && boxHeight !== undefined;\n if (region && boxHeight !== undefined) {\n const words = o.parts.flatMap((part) =>\n part.words.map((w) => pages[part.pageIndex].words[w])\n );\n const inkTop = Math.min(...words.map((w) => inkBand(w).yMin));\n const inkBottom = Math.max(...words.map((w) => inkBand(w).yMax));\n const edges = [\n { edge: 'bottom', over: inkBottom - region.yMax, at: inkBottom },\n { edge: 'top', over: region.yMin - inkTop, at: inkTop },\n ].sort((a, b) => b.over - a.over);\n const [worst] = edges;\n if (worst.over > VISIBLE_SPILL_PT) {\n findings.push(\n finding({\n ruleId: 'rendered/spill',\n mapping: 'mapped',\n page: o.pageIndex + 1,\n path: match.entry.path,\n message: `\"${excerpt(match.entry.text)}\" runs ${round(worst.over)} pt past the ${worst.edge} of its ${round(boxHeight)} pt box on page ${o.pageIndex + 1}.`,\n suggestion:\n 'Shorten the text, reduce its size, or enlarge the box; the renderer let it spill past the edge the author drew.',\n evidence: {\n summary: 'Rendered ink against the edge of the declared box',\n actual: round(worst.at),\n expected: round(\n worst.edge === 'bottom' ? region.yMax : region.yMin\n ),\n unit: 'pt',\n values: { edge: worst.edge, overPt: round(worst.over) },\n },\n })\n );\n continue;\n }\n }\n const extent = unionBox(o.parts);\n const rendered = {\n heightPt: extent.yMax - extent.yMin,\n widthPt: extent.xMax - extent.xMin,\n };\n const worst = (['heightPt', 'widthPt'] as const)\n .filter((axis) => box[axis] !== undefined)\n .filter((axis) => !(byEdges && axis === 'heightPt'))\n .map((axis) => ({\n axis,\n declared: box[axis] as number,\n rendered: rendered[axis],\n over: rendered[axis] - (box[axis] as number),\n }))\n .sort((a, b) => b.over - a.over)[0];\n if (!worst || worst.over <= VISIBLE_SPILL_PT) continue;\n const word = worst.axis === 'heightPt' ? 'taller' : 'wider';\n findings.push(\n finding({\n ruleId: 'rendered/spill',\n mapping: 'mapped',\n page: o.pageIndex + 1,\n path: match.entry.path,\n message: `\"${excerpt(match.entry.text)}\" rendered ${round(worst.over)} pt ${word} than its ${round(worst.declared)} pt box on page ${o.pageIndex + 1}.`,\n suggestion:\n 'Shorten the text, reduce its size, or enlarge the box; the renderer let it spill past the edge the author drew.',\n evidence: {\n summary: 'Rendered extent against the declared box',\n actual: round(worst.rendered),\n expected: round(worst.declared),\n unit: 'pt',\n values: { marginPt: round(-worst.over) },\n },\n })\n );\n }\n }\n\n // -- Overlap: two words from different lines drawn over each other.\n pages.forEach((page, pageIndex) => {\n const lines = lineOfWord(page);\n const seen = new Set<string>();\n const words = page.words;\n // Sorted by top edge so the inner loop stops at the first word that\n // starts below the outer one: nothing after it can intersect.\n const byTop = words\n .map((_, i) => i)\n .sort((a, b) => words[a].yMin - words[b].yMin);\n for (let p = 0; p < byTop.length; p++) {\n const i = byTop[p];\n const a = words[i];\n for (let q = p + 1; q < byTop.length; q++) {\n const j = byTop[q];\n const b = words[j];\n if (b.yMin >= a.yMax) break;\n const shared = intersects(a, b);\n if (shared <= 0) continue;\n const lineA = lines.get(i);\n if (lineA !== undefined && lineA === lines.get(j)) continue;\n const smaller = Math.min(area(a), area(b));\n if (smaller <= 0 || shared / smaller < OVERLAP_FRACTION) continue;\n const ownerA = ownerOf(pageIndex, i);\n const ownerB = ownerOf(pageIndex, j);\n const pathA = ownerA?.entry.path ?? '';\n const pathB = ownerB?.entry.path ?? '';\n // One finding per authored pair; unmapped pairs are each their own.\n const key =\n pathA && pathB\n ? [pathA, pathB].sort().join('|')\n : `${Math.min(i, j)}:${Math.max(i, j)}`;\n if (seen.has(key)) continue;\n seen.add(key);\n const mapping: RenderedMapping =\n ownerA && ownerB\n ? 'mapped'\n : ownerA || ownerB\n ? 'ambiguous'\n : 'unmapped';\n findings.push(\n finding({\n ruleId: 'rendered/overlap',\n mapping,\n page: pageIndex + 1,\n path: pathA || pathB,\n ...(pathA && pathB && pathA !== pathB && { relatedPaths: [pathB] }),\n message: `\"${a.text}\" and \"${b.text}\" are drawn over each other on page ${pageIndex + 1}.`,\n suggestion:\n 'Give the two elements separate room: move one, reduce the text, or increase the spacing between them.',\n evidence: {\n summary: 'Word boxes intersect',\n actual: round((100 * shared) / smaller),\n expected: 0,\n unit: '% of the smaller word',\n },\n })\n );\n }\n }\n });\n\n // -- Missing: authored text that never rendered anywhere.\n for (const match of matches) {\n if (match.status !== 'missing') continue;\n findings.push(\n finding({\n ruleId: 'rendered/text-missing',\n mapping: 'mapped',\n path: match.entry.path,\n message: `\"${excerpt(match.entry.text)}\" does not appear anywhere in the rendered pages.`,\n suggestion:\n 'The text was fully clipped, hidden behind another element, or set somewhere the renderer could not place it; preview the page it belongs to.',\n evidence: { summary: 'No rendered occurrence of the authored text' },\n context: { role: match.entry.role },\n })\n );\n }\n\n // -- Font substitution: a requested family absent from the PDF.\n let substituted = 0;\n if (input.fonts && input.fonts.length > 0 && input.requestedFonts) {\n const seen = new Set<string>();\n for (const requested of input.requestedFonts) {\n const family = requested.family.trim();\n if (family === '' || seen.has(family.toLowerCase())) continue;\n seen.add(family.toLowerCase());\n if (familyRendered(family, input.fonts)) continue;\n substituted += 1;\n findings.push(\n finding({\n ruleId: 'rendered/font-substituted',\n severity: requested.declared ? 'warning' : 'info',\n mapping: requested.path ? 'mapped' : 'unmapped',\n path: requested.path ?? '',\n message: requested.declared\n ? `\"${family}\" was declared with a source but the rendered PDF embeds no face of it; the renderer substituted another family.`\n : `\"${family}\" is not installed on this host, so the preview substituted another family; a recipient with the font sees the intended face.`,\n suggestion: requested.declared\n ? 'Check the font source: the file or URL must resolve to a face LibreOffice can load, or the recipient will see the same substitution.'\n : 'Install the family on the host for a faithful preview, or declare a source for it in the document fonts so every renderer has it.',\n context: { declared: requested.declared === true },\n evidence: {\n summary: 'Requested family against embedded PDF fonts',\n expected: family,\n actual: [...new Set(input.fonts.map((f) => f.baseName))].join(', '),\n },\n })\n );\n }\n }\n\n // -- Empty pages: no body text. In docx the running head and footer repeat\n // on a stray page too, so chrome does not count as content there; a deck\n // declares no chrome inventory, so a slide with any word is not empty (an\n // image slide carrying its slide number is the legitimate case). A\n // full-page figure is legitimate, so this is information, not a warning.\n pages.forEach((page, pageIndex) => {\n const hasBody =\n format === 'docx'\n ? page.words.some((_, i) => !chromeWords.has(`${pageIndex}:${i}`))\n : page.words.length > 0;\n if (hasBody) return;\n const kind = page.words.length > 0 ? 'chrome-only' : 'blank';\n findings.push(\n finding({\n ruleId: 'rendered/empty-page',\n mapping: 'unmapped',\n page: pageIndex + 1,\n path: '',\n message: `Page ${pageIndex + 1} carries ${kind === 'chrome-only' ? 'only its running head or footer' : 'no text'}. A full-page figure is fine; a blank page from a stray break or an empty section is not.`,\n suggestion:\n 'Preview the page; remove the page break or the empty section if nothing was meant to be there.',\n context: { kind },\n })\n );\n });\n\n // -- Under-filled page (docx): the ink stops well above the footer on a\n // page that is neither the cover nor the end, so a page break, not the\n // content, decided where the page ended. Ink, not words: a chart or a\n // table fills a page without a word the geometry can see.\n if (format === 'docx') {\n pages.forEach((page, pageIndex) => {\n if (!page.ink || pageIndex === 0) return;\n const last = pageIndex === pages.length - 1;\n const rowPt = page.heightPt / page.ink.rows;\n let headerBottom = 0;\n let footerTop = page.heightPt;\n page.words.forEach((w, i) => {\n if (!chromeWords.has(`${pageIndex}:${i}`)) return;\n if (w.yMax <= page.heightPt * CHROME_BAND) {\n headerBottom = Math.max(headerBottom, w.yMax);\n } else if (w.yMin >= page.heightPt * (1 - CHROME_BAND)) {\n footerTop = Math.min(footerTop, w.yMin);\n }\n });\n // Skip the row under the header text too: its hairline is chrome.\n const body = page.ink.inked.filter(\n (row) =>\n row * rowPt > headerBottom + rowPt && (row + 1) * rowPt < footerTop\n );\n if (body.length === 0) return; // the empty-page rule owns this page\n const top = body[0] * rowPt;\n const bottom = (body[body.length - 1] + 1) * rowPt;\n const fill = Math.round(((bottom - top) / (footerTop - top)) * 100) / 100;\n if (fill >= (last ? MINIMUM_LAST_PAGE_FILL : MINIMUM_PAGE_FILL)) return;\n // The section owning the page: the top-level child of the last body\n // text mapped onto it.\n let owner: { path: string; yMax: number } | undefined;\n for (const match of matches) {\n if (match.status !== 'mapped' || match.entry.repeats) continue;\n for (const o of match.occurrences) {\n // Every part on this page: a paragraph that starts here and runs\n // on still owns this page through the part it left behind.\n for (const part of o.parts) {\n if (part.pageIndex !== pageIndex) continue;\n if (!owner || part.yMax > owner.yMax) {\n owner = { path: match.entry.path, yMax: part.yMax };\n }\n }\n }\n }\n // A last page belongs to the section that closes the document, which\n // is not always the section its text was authored in: the notes are\n // painted from source slots the sections that cited them own, so the\n // text furthest down a stub last page names a section near the front.\n // The repair is a page break in the closing section, so that is the\n // pointer the finding carries — but only when the page mapped at all,\n // since `mapping` still says whether the words on it were owned.\n const owned = owner\n ? /^\\/children\\/\\d+/.exec(owner.path)?.[0]\n : undefined;\n const section =\n owned && last ? closingSection(input.inventory) ?? owned : owned;\n findings.push(\n finding({\n ruleId: 'rendered/page-underfilled',\n mapping: section ? 'mapped' : 'unmapped',\n page: pageIndex + 1,\n path: section ?? '',\n message: last\n ? `Page ${pageIndex + 1}, the last, is ${Math.round(fill * 100)}% filled: only the tail of the document reached it.`\n : `Page ${pageIndex + 1} is ${Math.round(fill * 100)}% filled: its content stops ${Math.round(footerTop - bottom)}pt above the footer and the next page begins anyway.`,\n suggestion: last\n ? 'Set `pageBreak: true` on this section — or on the one before it, when this section already starts a page — so the closing argument and its notes share one designed page instead of the last page taking only their tail, then re-preview. Keeping the notes with the paragraph above them moves the tail, not the page.'\n : 'Merge this section into its neighbour, or give the page the table or chart its argument owes. Do not pad it with prose: a short section on its own page reads as unfinished, a padded one reads as filler.',\n evidence: {\n summary: 'Share of the body area the page paints, header to footer',\n expected: `≥ ${Math.round((last ? MINIMUM_LAST_PAGE_FILL : MINIMUM_PAGE_FILL) * 100)}%`,\n actual: fill,\n },\n context: { fill, kind: last ? 'last-page' : 'middle-page' },\n })\n );\n });\n }\n\n // -- Stranded heading (docx): the last body line on its page.\n if (format === 'docx') {\n for (const match of matches) {\n if (match.entry.role !== 'heading' || match.status !== 'mapped') continue;\n const o = match.occurrences[0];\n const part = o.parts[o.parts.length - 1];\n if (part.pageIndex >= pages.length - 1) continue;\n const page = pages[part.pageIndex];\n const below = page.words.some(\n (w, i) =>\n !chromeWords.has(`${part.pageIndex}:${i}`) &&\n !part.words.includes(i) &&\n w.yMin >= part.yMax - 1\n );\n if (below) continue;\n findings.push(\n finding({\n ruleId: 'rendered/heading-stranded',\n mapping: 'mapped',\n page: part.pageIndex + 1,\n path: match.entry.path,\n message: `Heading \"${excerpt(match.entry.text)}\" is the last line on page ${part.pageIndex + 1}; its content starts on the next page.`,\n suggestion:\n 'Keep the heading with its first paragraph (keep-with-next), or move a page break so they land together.',\n })\n );\n }\n\n // -- Split paragraph: one line alone on either side of a page break.\n for (const match of matches) {\n const role = match.entry.role;\n if (\n (role !== 'body' && role !== 'list-item') ||\n match.status !== 'mapped'\n )\n continue;\n const o = match.occurrences[0];\n if (o.pageIndex === o.endPageIndex) continue;\n const first = o.parts[0];\n const last = o.parts[o.parts.length - 1];\n const linesOn = (part: OccurrencePart) => {\n const lines = lineOfWord(pages[part.pageIndex]);\n const set = new Set<number>();\n for (const w of part.words) {\n const line = lines.get(w);\n if (line !== undefined) set.add(line);\n }\n return set.size;\n };\n const before = linesOn(first);\n const after = linesOn(last);\n if (before === 0 || after === 0) continue;\n if (before > 1 && after > 1) continue;\n const which = before === 1 ? 'orphan' : 'widow';\n findings.push(\n finding({\n ruleId: 'rendered/paragraph-split',\n mapping: 'mapped',\n page: (which === 'orphan' ? first.pageIndex : last.pageIndex) + 1,\n path: match.entry.path,\n message:\n which === 'orphan'\n ? `\"${excerpt(match.entry.text)}\" leaves one line alone at the foot of page ${first.pageIndex + 1}.`\n : `\"${excerpt(match.entry.text)}\" leaves one line alone at the top of page ${last.pageIndex + 1}.`,\n suggestion:\n 'Edit the paragraph a few words shorter or longer, or set widow/orphan control on the style.',\n evidence: {\n summary: 'Lines on each side of the page break',\n values: { linesBefore: before, linesAfter: after },\n },\n context: { kind: which },\n })\n );\n }\n\n // -- Split table: the header alone at a page foot, or one row alone on\n // either side of the break. A table is grouped by its own pointer, and a\n // row by the index in the cell pointers the inventory wrote.\n // A cell pointer ends at the cell or inside it — `.../cells/2/content`\n // when the cell is an object rather than a bare string — and a block's\n // table reports at the slot that filled it, `.../props/slots/columns/…`.\n const TABLE_CELL =\n /^(.*)\\/props\\/(?:slots\\/)?columns\\/(\\d+)\\/(?:header|cells\\/(\\d+))(?:\\/.*)?$/;\n const tables = new Map<\n string,\n {\n pages: Map<number, Set<number>>;\n paths: Map<number, { column: number; path: string }>;\n }\n >();\n for (const match of matches) {\n if (\n (match.entry.role !== 'table-header' &&\n match.entry.role !== 'table-cell') ||\n match.status !== 'mapped'\n )\n continue;\n const parsed = TABLE_CELL.exec(match.entry.path);\n if (!parsed) continue;\n const [, table, column, row] = parsed;\n // A header carries no row index; -1 orders it above every row.\n const rowIndex = row === undefined ? -1 : Number(row);\n const entry = tables.get(table) ?? {\n pages: new Map<number, Set<number>>(),\n paths: new Map<number, { column: number; path: string }>(),\n };\n for (const occurrence of match.occurrences)\n for (const part of occurrence.parts) {\n const rows = entry.pages.get(part.pageIndex) ?? new Set<number>();\n rows.add(rowIndex);\n entry.pages.set(part.pageIndex, rows);\n }\n // The row's leftmost cell, so a finding always lands on the same\n // pointer however the matcher happened to order the columns.\n const held = entry.paths.get(rowIndex);\n if (!held || Number(column) < held.column)\n entry.paths.set(rowIndex, {\n column: Number(column),\n path: match.entry.path,\n });\n tables.set(table, entry);\n }\n for (const [table, { pages: onPage, paths }] of tables) {\n const ordered = [...onPage.keys()].sort((a, b) => a - b);\n if (ordered.length < 2) continue;\n const bodyRows = (page: number) =>\n [...(onPage.get(page) ?? [])].filter((row) => row >= 0);\n const report = (\n kind: 'header-alone' | 'orphan-row' | 'widow-row',\n page: number,\n path: string,\n message: string,\n suggestion: string\n ) =>\n findings.push(\n finding({\n ruleId: 'rendered/table-split',\n mapping: 'mapped',\n page: page + 1,\n path,\n message,\n suggestion,\n context: { kind, table },\n })\n );\n const firstPage = ordered[0];\n const lastPage = ordered[ordered.length - 1];\n if (onPage.get(firstPage)!.has(-1) && bodyRows(firstPage).length === 0) {\n report(\n 'header-alone',\n firstPage,\n paths.get(-1)!.path,\n `The table's header is the only part of it on page ${firstPage + 1}; its rows start on the next page.`,\n 'Keep the header with its first rows, or move the table so it starts on the next page.'\n );\n continue;\n }\n const opening = bodyRows(firstPage);\n const closing = bodyRows(lastPage);\n if (opening.length === 1)\n report(\n 'orphan-row',\n firstPage,\n paths.get(opening[0])!.path,\n `The table leaves one row alone at the foot of page ${firstPage + 1}.`,\n 'Keep the table together on one page, or edit the rows above it so the break falls elsewhere.'\n );\n else if (closing.length === 1)\n report(\n 'widow-row',\n lastPage,\n paths.get(closing[0])!.path,\n `The table leaves one row alone at the top of page ${lastPage + 1}.`,\n 'Keep the table together on one page, or edit the rows above it so the break falls elsewhere.'\n );\n }\n }\n\n const inventory: Record<MappingStatus, number> = {\n mapped: 0,\n ambiguous: 0,\n missing: 0,\n skipped: 0,\n };\n for (const match of matches) inventory[match.status] += 1;\n\n const byRule = new Map<RenderedRuleId, RenderedFindingDraft[]>();\n for (const draft of findings) {\n const bucket = byRule.get(draft.ruleId);\n if (bucket) bucket.push(draft);\n else byRule.set(draft.ruleId, [draft]);\n }\n\n return {\n drafts: findings,\n byRule,\n inventory,\n // The same gate the check itself runs under: an empty font list\n // verified nothing, and must not read as \"nothing was substituted\".\n ...(input.fonts &&\n input.fonts.length > 0 &&\n input.requestedFonts && {\n fonts: { requested: input.requestedFonts.length, substituted },\n }),\n };\n}\n\nlet engine: QualityEngine | undefined;\n\n/** The pass's engine: the rendered rule pack, built once. */\nfunction renderedEngine(): QualityEngine {\n engine ??= new QualityEngine(RENDERED_QUALITY_RULES.rules);\n return engine;\n}\n\n/**\n * The top-level section that closes the document: the highest `/children/n`\n * any authored string sits under. Read off the inventory because the\n * analysis never sees the document — the inventory is in document order and\n * every entry carries the pointer it was written at.\n */\nfunction closingSection(\n inventory: readonly RenderedTextEntry[]\n): string | undefined {\n let highest: number | undefined;\n for (const entry of inventory) {\n if (entry.repeats) continue;\n const index = /^\\/children\\/(\\d+)/.exec(entry.path)?.[1];\n if (index === undefined) continue;\n const value = Number(index);\n if (highest === undefined || value > highest) highest = value;\n }\n return highest === undefined ? undefined : `/children/${highest}`;\n}\n\n/**\n * Run the pass. Geometry, fonts and inventory become one `rendered/geometry`\n * fact; the rendered rule pack reads it under the caller's profile and\n * policy, so the findings come back suppressed, re-severed and gated the\n * way `jto_validate`'s would.\n */\nexport function analyzeRenderedDocument(\n input: RenderedAnalysisInput,\n options: QualityAnalyzeOptions = {}\n): RenderedAnalysis {\n const fact: RenderedGeometryFact = {\n id: 'rendered:geometry',\n kind: 'rendered/geometry',\n path: '',\n input,\n };\n const analysis = renderedEngine().analyzeSync(\n {\n format: input.format,\n model: undefined,\n facts: [fact],\n provenance: {},\n ...(input.renderer !== undefined && { renderer: input.renderer }),\n },\n options\n );\n // The rules met any fault in the measuring pass as a rule error; the\n // summary must not turn that into a crash of an advisory pass.\n let draft: RenderedDraft | undefined;\n try {\n draft = renderedDraftFor(fact);\n } catch {\n draft = undefined;\n }\n const byMapping: Record<RenderedMapping, number> = {\n mapped: 0,\n ambiguous: 0,\n unmapped: 0,\n };\n for (const f of analysis.diagnostics) {\n byMapping[(f.context?.mapping as RenderedMapping) ?? 'unmapped'] += 1;\n }\n return {\n findings: analysis.diagnostics,\n summary: {\n pages: input.pages.length,\n words: input.pages.reduce((n, p) => n + p.words.length, 0),\n inventory: draft?.inventory ?? {\n mapped: 0,\n ambiguous: 0,\n missing: 0,\n skipped: 0,\n },\n findings: byMapping,\n ...(draft?.fonts && { fonts: draft.fonts }),\n suppressed: analysis.suppressedCount,\n blocked: analysis.blocked,\n truncated: analysis.truncated,\n ...(analysis.profileId !== undefined && {\n profileId: analysis.profileId,\n }),\n },\n analysis,\n };\n}\n","/**\n * The rendered pass as a quality rule pack (#344).\n *\n * `draftRenderedFindings` measures; these rules are how the measurement\n * enters the quality contract. Each rule owns one code, one description the\n * design guide prints, and the defaults a profile or policy can move. All of\n * them read a single `rendered/geometry` fact — the PDF's word boxes, its\n * embedded fonts and the document's text inventory — and share one matching\n * pass over it, memoised per fact, so eight rules cost one search.\n */\n\nimport {\n QUALITY_CODES,\n type QualityRule,\n type QualityRuleFinding,\n type QualityRulePack,\n} from '@json-to-office/quality';\nimport { isRenderedGeometryFact, renderedDraftFor } from './rendered-analysis';\n\nexport type RenderedRuleId =\n | 'rendered/clip'\n | 'rendered/spill'\n | 'rendered/overlap'\n | 'rendered/text-missing'\n | 'rendered/font-substituted'\n | 'rendered/empty-page'\n | 'rendered/heading-stranded'\n | 'rendered/paragraph-split'\n | 'rendered/table-split'\n | 'rendered/page-underfilled';\n\ninterface RenderedRuleSpec {\n id: RenderedRuleId;\n code: string;\n description: string;\n category: QualityRule['category'];\n defaultSeverity: QualityRule['defaultSeverity'];\n formats?: readonly ('docx' | 'pptx')[];\n}\n\nfunction renderedRule(spec: RenderedRuleSpec): QualityRule {\n return {\n id: spec.id,\n code: spec.code,\n description: spec.description,\n category: spec.category,\n defaultSeverity: spec.defaultSeverity,\n defaultCertainty: 'rendered',\n ...(spec.formats && { formats: spec.formats }),\n evaluate({ facts }): readonly QualityRuleFinding[] {\n const fact = facts.find(isRenderedGeometryFact);\n if (!fact) return [];\n // A draft is a rule finding with one extra key the engine never reads.\n return renderedDraftFor(fact).byRule.get(spec.id) ?? [];\n },\n };\n}\n\nexport const RENDERED_QUALITY_RULES: QualityRulePack = {\n id: 'rendered',\n rules: [\n renderedRule({\n id: 'rendered/clip',\n code: QUALITY_CODES.RENDERED_CLIP,\n description:\n 'Words rendered past the page edge by more than 2 pt, or an authored string of which only a leading part rendered (`kind: truncated`, with the share found).',\n category: 'integrity',\n defaultSeverity: 'warning',\n }),\n renderedRule({\n id: 'rendered/spill',\n code: QUALITY_CODES.RENDERED_SPILL,\n description:\n 'A framed paragraph or slide text box drawn wider (DOCX) or larger (PPTX) than the box it declared.',\n category: 'integrity',\n defaultSeverity: 'warning',\n }),\n renderedRule({\n id: 'rendered/overlap',\n code: QUALITY_CODES.RENDERED_OVERLAP,\n description:\n 'Two words from different lines whose boxes intersect by more than 30% of the smaller.',\n category: 'integrity',\n defaultSeverity: 'warning',\n }),\n renderedRule({\n id: 'rendered/text-missing',\n code: QUALITY_CODES.RENDERED_TEXT_MISSING,\n description:\n 'An authored string that appears nowhere in the PDF: fully clipped, hidden, or dropped by the renderer.',\n category: 'integrity',\n defaultSeverity: 'warning',\n }),\n renderedRule({\n id: 'rendered/font-substituted',\n code: QUALITY_CODES.RENDERED_FONT_SUBSTITUTED,\n description:\n 'A requested family the PDF embeds no face of; a warning when the document declared a source for it, information when the host simply lacks it.',\n category: 'brand',\n defaultSeverity: 'info',\n }),\n renderedRule({\n id: 'rendered/empty-page',\n code: QUALITY_CODES.RENDERED_EMPTY_PAGE,\n description:\n 'A wordless page (`kind: blank`), or in docx one whose only words are its running head or footer (`kind: chrome-only`): a full-page figure is fine, a blank page from a stray break or an empty section is not.',\n category: 'integrity',\n defaultSeverity: 'info',\n }),\n renderedRule({\n id: 'rendered/page-underfilled',\n code: QUALITY_CODES.RENDERED_PAGE_UNDERFILLED,\n description:\n 'A docx page whose ink stops well above the footer: a middle page less than half filled before the next page begins (`kind: middle-page`), or a last page less than a quarter filled that holds only the tail of the document (`kind: last-page`); `context.fill` is the share used. The first page is never judged.',\n category: 'composition',\n defaultSeverity: 'info',\n formats: ['docx'],\n }),\n renderedRule({\n id: 'rendered/heading-stranded',\n code: QUALITY_CODES.RENDERED_HEADING_STRANDED,\n description: 'A heading that is the last body line on its page.',\n category: 'composition',\n defaultSeverity: 'warning',\n formats: ['docx'],\n }),\n renderedRule({\n id: 'rendered/table-split',\n code: QUALITY_CODES.RENDERED_TABLE_SPLIT,\n description:\n 'A table broken across a page badly: its header left alone at the foot of a page (`kind: header-alone`), or one row alone on either side of the break (`kind: orphan-row` or `widow-row`).',\n category: 'composition',\n defaultSeverity: 'info',\n formats: ['docx'],\n }),\n renderedRule({\n id: 'rendered/paragraph-split',\n code: QUALITY_CODES.RENDERED_PARAGRAPH_SPLIT,\n description:\n 'A paragraph leaving one line alone on either side of a page break (`kind: orphan` or `widow`).',\n category: 'composition',\n defaultSeverity: 'info',\n formats: ['docx'],\n }),\n ],\n};\n","/**\n * Locate authored text in rendered PDF geometry.\n *\n * Promoted from the ground-truth harness's sentinel search: fold both sides\n * into lowercase alphanumerics (NFKC splits ligatures, punctuation and bullet\n * glyphs drop out), concatenate every word fragment into one stream, and\n * search that. Narrow boxes hard-wrap a word mid-word and letter-spaced text\n * makes poppler emit per-cluster fragments; a per-word comparison loses both,\n * a stream keeps them. A match must start where a fragment starts and end\n * where one ends, so \"page\" never matches inside \"homepage\".\n *\n * The stream spans the whole document, not one page, so a paragraph that\n * breaks across a page is still one match — which is exactly the case the\n * widow and orphan checks need. Running heads and footers would interleave\n * the two halves, so text that repeats by design is matched first, inside\n * the top and bottom bands of each page, and its rows are removed from the\n * stream before anything else is; a row of nothing but digits in those\n * bands is a page number and goes with them.\n *\n * Fields, footnote marks and cross-references render a value the author\n * never typed. The needle is cut at each of them into segments that must\n * follow one another in the stream with at most a short gap between.\n *\n * Duplicate strings — \"Total\" in three tables — are why a search returns\n * every occurrence. `assignInventory` resolves them by reading order: the\n * inventory is walked in document order and the stream is in page order, so\n * the next unclaimed occurrence at or after the previous claim is the one.\n * Where the format knows more before rendering — a slide is its own page,\n * and its text boxes sit where the deck put them — the entry says so, and\n * only an occurrence on that page, preferably inside that box, is its own.\n */\n\nimport type {\n PdfTextLine,\n PdfTextWord,\n PdfTextPage,\n} from './pdf-text-geometry';\n\n/** Fold rendered and authored text into one comparable form. */\nexport function normalizeForMatch(value: string): string {\n return value\n .normalize('NFKC')\n .toLowerCase()\n .replace(/[^a-z0-9]/g, '');\n}\n\n/** Marks a place where the renderer paints something the author did not type. */\nconst FIELD = '\u001f';\n\n/**\n * Authored strings carry markup the page never shows: emphasis markers,\n * link targets, and fields, footnote references and cross-references whose\n * rendered value is unknowable here. The first two are dropped; the others\n * become a `FIELD` mark that `needleSegments` splits on.\n */\nexport function authoredTextForMatch(text: string): string {\n return text\n .replace(/\\{[^}\\s]+\\}/g, FIELD)\n .replace(/\\[\\^[^\\]\\s]+\\]/g, FIELD)\n .replace(/\\[@[^\\]\\s]+\\]/g, FIELD)\n .replace(/\\[([^\\]]*)\\]\\([^)]*\\)/g, '$1')\n .replace(/[*_`]+/g, '');\n}\n\n/** Folded segments of an authored string, split at every field. */\nexport function needleSegments(text: string): string[] {\n return authoredTextForMatch(text)\n .split(FIELD)\n .map(normalizeForMatch)\n .filter((segment) => segment !== '');\n}\n\n/** A word of the document: which page, which index on it. */\nexport interface WordRef {\n pageIndex: number;\n word: number;\n}\n\n/** The part of a match that lies on one page, with its union box. */\nexport interface OccurrencePart {\n pageIndex: number;\n words: number[];\n xMin: number;\n yMin: number;\n xMax: number;\n yMax: number;\n}\n\nexport interface TextOccurrence {\n /** Offset into the document stream, for ordering. */\n at: number;\n /** Offset just past the match: `[at, end)` is the range a claim holds. */\n end: number;\n /** First and last page the match touches; equal unless it breaks across. */\n pageIndex: number;\n endPageIndex: number;\n parts: OccurrencePart[];\n}\n\nexport interface StreamIndex {\n pages: readonly PdfTextPage[];\n /** One entry per word of the document, in stream order. */\n refs: WordRef[];\n fragments: string[];\n /** Offset of each fragment in `stream`; excluded fragments are empty. */\n positions: number[];\n stream: string;\n /** Where the rotated text of every page starts: all upright text is before. */\n rotatedFrom: number;\n}\n\n/**\n * Index every word of every page into one stream. Words in `exclude` keep\n * their slot with an empty fragment, so positions stay comparable while a\n * match can never touch them.\n *\n * The upright text of every page comes first, page after page, so a\n * paragraph that turns the page is one run of the stream; the rotated text\n * of every page follows it all. Anywhere inside the upright text a rotated\n * run cuts whatever string it lands in: where poppler emitted it, a deck's\n * takeaway beside a chart; at the top of a page, a report paragraph that\n * turns onto the page above a chart.\n */\nexport function indexDocument(\n pages: readonly PdfTextPage[],\n exclude: ReadonlySet<string> = new Set(),\n order: (\n words: readonly PdfTextWord[],\n lines: readonly PdfTextLine[]\n ) => { upright: number[]; rotated: number[] } = pageOrder\n): StreamIndex {\n const refs: WordRef[] = [];\n const fragments: string[] = [];\n const positions: number[] = [];\n let cursor = 0;\n const push = (pageIndex: number, index: number): void => {\n const word = pages[pageIndex].words[index];\n const key = `${pageIndex}:${index}`;\n const fragment = exclude.has(key) ? '' : normalizeForMatch(word.text);\n refs.push({ pageIndex, word: index });\n fragments.push(fragment);\n positions.push(cursor);\n cursor += fragment.length;\n };\n const orders = pages.map((page) => order(page.words, page.lines));\n orders.forEach((order, pageIndex) => {\n for (const index of order.upright) push(pageIndex, index);\n });\n const rotatedFrom = cursor;\n orders.forEach((order, pageIndex) => {\n for (const index of order.rotated) push(pageIndex, index);\n });\n return {\n pages,\n refs,\n fragments,\n positions,\n stream: fragments.join(''),\n rotatedFrom,\n };\n}\n\n/** A run of words on one row with no wide gap between them: one cell's line. */\ninterface RowFragment {\n xMin: number;\n xMax: number;\n words: number[];\n}\n\n/**\n * Word indices in the order a reader takes them, decided from geometry\n * rather than from the order poppler emitted them: poppler 24 lists a table\n * row line by line across every cell, poppler 26 lists it cell by cell, and\n * an authored string in a wrapped cell only survives the second.\n *\n * Rows are grouped by vertical overlap and split into fragments at gaps\n * wider than half the line is tall. A row of two or more fragments opens a\n * column block; the rows under it belong to the block while each of their\n * fragments sits inside one column (a right-aligned cell's second line\n * starts further right; a wrapped label's later lines are the only\n * fragment of their row) and the rows stay a line apart. A block reads\n * column by column, so a cell's lines come out together; a row of one\n * fragment outside any block — prose — reads as it lies.\n *\n * Rotated text — a chart's value-axis title — has no rows or columns to\n * take part in. It reads as one run in the order poppler gave it, after the\n * page's upright text: inside that text it could fall between two lines of\n * a paragraph and cut it in two, and a slide's takeaway set beside a chart\n * then reads as clipped, or as never rendered at all. Across a document the\n * stream sets it after every page (`indexDocument`).\n */\nexport function readingOrder(\n words: readonly PdfTextWord[],\n lines: readonly PdfTextLine[] = []\n): number[] {\n const { upright, rotated } = pageOrder(words, lines);\n return [...upright, ...rotated];\n}\n\n/** A page's upright words in reading order, and its rotated ones apart. */\nfunction pageOrder(\n words: readonly PdfTextWord[],\n lines: readonly PdfTextLine[] = []\n): { upright: number[]; rotated: number[] } {\n const rotated = rotatedWords(words, lines);\n return {\n upright: uprightOrder(\n words,\n words.map((_, i) => i).filter((i) => !rotated.has(i))\n ),\n rotated: [...rotated].sort((a, b) => a - b),\n };\n}\n\n/**\n * A page's upright words row by row, each row left to right — the page as it\n * lies, with no columns — and its rotated ones apart.\n */\nfunction linedOrder(\n words: readonly PdfTextWord[],\n lines: readonly PdfTextLine[] = []\n): { upright: number[]; rotated: number[] } {\n const rotated = rotatedWords(words, lines);\n const upright = words.map((_, i) => i).filter((i) => !rotated.has(i));\n return {\n upright: groupRows(words, upright).flatMap((row) =>\n [...row.words].sort((a, b) => words[a].xMin - words[b].xMin)\n ),\n rotated: [...rotated].sort((a, b) => a - b),\n };\n}\n\n/**\n * Words set on their side. A long enough word says so itself, its box\n * taller than it is wide; a short one does not — \"of\" in a rotated title is\n * still wider than tall — but poppler groups a rotated title into one line\n * whose words stack along the page instead of across it, and every word of\n * such a line is rotated.\n */\nfunction rotatedWords(\n words: readonly PdfTextWord[],\n lines: readonly PdfTextLine[]\n): Set<number> {\n const rotated = new Set(\n words\n .map((_, i) => i)\n .filter((i) => {\n const w = words[i];\n // Three glyphs or more: a \"1:\" or an \"I\" is narrow, not rotated.\n return w.text.length > 2 && w.yMax - w.yMin > 1.5 * (w.xMax - w.xMin);\n })\n );\n for (const line of lines) {\n if (line.words.length < 2) continue;\n const stacked = line.words.slice(1).every((index, n) => {\n const a = words[line.words[n]];\n const b = words[index];\n const across = Math.min(a.xMax, b.xMax) - Math.max(a.xMin, b.xMin);\n const along = Math.min(a.yMax, b.yMax) - Math.max(a.yMin, b.yMin);\n return across > 0 && along <= 0;\n });\n if (stacked) for (const index of line.words) rotated.add(index);\n }\n return rotated;\n}\n\n/** Middle value of a non-empty list, for a row's typical word gap. */\nfunction median(values: readonly number[]): number {\n const sorted = [...values].sort((a, b) => a - b);\n const mid = sorted.length >> 1;\n return sorted.length % 2 === 1\n ? sorted[mid]\n : (sorted[mid - 1] + sorted[mid]) / 2;\n}\n\n/** A row of text: the words that share it, and the box they fill. */\ninterface TextRow {\n words: number[];\n yMin: number;\n yMax: number;\n}\n\n/**\n * Two words set at sizes this far apart are not one line of text, however\n * they overlap: a display title's box spans both lines of the small print\n * beside it.\n */\nconst ROW_SIZE_RATIO = 2;\n\n/** Whether two words are set at sizes one line of text could hold. */\nfunction comparableSize(a: PdfTextWord, b: PdfTextWord): boolean {\n const ha = a.yMax - a.yMin;\n const hb = b.yMax - b.yMin;\n return Math.max(ha, hb) <= ROW_SIZE_RATIO * Math.min(ha, hb);\n}\n\n/**\n * The given words grouped into the rows they are read on, top down. The\n * reading order and the page-number sweep ask the same question of a page,\n * so they ask it once. A word joins a row it overlaps that holds a word of\n * about its own size; measured only against the row's first word, a title\n * beside a two-line caption took both caption lines into its row, and they\n * were read one word from each line at a time.\n */\nfunction groupRows(\n words: readonly PdfTextWord[],\n indices: readonly number[]\n): TextRow[] {\n const rows: TextRow[] = [];\n const byY = [...indices].sort(\n (a, b) => words[a].yMin - words[b].yMin || words[a].xMin - words[b].xMin\n );\n for (const i of byY) {\n const word = words[i];\n const row = rows[rows.length - 1];\n const peer = row?.words.find((w) => comparableSize(words[w], word));\n if (row && peer !== undefined && sameRow(words[peer], word)) {\n row.words.push(i);\n row.yMin = Math.min(row.yMin, word.yMin);\n row.yMax = Math.max(row.yMax, word.yMax);\n } else rows.push({ words: [i], yMin: word.yMin, yMax: word.yMax });\n }\n return rows;\n}\n\nfunction uprightOrder(\n words: readonly PdfTextWord[],\n indices: readonly number[]\n): number[] {\n const grouped = groupRows(words, indices);\n const rows = grouped.map((row) => row.words);\n const fragmentsOf = (row: number[]): RowFragment[] => {\n const sorted = [...row].sort((a, b) => words[a].xMin - words[b].xMin);\n // A space is a quarter of the size; half a line's height is two of\n // them, and a cell gap on a 23-point figure is more than that. Tight\n // cell padding puts less than that between two columns, though, and\n // then poppler runs the two cells into one fragment and the column\n // they belong to cannot be told. So a gap the row's own spaces cannot\n // account for parts cells as well: within a row a word space holds to\n // its median within a hundredth — justification stretches every space\n // of a line alike — while a cell boundary is close to twice it.\n const gaps = sorted.slice(1).map((i, n) => {\n const height = words[i].yMax - words[i].yMin;\n const gap = words[i].xMin - words[sorted[n]].xMax;\n return { gap, height, wide: gap > height / 2 };\n });\n const spaces = gaps.filter((g) => !g.wide).map((g) => g.gap);\n const typical = spaces.length > 1 ? median(spaces) : undefined;\n const isCellBoundary = (n: number): boolean => {\n const g = gaps[n - 1];\n return (\n g.wide ||\n (typical !== undefined &&\n g.gap > g.height / 3 &&\n g.gap >= typical * 1.5)\n );\n };\n const out: RowFragment[] = [];\n sorted.forEach((i, n) => {\n const last = out[out.length - 1];\n const w = words[i];\n if (last && !isCellBoundary(n)) {\n last.words.push(i);\n last.xMax = Math.max(last.xMax, w.xMax);\n } else out.push({ xMin: w.xMin, xMax: w.xMax, words: [i] });\n });\n return out;\n };\n const fragments = rows.map(fragmentsOf);\n const boxes = grouped;\n\n // Rows less than a line apart or overlapping (a cell centred beside a\n // wrapped label; DejaVu's wrapped lines sit two thirds of a line apart)\n // belong to one cluster. Columns are the x-intervals its fragments\n // fall in, merged where they overlap, so a right-aligned cell's shorter\n // second line joins its first. A cluster that resolves into more than\n // one column is a table and reads column by column — a single value\n // centred between the two lines of the label beside it parts them\n // exactly as a row of several cells does, and no row of that block has\n // to hold two fragments for the block to exist. A fragment across two\n // columns is prose under the table: it closes the cluster and opens\n // the next. The other way round, prose over the table, a row that sets\n // two fragments in one column shows the column was several all along:\n // what the column holds so far is read first, whole, and each fragment\n // opens a column of its own. Left as one column, the cells under the\n // prose would be read row by row, one cell's second line after its\n // neighbour's first.\n interface Column {\n xMin: number;\n xMax: number;\n fragments: RowFragment[];\n }\n const order: number[] = [];\n const read = new Set<RowFragment>();\n const readColumn = (column: Column): void => {\n column.fragments.sort(\n (a, b) =>\n words[a.words[0]].yMin - words[b.words[0]].yMin || a.xMin - b.xMin\n );\n for (const f of column.fragments) {\n order.push(...f.words);\n read.add(f);\n }\n };\n let cluster: { rows: number[]; columns: Column[] } | null = null;\n const flush = (): void => {\n if (!cluster) return;\n if (cluster.columns.length < 2) {\n for (const r of cluster.rows)\n for (const f of fragments[r]) if (!read.has(f)) order.push(...f.words);\n } else {\n cluster.columns.sort((a, b) => a.xMin - b.xMin);\n for (const column of cluster.columns) readColumn(column);\n }\n cluster = null;\n };\n const overlapping = (columns: Column[], f: RowFragment): Column[] =>\n columns.filter(\n (c) => Math.min(c.xMax, f.xMax) - Math.max(c.xMin, f.xMin) > 0\n );\n for (let r = 0; r < rows.length; r++) {\n const lineHeight = boxes[r].yMax - boxes[r].yMin;\n const adjacent =\n cluster !== null && boxes[r].yMin - boxes[r - 1].yMax <= lineHeight * 0.9;\n const wide =\n cluster !== null &&\n fragments[r].some((f) => overlapping(cluster!.columns, f).length > 1);\n if (!adjacent || wide) flush();\n if (!cluster) cluster = { rows: [], columns: [] };\n cluster.rows.push(r);\n const landing = new Map<Column, number>();\n for (const f of fragments[r]) {\n const hit = overlapping(cluster.columns, f)[0];\n if (hit) landing.set(hit, (landing.get(hit) ?? 0) + 1);\n }\n for (const [column, count] of landing) {\n if (count < 2) continue;\n readColumn(column);\n cluster.columns.splice(cluster.columns.indexOf(column), 1);\n }\n for (const f of fragments[r]) {\n const hit = overlapping(cluster.columns, f)[0];\n if (hit) {\n hit.xMin = Math.min(hit.xMin, f.xMin);\n hit.xMax = Math.max(hit.xMax, f.xMax);\n hit.fragments.push(f);\n } else\n cluster.columns.push({ xMin: f.xMin, xMax: f.xMax, fragments: [f] });\n }\n }\n flush();\n return order;\n}\n\n/** Index of the first non-empty fragment starting at or after `offset`. */\nfunction fragmentAt(index: StreamIndex, offset: number): number {\n let low = 0;\n let high = index.positions.length - 1;\n // Last fragment whose position is <= offset; empty fragments share their\n // successor's position, so walk forward past them afterwards.\n while (low < high) {\n const mid = Math.ceil((low + high) / 2);\n if (index.positions[mid] <= offset) low = mid;\n else high = mid - 1;\n }\n while (low < index.fragments.length && index.fragments[low] === '') low++;\n return low;\n}\n\n/** Whether [at, end) starts on a fragment boundary and ends on one. */\nfunction alignedToFragments(\n index: StreamIndex,\n at: number,\n end: number\n): boolean {\n const first = fragmentAt(index, at);\n if (first >= index.fragments.length || index.positions[first] !== at) {\n return false;\n }\n let i = first;\n while (\n i < index.fragments.length &&\n index.positions[i] + index.fragments[i].length < end\n ) {\n i++;\n }\n return (\n i < index.fragments.length &&\n index.positions[i] + index.fragments[i].length === end\n );\n}\n\n/**\n * The stream range [at, end) as one part per page it touches, each with the\n * union box of its words — how a paragraph broken across a page reports the\n * geometry of both halves without losing which page each is on.\n */\nfunction partsOf(\n index: StreamIndex,\n at: number,\n end: number\n): OccurrencePart[] {\n const parts: OccurrencePart[] = [];\n for (let i = fragmentAt(index, at); i < index.fragments.length; i++) {\n const start = index.positions[i];\n if (start >= end) break;\n if (index.fragments[i] === '') continue;\n const ref = index.refs[i];\n const w = index.pages[ref.pageIndex].words[ref.word];\n let part = parts[parts.length - 1];\n if (!part || part.pageIndex !== ref.pageIndex) {\n part = {\n pageIndex: ref.pageIndex,\n words: [],\n xMin: Infinity,\n yMin: Infinity,\n xMax: -Infinity,\n yMax: -Infinity,\n };\n parts.push(part);\n }\n part.words.push(ref.word);\n part.xMin = Math.min(part.xMin, w.xMin);\n part.yMin = Math.min(part.yMin, w.yMin);\n part.xMax = Math.max(part.xMax, w.xMax);\n part.yMax = Math.max(part.yMax, w.yMax);\n }\n return parts;\n}\n\n/** One match of a stream range, with the pages it spans. */\nfunction occurrence(\n index: StreamIndex,\n at: number,\n end: number\n): TextOccurrence {\n const parts = partsOf(index, at, end);\n return {\n at,\n end,\n pageIndex: parts[0].pageIndex,\n endPageIndex: parts[parts.length - 1].pageIndex,\n parts,\n };\n}\n\n/** The most a rendered field value may add between two segments, folded. */\nexport const MAX_FIELD_GAP = 24;\n\n/**\n * Every occurrence of a segmented needle in the stream, in reading order:\n * the first segment anywhere, each following one within `MAX_FIELD_GAP`\n * characters of the previous, the whole aligned to fragment boundaries.\n */\nexport function findOccurrences(\n index: StreamIndex,\n segments: readonly string[]\n): TextOccurrence[] {\n if (segments.length === 0 || segments[0] === '') return [];\n const hits: TextOccurrence[] = [];\n let searchFrom = 0;\n for (;;) {\n const at = index.stream.indexOf(segments[0], searchFrom);\n if (at === -1) break;\n searchFrom = at + 1;\n let end = at + segments[0].length;\n let complete = true;\n for (let s = 1; s < segments.length; s++) {\n const next = index.stream.indexOf(segments[s], end);\n if (next === -1 || next - end > MAX_FIELD_GAP) {\n complete = false;\n break;\n }\n end = next + segments[s].length;\n }\n if (!complete || !alignedToFragments(index, at, end)) continue;\n const hit = occurrence(index, at, end);\n // The stream sets the rotated text of every page after all the upright\n // text: upright text running on into it, or one page's rotated run into\n // the next page's, is two neighbours of the index, not text of a page.\n if (\n at < index.rotatedFrom\n ? end > index.rotatedFrom\n : hit.pageIndex !== hit.endPageIndex\n ) {\n continue;\n }\n hits.push(hit);\n searchFrom = end;\n }\n return hits;\n}\n\n/** What an inventory entry needs to be matched: its text, in reading order. */\nexport interface InventoryEntry {\n path: string;\n text: string;\n /** Header/footer text repeats per page: every occurrence is legitimate. */\n repeats?: boolean;\n /**\n * Painted by the renderer from other authored text — a contents entry —\n * so it claims its occurrence when there is one and is `skipped`, never\n * `missing`, when there is not.\n */\n optional?: boolean;\n /**\n * The 0-based page the text is drawn on, when the format knows it before\n * rendering: a slide is its own page. Only an occurrence starting there\n * is the entry's — \"13 pts\" on slide 2 is not \"+1.3 pts\" on slide 8,\n * however alike the two fold.\n */\n page?: number;\n /**\n * Where on that page the text was laid out, in PDF points: a slide text\n * box. Of the entry's occurrences, one inside this region is its own. A\n * section tracker that repeats the first word of the title under it is\n * two occurrences of one word, and reading order cannot say which box\n * painted which; the boxes can.\n */\n region?: { xMin: number; yMin: number; xMax: number; yMax: number };\n}\n\nexport type MappingStatus = 'mapped' | 'ambiguous' | 'missing' | 'skipped';\n\nexport interface InventoryMatch<T extends InventoryEntry = InventoryEntry> {\n entry: T;\n needle: string;\n status: MappingStatus;\n /** One occurrence when mapped; every occurrence when the text repeats. */\n occurrences: TextOccurrence[];\n /**\n * Set when only a leading part of the text rendered: the rest was cut\n * off by a frame, a page edge or a box. Folded character counts.\n */\n partial?: { matchedChars: number; totalChars: number };\n}\n\n/** Below this many folded characters a needle matches too much to trust. */\nexport const MIN_NEEDLE_LENGTH = 3;\n/** A prefix shorter than this could be any sentence; it proves nothing. */\nexport const MIN_PARTIAL_PREFIX = 16;\n/** Running heads and footers live in the outer fifth of the page. */\nexport const CHROME_BAND = 0.2;\n\n/**\n * The longest leading part of `needle` that occurs in the stream, when the\n * whole does not — a paragraph whose tail was clipped still starts where it\n * was written. Every place the first `MIN_PARTIAL_PREFIX` characters occur\n * on a fragment boundary is extended character by character; the longest\n * extension wins. Given a page, only a prefix starting on it counts, and it\n * ends where the page does: a slide's text never runs on into the next\n * slide's. Upright text never runs on into the rotated text after it, and\n * rotated text never past its own page's run.\n */\nexport function longestRenderedPrefix(\n index: StreamIndex,\n needle: string,\n page?: number,\n region?: InventoryEntry['region']\n): { length: number; at: number } | undefined {\n if (needle.length < MIN_PARTIAL_PREFIX) return undefined;\n const anchor = needle.slice(0, MIN_PARTIAL_PREFIX);\n let best: { length: number; at: number } | undefined;\n let searchFrom = 0;\n for (;;) {\n const at = index.stream.indexOf(anchor, searchFrom);\n if (at === -1) break;\n searchFrom = at + 1;\n const first = fragmentAt(index, at);\n if (first >= index.fragments.length || index.positions[first] !== at) {\n continue;\n }\n if (page !== undefined && index.refs[first].pageIndex !== page) continue;\n const rotated = at >= index.rotatedFrom;\n const limit = Math.min(\n page !== undefined || rotated\n ? pageEnd(index, first)\n : index.stream.length,\n rotated ? index.stream.length : index.rotatedFrom\n );\n if (at + MIN_PARTIAL_PREFIX > limit) continue;\n let length = MIN_PARTIAL_PREFIX;\n while (\n length < needle.length &&\n at + length < limit &&\n index.stream.charCodeAt(at + length) === needle.charCodeAt(length)\n ) {\n length++;\n }\n if (region && !touchesRegion(partsOf(index, at, at + length), region)) {\n continue;\n }\n if (!best || length > best.length) best = { length, at };\n }\n return best;\n}\n\n/** Offset where the stream leaves the page of fragment `from`. */\nfunction pageEnd(index: StreamIndex, from: number): number {\n const page = index.refs[from].pageIndex;\n let i = from;\n while (i < index.refs.length && index.refs[i].pageIndex === page) i++;\n return i < index.positions.length ? index.positions[i] : index.stream.length;\n}\n\n/** A little past a box's edge still touches it: renderer rounding, insets. */\nconst REGION_TOLERANCE_PT = 2;\n\n/**\n * Whether any part of an occurrence reaches into a region. Slide text starts\n * inside its box however far it overflows, so an occurrence that nowhere\n * touches the box is some other box's words that happen to read the same.\n */\nfunction touchesRegion(\n parts: readonly OccurrencePart[],\n region: NonNullable<InventoryEntry['region']>\n): boolean {\n return parts.some(\n (part) =>\n part.xMax >= region.xMin - REGION_TOLERANCE_PT &&\n part.xMin <= region.xMax + REGION_TOLERANCE_PT &&\n part.yMax >= region.yMin - REGION_TOLERANCE_PT &&\n part.yMin <= region.yMax + REGION_TOLERANCE_PT\n );\n}\n\n/** Vertical overlap of more than half the shorter box: one row of text. */\nfunction sameRow(\n a: { yMin: number; yMax: number },\n b: { yMin: number; yMax: number }\n): boolean {\n const overlap = Math.min(a.yMax, b.yMax) - Math.max(a.yMin, b.yMin);\n const shorter = Math.min(a.yMax - a.yMin, b.yMax - b.yMin);\n return shorter > 0 && overlap > shorter / 2;\n}\n\n/** Whether a box sits in the top or bottom fifth, where chrome lives. */\nfunction inChromeBand(\n page: PdfTextPage,\n box: { yMin: number; yMax: number }\n): boolean {\n return (\n box.yMax <= page.heightPt * CHROME_BAND ||\n box.yMin >= page.heightPt * (1 - CHROME_BAND)\n );\n}\n\nconst PAGE_NUMBER_ROW = /^[0-9\\s./|-]*$/;\n\n/** Rows this close in `yMin` are the same chrome position on different pages. */\nconst CHROME_ROW_TOLERANCE_PT = 6;\n\n/**\n * Which of a repeating entry's in-band occurrences are its chrome.\n *\n * Chrome sits nearest the page edge and repeats at the same height. So on\n * each page only the occurrence closest to the top or bottom edge stands —\n * a body line near the top of a page that happens to say the same words is\n * further in than the running head above it — and, when the entry shows on\n * more than one page, only heights that recur on another page count. A\n * header that exists on a single page (a two-page report whose cover has\n * none) has nothing to recur against and is kept as it is.\n */\nfunction chromeOccurrences<\n T extends { pageIndex: number; parts: { yMin: number; yMax: number }[] },\n>(occurrences: readonly T[], pages: readonly PdfTextPage[]): T[] {\n const nearestPerPage = new Map<number, T>();\n const edgeDistance = (o: T) =>\n Math.min(o.parts[0].yMin, pages[o.pageIndex].heightPt - o.parts[0].yMax);\n for (const o of occurrences) {\n const held = nearestPerPage.get(o.pageIndex);\n if (!held || edgeDistance(o) < edgeDistance(held)) {\n nearestPerPage.set(o.pageIndex, o);\n }\n }\n const nearest = [...nearestPerPage.values()];\n if (nearest.length < 2) return nearest;\n return nearest.filter((o) =>\n nearest.some(\n (other) =>\n other.pageIndex !== o.pageIndex &&\n Math.abs(other.parts[0].yMin - o.parts[0].yMin) <=\n CHROME_ROW_TOLERANCE_PT\n )\n );\n}\n\n/**\n * The rows that are page numbers: digits alone in a band, and chrome rather\n * than content that flowed there.\n *\n * A footer that is nothing but the field renders as a bare number with no\n * repeating text to claim it, so it needs a rule of its own. The band is a\n * generous fifth of the page, though, and body content reaches into it: a\n * table's numeric row lands there on whatever page the table breaks on,\n * and a numeric cell claimed as chrome loses its own occurrence and reads\n * as never rendered.\n *\n * Two things tell chrome from content, both geometry. A page number stands\n * apart — the page margin separates it from the body, so no unclaimed row\n * lies within a line of it, while a table row has its wrapped label or its\n * neighbouring rows right against it. And a page number recurs: the\n * renderer paints one at the same height on every page, which content that\n * merely flowed into the band on one page does not. A single-page document\n * has nothing to recur against, so there the first test stands alone.\n */\nfunction pageNumberRows(\n pages: readonly PdfTextPage[],\n claimed: ReadonlySet<string>\n): { pageIndex: number; box: { yMin: number; yMax: number } }[] {\n const hits: { pageIndex: number; box: TextRow }[] = [];\n pages.forEach((page, pageIndex) => {\n const rows = groupRows(\n page.words,\n page.words.map((_, i) => i)\n );\n const free = rows.map((row) =>\n row.words.some((i) => !claimed.has(`${pageIndex}:${i}`))\n );\n rows.forEach((row, r) => {\n if (!free[r] || !inChromeBand(page, row)) return;\n if (!row.words.every((i) => PAGE_NUMBER_ROW.test(page.words[i].text)))\n return;\n const line = row.yMax - row.yMin;\n const attached = rows.some(\n (other, o) =>\n o !== r &&\n free[o] &&\n Math.max(other.yMin - row.yMax, row.yMin - other.yMax) < line\n );\n if (!attached) hits.push({ pageIndex, box: row });\n });\n });\n if (pages.length < 2) return hits;\n return hits.filter((hit) =>\n hits.some(\n (other) =>\n other.pageIndex !== hit.pageIndex &&\n Math.abs(other.box.yMin - hit.box.yMin) <= CHROME_ROW_TOLERANCE_PT\n )\n );\n}\n\n/**\n * Of an entry's free occurrences, the one lying most inside its region: the\n * largest share of its words' box within it. An occurrence that runs out of\n * the region still counts by the part inside — text overflowing its box\n * starts in it. Nothing inside, and reading order decides after all.\n */\nfunction insideRegion(\n occurrences: readonly TextOccurrence[],\n region: NonNullable<InventoryEntry['region']>\n): TextOccurrence | undefined {\n let best: { occurrence: TextOccurrence; share: number } | undefined;\n for (const o of occurrences) {\n let inside = 0;\n let total = 0;\n for (const part of o.parts) {\n const w =\n Math.min(part.xMax, region.xMax) - Math.max(part.xMin, region.xMin);\n const h =\n Math.min(part.yMax, region.yMax) - Math.max(part.yMin, region.yMin);\n if (w > 0 && h > 0) inside += w * h;\n total += (part.xMax - part.xMin) * (part.yMax - part.yMin);\n }\n const share = total > 0 ? inside / total : 0;\n if (share > 0 && (!best || share > best.share)) {\n best = { occurrence: o, share };\n }\n }\n return best?.occurrence;\n}\n\n/**\n * Slide text that overflowed its box onto other text. No run of the stream\n * holds it whole: the words it was drawn over share its rows and read in\n * between. Inside the column the box stands in, from its top down, its own\n * words still come in order with only those foreign words between them — so\n * the needle is matched there across whole fragments, skipping the fewest\n * foreign ones, and never consuming a word another entry already claimed.\n * A match that skips more words than it keeps is not the text overprinted\n * but words that happen to spell it, and is refused.\n *\n * The stream range from its first word to its last holds the words it was\n * drawn over, which are another entry's, so the occurrence claims no range:\n * `claims` holds the range of each word it kept, to be claimed one by one.\n */\nfunction overprintedOccurrence(\n index: StreamIndex,\n page: number,\n region: NonNullable<InventoryEntry['region']>,\n needle: string,\n claimed: (offset: number) => boolean\n):\n | { occurrence: TextOccurrence; claims: { at: number; end: number }[] }\n | undefined {\n const words = index.pages[page]?.words;\n if (!words) return undefined;\n const fragmentOf = new Map<number, number>();\n index.refs.forEach((ref, i) => {\n if (ref.pageIndex !== page || index.fragments[i] === '') return;\n const w = words[ref.word];\n const centre = (w.xMin + w.xMax) / 2;\n if (centre < region.xMin || centre > region.xMax) return;\n if (w.yMax < region.yMin) return;\n fragmentOf.set(ref.word, i);\n });\n const sequence = groupRows(words, [...fragmentOf.keys()]).flatMap((row) =>\n [...row.words].sort((a, b) => words[a].xMin - words[b].xMin)\n );\n interface Path {\n skips: number;\n words: number[];\n }\n let reach = new Map<number, Path>();\n let best: Path | undefined;\n for (const word of sequence) {\n const i = fragmentOf.get(word) as number;\n const text = index.fragments[i];\n const usable = !claimed(index.positions[i]);\n const next = new Map<number, Path>();\n const offer = (consumed: number, path: Path) => {\n const held = next.get(consumed);\n if (!held || path.skips < held.skips) next.set(consumed, path);\n };\n // Overflow runs out of the box, never into it: the text starts inside.\n const inside =\n words[word].yMin + words[word].yMax <= 2 * region.yMax &&\n words[word].yMin + words[word].yMax >= 2 * region.yMin;\n if (usable && inside && needle.startsWith(text)) {\n offer(text.length, { skips: 0, words: [word] });\n }\n for (const [consumed, path] of reach) {\n if (usable && needle.startsWith(text, consumed)) {\n offer(consumed + text.length, {\n skips: path.skips,\n words: [...path.words, word],\n });\n }\n offer(consumed, { skips: path.skips + 1, words: path.words });\n }\n const done = next.get(needle.length);\n if (done && (!best || done.skips < best.skips)) best = done;\n next.delete(needle.length);\n reach = next;\n }\n if (!best || best.skips > best.words.length) return undefined;\n const first = fragmentOf.get(best.words[0]) as number;\n const part: OccurrencePart = {\n pageIndex: page,\n words: best.words,\n xMin: Math.min(...best.words.map((w) => words[w].xMin)),\n yMin: Math.min(...best.words.map((w) => words[w].yMin)),\n xMax: Math.max(...best.words.map((w) => words[w].xMax)),\n yMax: Math.max(...best.words.map((w) => words[w].yMax)),\n };\n const at = index.positions[first];\n return {\n occurrence: {\n at,\n end: at,\n pageIndex: page,\n endPageIndex: page,\n parts: [part],\n },\n claims: best.words.map((w) => {\n const i = fragmentOf.get(w) as number;\n return {\n at: index.positions[i],\n end: index.positions[i] + index.fragments[i].length,\n };\n }),\n };\n}\n\n/**\n * Report text the reading order took for a table. Label and value set on one\n * line by a tab, line under line — a memo's To, From, Date — part into two\n * columns exactly as two cells do, and a table reads column by column, so\n * every label comes out before any value, while the author wrote each line\n * as one string. A string the column reading never spells is looked for once\n * more in the rows as they lie (`lined`). Its words are claimed one by one in\n * the column reading, and only where no other entry holds any of them.\n */\nfunction linedOccurrence(\n lined: StreamIndex,\n body: StreamIndex,\n segments: readonly string[],\n claimed: (offset: number) => boolean\n):\n | { occurrence: TextOccurrence; claims: { at: number; end: number }[] }\n | undefined {\n const bodyFragment = new Map<string, number>();\n body.refs.forEach((ref, i) => {\n if (body.fragments[i] !== '')\n bodyFragment.set(`${ref.pageIndex}:${ref.word}`, i);\n });\n for (const hit of findOccurrences(lined, segments)) {\n const claims: { at: number; end: number }[] = [];\n for (const part of hit.parts)\n for (const word of part.words) {\n const i = bodyFragment.get(`${part.pageIndex}:${word}`);\n if (i === undefined) continue;\n claims.push({\n at: body.positions[i],\n end: body.positions[i] + body.fragments[i].length,\n });\n }\n if (claims.length === 0 || claims.some((claim) => claimed(claim.at)))\n continue;\n const at = Math.min(...claims.map((claim) => claim.at));\n return { occurrence: { ...hit, at, end: at }, claims };\n }\n return undefined;\n}\n\nexport interface InventoryAssignment<T extends InventoryEntry> {\n matches: InventoryMatch<T>[];\n /** `pageIndex:word` keys of every word chrome claimed — whole rows. */\n chromeWords: ReadonlySet<string>;\n}\n\n/**\n * Attribute rendered occurrences to inventory entries.\n *\n * Repeating entries go first and claim their occurrences inside the page\n * bands, one row each; rows of nothing but digits in those bands — page\n * numbers — go with them. The rest are visited in document order over a\n * stream without those words; each claims the first unclaimed occurrence at\n * or after the previous claim, which is how \"Total\" in the second table\n * finds the second \"Total\". An entry with no occurrence is `missing` —\n * fully clipped, or never set — unless it is optional, and one whose every\n * occurrence was already claimed is `ambiguous`.\n *\n * A claim holds the whole range it matched, not just where it starts: a\n * rendered word belongs to one authored string, so \"Revenue\" inside a title\n * another entry already owns is not free for a tracker that says \"Revenue\".\n * An entry that names its page looks only there, and one that names its\n * region takes the occurrence lying most inside it before reading order is\n * consulted at all.\n */\nexport function assignInventory<T extends InventoryEntry>(\n pages: readonly PdfTextPage[],\n inventory: readonly T[]\n): InventoryAssignment<T> {\n const results = new Map<T, InventoryMatch<T>>();\n const chromeWords = new Set<string>();\n const claimRow = (\n pageIndex: number,\n box: { yMin: number; yMax: number }\n ): void => {\n pages[pageIndex].words.forEach((w, i) => {\n if (sameRow(w, box)) chromeWords.add(`${pageIndex}:${i}`);\n });\n };\n\n const repeating = inventory.filter((entry) => entry.repeats);\n if (repeating.length > 0) {\n const full = indexDocument(pages);\n for (const entry of repeating) {\n const segments = needleSegments(entry.text);\n const needle = segments.join('');\n if (needle.length < MIN_NEEDLE_LENGTH) {\n results.set(entry, {\n entry,\n needle,\n status: 'skipped',\n occurrences: [],\n });\n continue;\n }\n const inBand = findOccurrences(full, segments).filter(\n (o) =>\n o.pageIndex === o.endPageIndex &&\n inChromeBand(pages[o.pageIndex], o.parts[0])\n );\n const occurrences = chromeOccurrences(inBand, pages);\n for (const o of occurrences) claimRow(o.pageIndex, o.parts[0]);\n results.set(entry, {\n entry,\n needle,\n status: occurrences.length > 0 ? 'mapped' : 'missing',\n occurrences,\n });\n }\n }\n for (const row of pageNumberRows(pages, chromeWords))\n claimRow(row.pageIndex, row.box);\n\n const body = indexDocument(pages, chromeWords);\n /** The page as it lies, row by row: built only when a string needs it. */\n let lined: StreamIndex | undefined;\n /** Every stream range an entry has claimed, and the entry holding it. */\n const claims: { at: number; end: number; entry: T }[] = [];\n const claimsOver = (o: TextOccurrence) =>\n claims.filter((claim) => o.at < claim.end && claim.at < o.end);\n let cursor = 0;\n for (const entry of inventory) {\n if (entry.repeats) continue;\n const segments = needleSegments(entry.text);\n const needle = segments.join('');\n if (needle.length < MIN_NEEDLE_LENGTH) {\n results.set(entry, { entry, needle, status: 'skipped', occurrences: [] });\n continue;\n }\n let all = findOccurrences(body, segments).filter(\n (o) =>\n (entry.page === undefined ||\n (o.pageIndex === entry.page && o.endPageIndex === entry.page)) &&\n (!entry.region || touchesRegion(o.parts, entry.region))\n );\n let partial: InventoryMatch<T>['partial'];\n /** The words an overprinted match kept, when that is the match. */\n let sparse: { at: number; end: number }[] | undefined;\n if (\n all.length === 0 &&\n segments.length === 1 &&\n entry.page !== undefined &&\n entry.region\n ) {\n const overprinted = overprintedOccurrence(\n body,\n entry.page,\n entry.region,\n needle,\n (offset) =>\n claims.some((claim) => claim.at <= offset && offset < claim.end)\n );\n if (overprinted) {\n all = [overprinted.occurrence];\n sparse = overprinted.claims;\n }\n }\n if (all.length === 0 && entry.page === undefined && !entry.optional) {\n lined ??= indexDocument(pages, chromeWords, linedOrder);\n const found = linedOccurrence(lined, body, segments, (offset) =>\n claims.some((claim) => claim.at <= offset && offset < claim.end)\n );\n if (found) {\n all = [found.occurrence];\n sparse = found.claims;\n }\n }\n // An optional entry is skipped when absent, so its prefix — the costliest\n // search here — is never worth looking for.\n if (all.length === 0 && segments.length === 1 && !entry.optional) {\n const prefix = longestRenderedPrefix(\n body,\n needle,\n entry.page,\n entry.region\n );\n if (prefix) {\n all = [occurrence(body, prefix.at, prefix.at + prefix.length)];\n partial = { matchedChars: prefix.length, totalChars: needle.length };\n }\n }\n if (all.length === 0) {\n results.set(entry, {\n entry,\n needle,\n status: entry.optional ? 'skipped' : 'missing',\n occurrences: [],\n });\n continue;\n }\n let free = all.filter((o) => claimsOver(o).length === 0);\n if (free.length === 0 && !entry.optional) {\n // Every occurrence is taken. One taken by an optional entry — a\n // contents line for a heading the page shows only once — is the\n // author's text, not the field's: the optional claim is released and\n // the entry that was actually written keeps its occurrence.\n const held = all.find((o) => {\n const over = claimsOver(o);\n return over.length > 0 && over.every((claim) => claim.entry.optional);\n });\n if (held) {\n for (const claim of claimsOver(held)) {\n claims.splice(claims.indexOf(claim), 1);\n results.set(claim.entry, {\n entry: claim.entry,\n needle: results.get(claim.entry)?.needle ?? '',\n status: 'skipped',\n occurrences: [],\n });\n }\n free = [held];\n }\n }\n if (free.length === 0) {\n results.set(entry, {\n entry,\n needle,\n status: 'ambiguous',\n occurrences: all,\n });\n continue;\n }\n const chosen =\n (entry.region && insideRegion(free, entry.region)) ??\n free.find((o) => o.at >= cursor) ??\n free[0];\n for (const range of sparse ?? [chosen])\n claims.push({ at: range.at, end: range.end, entry });\n // Rotated text sits past every page's upright text: taking it says\n // nothing about how far reading has got.\n if (chosen.at < body.rotatedFrom) cursor = chosen.at;\n results.set(entry, {\n entry,\n needle,\n status: 'mapped',\n occurrences: [chosen],\n ...(partial && { partial }),\n });\n }\n\n return {\n matches: inventory.map((entry) => results.get(entry) as InventoryMatch<T>),\n chromeWords,\n };\n}\n","/**\n * The authored side of the rendered pass (#344): the text inventory and the\n * requested font families, read off a prepared document's quality facts.\n *\n * `jto_preview` and the ground-truth harness both hand the pass this, so the\n * mapping a measurement scores is the mapping the tool ships. Every pointer\n * in it has already been through the block source maps, and lands on a slot\n * the author can patch.\n */\n\nimport type { QualityFact } from '@json-to-office/quality';\nimport type { FormatName } from './format-adapter';\nimport type { RenderedTextEntry, RequestedFont } from './rendered-analysis';\n\ntype Rec = Record<string, unknown>;\n\n/** The text inventory a format's facts expose, as the analysis reads it. */\nexport function renderedInventoryFromFacts(\n format: FormatName,\n facts: readonly QualityFact[]\n): RenderedTextEntry[] {\n const entries: RenderedTextEntry[] = [];\n for (const fact of facts as readonly (QualityFact & Rec)[]) {\n if (format === 'docx' && fact.kind === 'docx/text') {\n const frame = fact.frame as\n | { widthPt?: number; heightPt?: number }\n | undefined;\n entries.push({\n path: fact.path,\n text: String(fact.text),\n role: fact.role as RenderedTextEntry['role'],\n ...(typeof fact.level === 'number' && { level: fact.level }),\n ...(fact.repeats === true && { repeats: true }),\n ...(fact.optional === true && { optional: true }),\n ...(frame && {\n box: {\n ...(frame.widthPt !== undefined && { widthPt: frame.widthPt }),\n ...(frame.heightPt !== undefined && { heightPt: frame.heightPt }),\n },\n }),\n });\n } else if (format === 'pptx' && fact.kind === 'pptx/text') {\n // A hidden slide is left out of the export: its text is on no page,\n // and looking for it would only claim a visible slide's words.\n if (fact.slideHidden === true) continue;\n // Text set at an angle comes out of the PDF as per-glyph fragments in\n // no reading order, and its box no longer bounds it on the page's\n // axes: it claims an occurrence when one matches, is never reported\n // missing, and is held to no box.\n const turned =\n typeof fact.rotationDeg === 'number' &&\n ((fact.rotationDeg % 360) + 360) % 360 !== 0;\n if (turned) {\n entries.push({\n path: fact.path,\n text: String(fact.text),\n role: 'slide-text',\n optional: true,\n ...(typeof fact.page === 'number' && { page: fact.page }),\n });\n continue;\n }\n const { boxXPt: x, boxYPt: y } = fact;\n const widthPt = fact.boxWidthPt;\n const heightPt = fact.boxHeightPt;\n entries.push({\n path: fact.path,\n text: String(fact.text),\n role: 'slide-text',\n // A slide is its own page and its boxes are where the deck put\n // them, so the text is looked for there before anywhere else.\n ...(typeof fact.page === 'number' && { page: fact.page }),\n ...(typeof x === 'number' &&\n typeof y === 'number' &&\n typeof widthPt === 'number' &&\n typeof heightPt === 'number' && {\n region: {\n xMin: x,\n yMin: y,\n xMax: x + widthPt,\n yMax: y + heightPt,\n },\n }),\n ...((typeof widthPt === 'number' || typeof heightPt === 'number') && {\n box: {\n ...(typeof widthPt === 'number' && { widthPt }),\n ...(typeof heightPt === 'number' && { heightPt }),\n },\n }),\n });\n }\n }\n return entries;\n}\n\n/**\n * Families the pass should find in the PDF: every family the document's\n * text uses (a `font-family` fact, with its pointer) plus every family the\n * render resolved from a declared source. A theme family nothing on the page\n * uses — the default mono face in a report without code — is never embedded\n * and must not read as substituted.\n */\nexport function requestedFontsFromFacts(\n format: FormatName,\n facts: readonly QualityFact[],\n resolvedFonts: readonly { family: string; declared: boolean }[]\n): RequestedFont[] {\n const kind = format === 'docx' ? 'docx/font-family' : 'pptx/font-family';\n const declared = new Set(\n resolvedFonts\n .filter((font) => font.declared)\n .map((font) => font.family.trim().toLowerCase())\n );\n const seen = new Set<string>();\n const requested: RequestedFont[] = [];\n const add = (family: unknown, path?: string) => {\n if (typeof family !== 'string' || family.trim() === '') return;\n const key = family.trim().toLowerCase();\n if (seen.has(key)) return;\n seen.add(key);\n requested.push({\n family: family.trim(),\n ...(path && { path }),\n ...(declared.has(key) && { declared: true }),\n });\n };\n for (const fact of facts as readonly (QualityFact & Rec)[]) {\n if (fact.kind === kind) add(fact.family, fact.path);\n }\n for (const font of resolvedFonts) if (font.declared) add(font.family);\n return requested;\n}\n","/**\n * The block boundary matrix (#343, report portion): every JSON block\n * definition a playground template embeds, invoked at the edges of its own\n * slot schema — minimum and maximum cardinality, every string at its word\n * budget, every figure at its widest — on each bundled theme, with the\n * design fonts and with the fallback faces LibreOffice substitutes when a\n * host lacks them, on A4 and on Letter.\n *\n * The generator is pure and reads nothing but the template it is handed:\n * a definition gains coverage by being embedded, never by being listed here.\n * What the suites do with a case — the static rules under the archetype's\n * profile, the rendered pass over the LibreOffice PDF — is theirs; this\n * module only says what a boundary document is.\n *\n * Two shapes come out. A *block case* is a small report — cover, running\n * head, section opener — with one block at an edge in the body, so a\n * finding names the block that caused it. A *report case* is the template's\n * own document with every invocation at that edge, so the blocks meet each\n * other the way they do in a real report. Both carry their definitions and\n * dependencies inline, so nothing is resolved at render time.\n */\n\nimport {\n blockDependencies,\n blockReferencesFromDocument,\n readBlockDefinitions,\n type BlockSlot,\n type JsonBlockDefinition,\n} from '@json-to-office/shared';\nimport {\n authoredDeckCanvas,\n templateFormat,\n type MatrixFormat,\n type MatrixInventory,\n type MatrixTemplateSource,\n} from './matrix-inventory';\n\ntype Rec = Record<string, unknown>;\n\nexport type MatrixEdge = 'min' | 'max';\nexport type MatrixFont = 'design' | 'fallback';\n/** Page sizes for reports; the two slide shapes theme scales key for decks. */\nexport type MatrixCanvas = 'A4' | 'LETTER' | 'wide169' | 'standard43';\n\nexport interface BlockMatrixDefinition {\n name: string;\n format: MatrixFormat;\n /** The template the definition is embedded in, as the catalog names it. */\n template: string;\n /** Where inside that template: `/props/blocks/<name>`. */\n pointer: string;\n definition: JsonBlockDefinition;\n /** The template's own first invocation, when it has one: the nominal fill. */\n example?: Rec;\n}\n\nexport interface BlockMatrixCase {\n id: string;\n /** The block at its edge, or `report` for the whole template at that edge. */\n block: string;\n edge: MatrixEdge;\n theme: string;\n font: MatrixFont;\n canvas: MatrixCanvas;\n document: Rec;\n}\n\nexport interface BlockMatrixOptions {\n themes: readonly string[];\n fonts?: readonly MatrixFont[];\n edges?: readonly MatrixEdge[];\n canvases?: readonly MatrixCanvas[];\n /** Whether the whole-template report cases are generated. Default true. */\n report?: boolean;\n /** Whether the per-block cases are generated. Default true. */\n blocks?: boolean;\n /**\n * Slot roles the profile under test requires present, so `min` keeps them:\n * the client-report profile's `takeaway` and `source`.\n */\n requiredRoles?: readonly string[];\n}\n\nexport interface CaseConditions {\n font?: MatrixFont;\n canvas?: MatrixCanvas;\n requiredRoles?: readonly string[];\n}\n\n/**\n * The same faces for a deck. PPTX themes take family names and a deck has\n * no theme overrides, so the fallback case carries an inline copy of the\n * bundled theme with these two roles replaced.\n */\nexport const PPTX_FALLBACK_FONTS = {\n heading: 'DejaVu Sans',\n body: 'DejaVu Sans',\n} as const;\n\n/** 16:9 and 4:3 at the sizes the house deck and PowerPoint use. */\nexport const PPTX_CANVAS_SIZE = {\n wide169: { slideWidth: 13.333, slideHeight: 7.5 },\n standard43: { slideWidth: 10, slideHeight: 7.5 },\n} as const;\n\n/**\n * The face every LibreOffice ships with, bundled inside the application on\n * each platform: what a design font degrades to when the host lacks it, and\n * wider than Arial or Calibri, so a fit at these metrics is a fit anywhere.\n * Only the roles a report paints are overridden: a family declared for a\n * role nothing uses is never embedded, and the rendered pass would report\n * it as substituted.\n */\nexport const FALLBACK_FONTS = {\n heading: { family: 'DejaVu Sans' },\n body: { family: 'DejaVu Sans' },\n} as const;\n\n/** A 4x2 PNG: an image with an aspect ratio, and no bytes outside the process. */\nexport const MATRIX_IMAGE =\n 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAACCAYAAABytg0kAAAAFElEQVR42mNk+M9QzwAFjDAGACPuA/8fMSCgAAAAAElFTkSuQmCC';\n\n/**\n * Words of the length a report actually uses, so a budget of N words costs\n * what N report words cost — \"a b c\" at the limit proves nothing.\n */\nconst WORDS = [\n 'performance',\n 'delivery',\n 'retention',\n 'operating',\n 'programme',\n 'contracted',\n 'recommendation',\n 'quarter',\n 'measurement',\n 'ownership',\n 'renewal',\n 'segment',\n 'margin',\n 'baseline',\n 'procurement',\n 'integration',\n];\n\n/**\n * `count` words from the lexicon, starting at `seed`. The first word is the\n * seed's ordinal spelt as a word-length token (`item7`), so no two strings\n * from different seeds share a run of words: the rendered pass matches\n * authored strings against the page text, and two slots that read the\n * same would claim each other's occurrences.\n */\nfunction words(count: number, seed: number, sentence: boolean): string {\n if (count <= 0) return '';\n const out: string[] = [`Item${seed}`];\n for (let i = 1; i < count; i += 1) out.push(WORDS[(seed + i) % WORDS.length]);\n return sentence ? `${out.join(' ')}.` : out.join(' ');\n}\n\n/**\n * The widest number of a given length: a true minus, thousands separators\n * and one decimal, every glyph at tabular width. `−1,234,567.0` for 12.\n */\nexport function widestNumber(length: number, seed = 0): string {\n if (length <= 0) return '';\n if (length === 1) return '9';\n if (length < 4) return `−${'9'.repeat(length - 1)}`;\n const group = (digits: number): string =>\n Array.from({ length: digits }, (_, i) => String(((i + seed) % 9) + 1))\n .join('')\n .replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',');\n // The most digits whose grouped form still leaves room for `−` and `.0`;\n // the decimals then grow to the exact length when a comma boundary\n // makes the next digit cost two.\n let digits = 1;\n while (group(digits + 1).length + 3 <= length) digits += 1;\n const integer = group(digits);\n return `−${integer}.${'0'.repeat(length - integer.length - 2)}`;\n}\n\nconst isRecord = (v: unknown): v is Rec =>\n typeof v === 'object' && v !== null && !Array.isArray(v);\n\n/** Slot names whose strings are figures, not prose. */\nconst NUMERIC_SLOTS = new Set(['value', 'delta', 'cells']);\n\nfunction stringValue(\n slot: BlockSlot,\n name: string,\n edge: MatrixEdge,\n seed: number\n): string {\n if (slot.enum && slot.enum.length > 0) {\n const values = slot.enum.map(String);\n return edge === 'max'\n ? values.reduce((a, b) => (b.length > a.length ? b : a))\n : values[0];\n }\n if (slot.maxLength !== undefined && NUMERIC_SLOTS.has(name)) {\n return widestNumber(\n edge === 'max' ? slot.maxLength : Math.min(3, slot.maxLength),\n seed\n );\n }\n if (slot.maxLength !== undefined && name === 'unit') {\n return edge === 'max'\n ? ` €${'m'.repeat(Math.max(0, slot.maxLength - 2))}`.slice(\n 0,\n slot.maxLength\n )\n : '%';\n }\n if (slot.maxLength !== undefined) {\n const target =\n edge === 'max' ? slot.maxLength : Math.max(1, slot.minLength ?? 1);\n // Whole words up to the length: a word cut in two is not a word a\n // report would print, and the rendered pass would go looking for it.\n let text = `Item${seed}`.slice(0, target);\n let i = 0;\n for (;;) {\n const next = WORDS[(seed + i) % WORDS.length];\n i += 1;\n if (text.length + 1 + next.length > target) {\n if (i > WORDS.length) break;\n continue;\n }\n text += ` ${next}`;\n }\n return text;\n }\n const budget = slot.maxWords ?? 12;\n const count = edge === 'max' ? budget : Math.max(1, Math.min(2, budget));\n const sentence = !slot.oneLine && slot.role !== 'source';\n if (slot.role === 'source') return `Source: ${words(count - 1, seed, false)}`;\n if (name === 'number')\n return edge === 'max' ? words(count, seed, false) : '01';\n return words(count, seed, sentence);\n}\n\nfunction componentValue(\n name: string,\n edge: MatrixEdge,\n seed: number,\n format: MatrixFormat = 'docx'\n): Rec {\n // A deck's content column takes a chart at its widest and a picture at its\n // narrowest: the two ends of what the slot accepts.\n const chart = name === 'chart' || (format === 'pptx' && name === 'content');\n if (chart && (name === 'chart' || edge === 'max')) {\n const categories = edge === 'max' ? 8 : 2;\n const series = edge === 'max' ? 3 : 1;\n return {\n name: 'chart',\n props: {\n // The native column chart: `column` in a report, `bar` standing\n // upright in a deck.\n type: format === 'pptx' ? 'bar' : 'column',\n valAxisTitle:\n edge === 'max' ? `${words(3, seed, false)} (€m)` : 'Revenue (€m)',\n ...(edge === 'max' && { catAxisTitle: words(3, seed + 3, false) }),\n chartColors: ['primary', 'secondary', 'accent'].slice(0, series),\n data: Array.from({ length: series }, (_, s) => ({\n name: words(edge === 'max' ? 3 : 1, seed + s, false),\n // Two words per category is where LibreOffice's native chart\n // still keeps rotated labels clear of the axis title; at three\n // the title is drawn over them, at any category count.\n labels: Array.from({ length: categories }, (_, c) =>\n edge === 'max' ? words(2, seed + c, false) : `Q${c + 1}`\n ),\n values: Array.from(\n { length: categories },\n (_, c) => 1.2 * (c + 1) + s\n ),\n })),\n },\n };\n }\n if (format === 'pptx') {\n // The definition owns a deck image's frame; the picture only has to be one.\n return {\n name: 'image',\n props: { path: MATRIX_IMAGE, alt: words(6, seed, false) },\n };\n }\n return {\n name: 'image',\n props: {\n base64: MATRIX_IMAGE,\n width: edge === 'max' ? (name === 'logo' ? '40%' : '100%') : '25%',\n alt: words(6, seed, false),\n },\n };\n}\n\n/** A slot's value at an edge; `undefined` means \"leave it out\". */\nexport function boundarySlotValue(\n slot: BlockSlot,\n name: string,\n edge: MatrixEdge,\n seed = 0,\n format: MatrixFormat = 'docx'\n): unknown {\n switch (slot.type) {\n case 'string':\n return stringValue(slot, name, edge, seed);\n case 'number':\n case 'integer':\n return edge === 'max' ? slot.maximum ?? 9999 : slot.minimum ?? 0;\n case 'boolean':\n return edge === 'max' ? true : slot.default ?? true;\n case 'component':\n return componentValue(name, edge, seed, format);\n case 'object': {\n const out: Rec = {};\n for (const [key, property] of Object.entries(slot.properties ?? {})) {\n if (edge === 'min' && !property.required) continue;\n const value = boundarySlotValue(\n property,\n key,\n edge,\n seed + key.length,\n format\n );\n if (value !== undefined) out[key] = value;\n }\n return out;\n }\n case 'array': {\n const count =\n edge === 'max'\n ? slot.maxItems ?? 3\n : slot.minItems ?? (slot.required ? 1 : 0);\n const item = slot.items ?? { type: 'string' };\n return Array.from({ length: count }, (_, i) =>\n boundarySlotValue(item, name, edge, seed + i, format)\n );\n }\n default:\n return undefined;\n }\n}\n\n/**\n * One invocation of a definition with every slot at the edge: optional slots\n * left out at `min` (so defaults are what renders), every slot at `max`.\n * A slot whose role the profile requires — a source under every figure —\n * is never optional to that profile, so `requiredRoles` keeps it at `min`.\n * Columns whose cells must count the rows do; nothing else knows a block.\n */\nexport function boundaryInvocation(\n name: string,\n definition: JsonBlockDefinition,\n edge: MatrixEdge,\n seed = 0,\n requiredRoles: readonly string[] = [],\n format: MatrixFormat = 'docx'\n): Rec {\n const slots: Rec = {};\n let offset = seed;\n for (const [slotName, slot] of Object.entries(definition.slots)) {\n offset += slotName.length;\n const required =\n slot.required === true ||\n (slot.role !== undefined && requiredRoles.includes(slot.role));\n if (edge === 'min' && !required) continue;\n const value = boundarySlotValue(slot, slotName, edge, offset, format);\n if (value !== undefined) slots[slotName] = value;\n }\n const labels = slots.labels;\n const columns = slots.columns;\n if (Array.isArray(labels) && Array.isArray(columns)) {\n const cells = definition.slots.columns?.items?.properties?.cells;\n for (const column of columns) {\n if (isRecord(column) && Array.isArray(column.cells) && cells?.items) {\n column.cells = labels.map((_, i) =>\n boundarySlotValue(\n cells.items as BlockSlot,\n 'cells',\n edge,\n offset + i,\n format\n )\n );\n }\n }\n }\n return { name: 'block', props: { ref: name, slots } };\n}\n\n/**\n * The same invocation one past the edge: the first budgeted string a word\n * over, or the first bounded array an item over. What a slot violation\n * looks like, so a suite can prove it is reported at the authored pointer.\n */\nexport function overBudgetInvocation(\n name: string,\n definition: JsonBlockDefinition,\n seed = 0,\n format: MatrixFormat = 'docx'\n): { invocation: Rec; slot: string; kind: 'words' | 'items' } | undefined {\n const invocation = boundaryInvocation(\n name,\n definition,\n 'max',\n seed,\n [],\n format\n );\n const slots = (invocation.props as Rec).slots as Rec;\n for (const [slotName, slot] of Object.entries(definition.slots)) {\n if (slot.type === 'string' && slot.maxWords !== undefined) {\n slots[slotName] = `${words(slot.maxWords + 1, seed, !slot.oneLine)}`;\n return { invocation, slot: slotName, kind: 'words' };\n }\n }\n for (const [slotName, slot] of Object.entries(definition.slots)) {\n if (\n slot.type === 'array' &&\n slot.maxItems !== undefined &&\n Array.isArray(slots[slotName])\n ) {\n const items = slots[slotName] as unknown[];\n slots[slotName] = [...items, items[0]];\n return { invocation, slot: slotName, kind: 'items' };\n }\n }\n return undefined;\n}\n\n/**\n * Every definition a template embeds, with the template's own example of it.\n * The definitions come from the extraction MCP discovery publishes, so the\n * matrix covers exactly what an agent can copy; a template whose definitions\n * do not validate is an error here, not a template with fewer blocks.\n */\nexport function enumerateBlockDefinitions(\n template: unknown,\n templateName: string\n): BlockMatrixDefinition[] {\n const format = templateFormat(templateName, template);\n if (!format)\n throw new Error(`${templateName} is neither a report nor a deck.`);\n const references = blockReferencesFromDocument(template, {\n template: templateName,\n format,\n });\n const declared = Object.keys(readBlockDefinitions(template));\n if (references.length !== declared.length)\n throw new Error(\n `${templateName}: discovery publishes ${references.length} of ${declared.length} definitions; the rest do not validate.`\n );\n const examples = new Map<string, Rec>();\n const walk = (node: unknown): void => {\n if (Array.isArray(node)) {\n node.forEach(walk);\n return;\n }\n if (!isRecord(node)) return;\n const props = node.props;\n if (\n node.name === 'block' &&\n isRecord(props) &&\n typeof props.ref === 'string' &&\n !examples.has(props.ref)\n ) {\n examples.set(props.ref, isRecord(props.slots) ? props.slots : {});\n }\n walk(node.children);\n };\n walk(isRecord(template) ? template.children : undefined);\n return references.map((reference) => ({\n name: reference.name,\n format,\n template: templateName,\n pointer: reference.definitionPointer,\n definition: reference.definition,\n ...(examples.has(reference.name) && {\n example: examples.get(reference.name),\n }),\n }));\n}\n\n/** The block names a report's chrome is made of, when the template has them. */\nconst CHROME = ['cover', 'running-head', 'section-opener'] as const;\n\nfunction nominal(\n entry: BlockMatrixDefinition | undefined,\n name: string,\n definitions: Record<string, JsonBlockDefinition>\n): Rec | undefined {\n if (!entry) return undefined;\n if (entry.example) {\n return {\n name: 'block',\n props: { ref: name, slots: structuredClone(entry.example) },\n };\n }\n return boundaryInvocation(\n name,\n definitions[name],\n 'min',\n 0,\n [],\n entry.format\n );\n}\n\n/** The template's own image slot values point at files; the matrix inlines. */\nfunction inlineImages(value: unknown, format: MatrixFormat = 'docx'): unknown {\n if (Array.isArray(value)) return value.map((v) => inlineImages(v, format));\n if (!isRecord(value)) return value;\n if (\n value.name === 'image' &&\n isRecord(value.props) &&\n typeof value.props.path === 'string' &&\n !value.props.path.startsWith('data:')\n ) {\n const props: Rec =\n format === 'pptx'\n ? { ...value.props, path: MATRIX_IMAGE }\n : { ...value.props, base64: MATRIX_IMAGE };\n if (format === 'docx') delete props.path;\n return { ...value, props };\n }\n if (value.name === 'highcharts')\n return componentValue('chart', 'min', 0, format);\n return Object.fromEntries(\n Object.entries(value).map(([k, v]) => [k, inlineImages(v, format)])\n );\n}\n\nfunction withDefinitions(\n definitions: Record<string, JsonBlockDefinition>,\n names: Iterable<string>\n): Record<string, JsonBlockDefinition> {\n const out: Record<string, JsonBlockDefinition> = {};\n for (const name of names) {\n if (!(name in definitions)) continue;\n out[name] = definitions[name];\n for (const dependency of blockDependencies(definitions, name)) {\n if (dependency in definitions) out[dependency] = definitions[dependency];\n }\n }\n return out;\n}\n\nfunction conditions(\n props: Rec,\n children: unknown[],\n theme: string,\n font: MatrixFont,\n canvas: MatrixCanvas\n): Rec {\n // Every section takes the canvas, keeping whatever else its own page\n // override says (a template section can state margins of its own).\n const sections = children.map((child) => {\n if (!isRecord(child) || child.name !== 'section') return child;\n const props = isRecord(child.props) ? child.props : {};\n const page = isRecord(props.page) ? props.page : {};\n return { ...child, props: { ...props, page: { ...page, size: canvas } } };\n });\n // A native chart is drawn by the office-open renderer only.\n const chart = JSON.stringify(sections).includes('\"name\":\"chart\"');\n return {\n name: 'docx',\n ...(chart && { renderer: 'office-open' }),\n props: {\n ...props,\n theme,\n ...(font === 'fallback' && { themeOverrides: { fonts: FALLBACK_FONTS } }),\n },\n children: sections,\n };\n}\n\n// A seed no slot offset reaches (offsets are sums of slot-name lengths):\n// body copy must not read as the prefix of any slot's text, or the matcher\n// could map a dropped title onto the paragraph after it.\nconst BODY_COPY = words(40, 1000, true);\n\n/**\n * A small report with one block at its edge: the chrome at the template's\n * nominal values (or at the edge, when the chrome block is the one under\n * test), the block in the first body section, body copy after it.\n */\nexport function blockCaseDocument(\n entries: readonly BlockMatrixDefinition[],\n name: string,\n edge: MatrixEdge,\n theme: string,\n { font = 'design', canvas = 'A4', requiredRoles = [] }: CaseConditions = {}\n): Rec {\n const definitions = Object.fromEntries(\n entries.map((e) => [e.name, e.definition])\n );\n const byName = new Map(entries.map((e) => [e.name, e]));\n const under = (chrome: string): Rec | undefined =>\n chrome === name\n ? boundaryInvocation(name, definitions[name], edge, 0, requiredRoles)\n : (inlineImages(nominal(byName.get(chrome), chrome, definitions)) as\n | Rec\n | undefined);\n const cover = under('cover');\n const runningHead = under('running-head');\n const opener = under('section-opener');\n const body: unknown[] = [];\n if (!CHROME.includes(name as (typeof CHROME)[number])) {\n // A notes block collects sources: give it a figure to collect from.\n if (name === 'footnotes' && byName.has('figure')) {\n body.push(\n inlineImages(nominal(byName.get('figure'), 'figure', definitions))\n );\n }\n body.push(\n boundaryInvocation(name, definitions[name], edge, 0, requiredRoles)\n );\n }\n body.push({ name: 'paragraph', props: { text: BODY_COPY } });\n const used = new Set<string>([\n name,\n ...CHROME,\n ...(name === 'footnotes' ? ['figure'] : []),\n ]);\n const children: unknown[] = [];\n if (cover) children.push({ name: 'section', children: [cover] });\n children.push({\n name: 'section',\n children: [runningHead, opener, ...body].filter((c) => c !== undefined),\n });\n return conditions(\n {\n metadata: {\n title: 'Boundary report',\n author: 'jto-ops block matrix',\n company: 'Example client',\n date: 'September 2026',\n },\n blocks: withDefinitions(definitions, used),\n },\n children,\n theme,\n font,\n canvas\n );\n}\n\n/** The whole template with every invocation at the edge. */\nexport function reportCaseDocument(\n template: unknown,\n edge: MatrixEdge,\n theme: string,\n { font = 'design', canvas = 'A4', requiredRoles = [] }: CaseConditions = {}\n): Rec {\n if (!isRecord(template) || !isRecord(template.props))\n throw new Error('A template is a document with props.');\n const definitions = readBlockDefinitions(template);\n let seed = 0;\n const rewrite = (node: unknown): unknown => {\n if (Array.isArray(node)) return node.map(rewrite);\n if (!isRecord(node)) return node;\n const props = node.props;\n if (\n node.name === 'block' &&\n isRecord(props) &&\n typeof props.ref === 'string' &&\n definitions[props.ref]\n ) {\n seed += 7;\n return boundaryInvocation(\n props.ref,\n definitions[props.ref],\n edge,\n seed,\n requiredRoles\n );\n }\n if (\n node.name === 'paragraph' &&\n isRecord(props) &&\n typeof props.text === 'string'\n ) {\n return {\n ...node,\n props: { ...props, text: edge === 'max' ? BODY_COPY : props.text },\n };\n }\n return Array.isArray(node.children)\n ? { ...node, children: rewrite(node.children) }\n : node;\n };\n const props: Rec = structuredClone(template.props);\n delete props.theme;\n delete props.themeOverrides;\n return conditions(\n props,\n rewrite(template.children) as unknown[],\n theme,\n font,\n canvas\n );\n}\n\n/** Every case the options span, in a stable order. */\nexport function generateBlockMatrix(\n template: unknown,\n templateName: string,\n options: BlockMatrixOptions\n): BlockMatrixCase[] {\n const entries = enumerateBlockDefinitions(template, templateName);\n const fonts = options.fonts ?? ['design', 'fallback'];\n const edges = options.edges ?? ['min', 'max'];\n const canvases = options.canvases ?? ['A4'];\n const cases: BlockMatrixCase[] = [];\n for (const theme of options.themes)\n for (const font of fonts)\n for (const canvas of canvases)\n for (const edge of edges) {\n if (options.blocks !== false)\n for (const entry of entries)\n cases.push({\n id: `${entry.name}@${edge}/${theme}/${font}/${canvas}`,\n block: entry.name,\n edge,\n theme,\n font,\n canvas,\n document: blockCaseDocument(entries, entry.name, edge, theme, {\n font,\n canvas,\n requiredRoles: options.requiredRoles,\n }),\n });\n if (options.report !== false)\n cases.push({\n id: `report@${edge}/${theme}/${font}/${canvas}`,\n block: 'report',\n edge,\n theme,\n font,\n canvas,\n document: reportCaseDocument(template, edge, theme, {\n font,\n canvas,\n requiredRoles: options.requiredRoles,\n }),\n });\n }\n return cases;\n}\n\nexport interface DeckCaseConditions extends CaseConditions {\n /**\n * A bundled theme's object, for the fallback-font case: a deck has no\n * theme overrides, so the faces go into an inline copy of the theme.\n */\n themeObject?: (name: string) => Rec | undefined;\n}\n\n/**\n * A deck under the case's conditions. The theme is the one named, or the\n * template's own object when the template carries one; the fallback faces\n * replace heading and body in an inline copy of the bundled theme; a canvas\n * other than the deck's own sets the slide size, and a deck already on it\n * keeps its authored size.\n */\nfunction deckConditions(\n props: Rec,\n children: unknown[],\n theme: string,\n font: MatrixFont,\n canvas: MatrixCanvas,\n themeObject?: (name: string) => Rec | undefined\n): Rec {\n const out: Rec = structuredClone(props);\n // The suite names the profile under test; a declared one would compete.\n delete out.qualityProfile;\n const inline = typeof props.theme !== 'string';\n if (!inline || theme !== 'inline') {\n if (font === 'fallback') {\n const base = themeObject?.(theme);\n if (!base)\n throw new Error(\n `The fallback case on \"${theme}\" needs the theme's object.`\n );\n const fonts = isRecord(base.fonts) ? base.fonts : {};\n out.theme = {\n ...structuredClone(base),\n fonts: { ...fonts, ...PPTX_FALLBACK_FONTS },\n };\n } else {\n out.theme = theme;\n }\n }\n if (\n (canvas === 'wide169' || canvas === 'standard43') &&\n authoredDeckCanvas(props) !== canvas\n )\n Object.assign(out, PPTX_CANVAS_SIZE[canvas]);\n return { name: 'pptx', props: out, children };\n}\n\nfunction templateProps(template: unknown): Rec {\n if (!isRecord(template) || !isRecord(template.props))\n throw new Error('A template is a document with props.');\n return template.props;\n}\n\n/**\n * A deck with one block at its edge: the template's cover at its nominal\n * values first, when the template has one and it is not the block under\n * test, so the slide under test is where footer rules start counting.\n */\nexport function deckBlockCaseDocument(\n template: unknown,\n entries: readonly BlockMatrixDefinition[],\n name: string,\n edge: MatrixEdge,\n theme: string,\n {\n font = 'design',\n canvas = 'wide169',\n requiredRoles = [],\n themeObject,\n }: DeckCaseConditions = {}\n): Rec {\n const props = templateProps(template);\n const definitions = Object.fromEntries(\n entries.map((e) => [e.name, e.definition])\n );\n const byName = new Map(entries.map((e) => [e.name, e]));\n const used = new Set<string>([name]);\n const slides: unknown[] = [];\n if (name !== 'cover' && byName.has('cover')) {\n used.add('cover');\n slides.push({\n name: 'slide',\n children: [\n inlineImages(\n nominal(byName.get('cover'), 'cover', definitions),\n 'pptx'\n ),\n ],\n });\n }\n slides.push({\n name: 'slide',\n children: [\n boundaryInvocation(\n name,\n definitions[name],\n edge,\n 0,\n requiredRoles,\n 'pptx'\n ),\n ],\n });\n return deckConditions(\n { ...props, blocks: withDefinitions(definitions, used) },\n slides,\n theme,\n font,\n canvas,\n themeObject\n );\n}\n\n/** The whole deck with every invocation at the edge. */\nexport function deckReportCaseDocument(\n template: unknown,\n edge: MatrixEdge,\n theme: string,\n {\n font = 'design',\n canvas = 'wide169',\n requiredRoles = [],\n themeObject,\n }: DeckCaseConditions = {}\n): Rec {\n const props = templateProps(template);\n const definitions = readBlockDefinitions(template);\n let seed = 0;\n const rewrite = (node: unknown): unknown => {\n if (Array.isArray(node)) return node.map(rewrite);\n if (!isRecord(node)) return node;\n const nodeProps = node.props;\n if (\n node.name === 'block' &&\n isRecord(nodeProps) &&\n typeof nodeProps.ref === 'string' &&\n definitions[nodeProps.ref]\n ) {\n seed += 7;\n return boundaryInvocation(\n nodeProps.ref,\n definitions[nodeProps.ref],\n edge,\n seed,\n requiredRoles,\n 'pptx'\n );\n }\n return Array.isArray(node.children)\n ? { ...node, children: rewrite(node.children) }\n : node;\n };\n return deckConditions(\n props,\n rewrite((template as Rec).children) as unknown[],\n theme,\n font,\n canvas,\n themeObject\n );\n}\n\nexport interface MatrixCase extends BlockMatrixCase {\n format: MatrixFormat;\n template: string;\n /** The profile the template's blueprint judges it under, when one does. */\n profile?: string;\n}\n\n/** Which of the inventory's supported conditions a run takes. */\nexport interface MatrixSelection {\n templates?: readonly string[];\n themes?: readonly string[];\n fonts?: readonly MatrixFont[];\n edges?: readonly MatrixEdge[];\n canvases?: readonly MatrixCanvas[];\n /** Whether the per-definition cases are generated. Default true. */\n blocks?: boolean;\n /** Whether the whole-template cases are generated. Default true. */\n report?: boolean;\n}\n\nconst stem = (template: string) => template.replace(/\\.(docx|pptx)\\.json$/, '');\n\n/**\n * Every case the inventory supports under a selection, in a stable order:\n * template, theme, font, canvas, edge, then each definition before the\n * whole template. A template whose definitions all lack slots has no\n * whole-template case: at either edge it is the template itself, which is\n * gallery coverage.\n */\nexport function generateMatrixCases(\n inventory: MatrixInventory,\n templates: readonly MatrixTemplateSource[],\n selection: MatrixSelection = {},\n { themeObject }: { themeObject?: (name: string) => Rec | undefined } = {}\n): MatrixCase[] {\n const pick = <T extends string>(\n supported: readonly T[],\n wanted: readonly T[] | undefined\n ): T[] => supported.filter((value) => !wanted || wanted.includes(value));\n const cases: MatrixCase[] = [];\n for (const summary of inventory.templates) {\n if (selection.templates && !selection.templates.includes(summary.template))\n continue;\n const source = templates.find((t) => t.name === summary.template);\n const entries = inventory.entries.filter(\n (e) => e.template === summary.template\n );\n if (!source || entries.length === 0) continue;\n const definitions = enumerateBlockDefinitions(\n source.document,\n summary.template\n );\n const withSlots = entries.some((e) => e.edges.includes('min'));\n for (const theme of pick(summary.conditions.themes, selection.themes))\n for (const font of pick(summary.conditions.fonts, selection.fonts))\n for (const canvas of pick(\n summary.conditions.canvases,\n selection.canvases\n ))\n for (const edge of pick(summary.conditions.edges, selection.edges)) {\n const shared = {\n format: summary.format,\n template: summary.template,\n ...(summary.profile && { profile: summary.profile }),\n edge,\n theme,\n font,\n canvas,\n };\n const conditions = {\n font,\n canvas,\n requiredRoles: summary.requiredRoles,\n themeObject,\n };\n if (selection.blocks !== false)\n for (const entry of entries) {\n if (!entry.edges.includes(edge)) continue;\n cases.push({\n ...shared,\n id: `${stem(summary.template)}/${entry.name}@${edge}/${theme}/${font}/${canvas}`,\n block: entry.name,\n document:\n summary.format === 'docx'\n ? blockCaseDocument(\n definitions,\n entry.name,\n edge,\n theme,\n conditions\n )\n : deckBlockCaseDocument(\n source.document,\n definitions,\n entry.name,\n edge,\n theme,\n conditions\n ),\n });\n }\n if (selection.report !== false && withSlots)\n cases.push({\n ...shared,\n id: `${stem(summary.template)}/report@${edge}/${theme}/${font}/${canvas}`,\n block: 'report',\n document:\n summary.format === 'docx'\n ? reportCaseDocument(\n source.document,\n edge,\n theme,\n conditions\n )\n : deckReportCaseDocument(\n source.document,\n edge,\n theme,\n conditions\n ),\n });\n }\n }\n return cases;\n}\n","/**\n * The matrix inventory (#343): every JSON block definition the playground\n * templates embed, found by the same extraction MCP discovery publishes as\n * `jto://blocks`, and for each one the conditions it is supported on — theme,\n * canvas, font, slot edge — with a stated reason for every condition it is\n * not. Nothing is omitted silently: a condition a definition does not get is\n * an exclusion with a reason, and a template with no definitions is listed\n * with none.\n *\n * Applicability is decided from the template, never from a list of blocks:\n *\n * - A template on a bundled theme is authored against theme roles and\n * tokens, so its definitions are supported on every bundled theme of the\n * format, in the design faces and in the fallback faces. A report's are\n * supported on every page size its themes scale for; a deck's on the slide\n * shape the template is drawn on, because that is where its slot budgets\n * are measured — another shape lays out, but holds fewer words.\n * - A template carrying its own theme object is authored against that\n * object: absolute geometry fitted to its own faces and its own canvas. Its\n * definitions are supported there only; a bundled theme, another canvas or\n * a wider fallback face would be a different design, not a boundary.\n * - A definition with no slots has one document, so `min` is `max`.\n *\n * The profile a template's definitions are judged under is the one the\n * blueprint drawing on that template names; a template no blueprint uses is\n * judged under the format's default. The module is pure: themes, blueprints\n * and profiles are handed in, so a new theme or template gains its cases\n * without an edit here.\n */\n\nimport {\n blockReferencesFromDocument,\n readBlockDefinitions,\n} from '@json-to-office/shared';\nimport type { MatrixCanvas, MatrixEdge, MatrixFont } from './block-matrix';\n\ntype Rec = Record<string, unknown>;\n\nexport type MatrixFormat = 'docx' | 'pptx';\n\nexport interface MatrixTemplateSource {\n /** File name, as the gallery and `jto://blocks` name it. */\n name: string;\n document: unknown;\n}\n\nexport interface MatrixBlueprintSource {\n id: string;\n format: string;\n profile: string;\n /** The template whose definitions the blueprint invokes. */\n definitions: string;\n}\n\nexport interface MatrixProfileSource {\n id: string;\n rules?: Readonly<\n Record<string, { parameters?: Readonly<Record<string, unknown>> }>\n >;\n}\n\nexport interface MatrixInventoryOptions {\n /** The bundled theme names of each format. */\n themes: Readonly<Record<MatrixFormat, readonly string[]>>;\n blueprints?: readonly MatrixBlueprintSource[];\n /** Shipped profiles by id, to read the roles a profile requires present. */\n profiles?: Readonly<Record<string, MatrixProfileSource>>;\n}\n\nexport interface MatrixExclusion {\n /** `theme minimal`, `canvas standard43`, `font fallback`, `edge min`. */\n condition: string;\n reason: string;\n}\n\nexport interface MatrixConditions {\n themes: string[];\n canvases: MatrixCanvas[];\n fonts: MatrixFont[];\n edges: MatrixEdge[];\n}\n\nexport interface MatrixInventoryTemplate {\n template: string;\n format: MatrixFormat;\n /** The bundled theme the template names, or `inline` for a theme object. */\n theme: string;\n /** The blueprint profile its definitions are judged under, when one uses it. */\n profile?: string;\n /** Slot roles that profile requires present, so `min` keeps them. */\n requiredRoles: string[];\n /** Every definition the template embeds, in authored order. */\n definitions: string[];\n /** The conditions a whole-template case is supported on. */\n conditions: MatrixConditions;\n exclusions: MatrixExclusion[];\n}\n\nexport interface MatrixInventoryEntry extends MatrixConditions {\n /** `<template>#<definition pointer>`. */\n id: string;\n template: string;\n format: MatrixFormat;\n name: string;\n definitionPointer: string;\n /** Definitions this one invokes, dependencies first. */\n dependencies: string[];\n profile?: string;\n requiredRoles: string[];\n exclusions: MatrixExclusion[];\n}\n\nexport interface MatrixInventory {\n templates: MatrixInventoryTemplate[];\n entries: MatrixInventoryEntry[];\n}\n\n/** Every condition of a format, whether or not a definition gets it. */\nexport function matrixUniverse(\n format: MatrixFormat,\n themes: readonly string[]\n): MatrixConditions {\n return {\n themes: [...themes],\n canvases: format === 'docx' ? ['A4', 'LETTER'] : ['wide169', 'standard43'],\n fonts: ['design', 'fallback'],\n edges: ['min', 'max'],\n };\n}\n\nconst isRecord = (v: unknown): v is Rec =>\n typeof v === 'object' && v !== null && !Array.isArray(v);\n\nexport function templateFormat(\n name: string,\n document: unknown\n): MatrixFormat | undefined {\n const root = isRecord(document) ? document.name : undefined;\n if (root === 'docx' || root === 'pptx') return root;\n if (name.endsWith('.docx.json')) return 'docx';\n if (name.endsWith('.pptx.json')) return 'pptx';\n return undefined;\n}\n\n/** The canvas a deck's own slide size is: 16:9 or 4:3, by aspect ratio. */\nexport function authoredDeckCanvas(props: Rec): MatrixCanvas {\n const width = typeof props.slideWidth === 'number' ? props.slideWidth : 10;\n const height =\n typeof props.slideHeight === 'number' ? props.slideHeight : 7.5;\n return Math.abs(width / height - 16 / 9) < Math.abs(width / height - 4 / 3)\n ? 'wide169'\n : 'standard43';\n}\n\nfunction requiredRoles(\n format: MatrixFormat,\n profile: MatrixProfileSource | undefined\n): string[] {\n const required =\n profile?.rules?.[`${format}/required-chrome`]?.parameters?.required;\n return Array.isArray(required)\n ? required.filter((role): role is string => typeof role === 'string')\n : [];\n}\n\nfunction templateConditions(\n format: MatrixFormat,\n props: Rec,\n universe: MatrixConditions\n): {\n theme: string;\n conditions: MatrixConditions;\n exclusions: MatrixExclusion[];\n} {\n const named = typeof props.theme === 'string' ? props.theme : undefined;\n // A deck's slot budgets are measured on the slide it is drawn on: another\n // shape still lays out, but the words a slot holds there are no promise.\n // A report flows, so every page size its themes scale for is a boundary.\n const canvas: MatrixCanvas | undefined =\n format === 'pptx' ? authoredDeckCanvas(props) : undefined;\n const canvasExclusions = (reason: string): MatrixExclusion[] =>\n canvas === undefined\n ? []\n : universe.canvases\n .filter((c) => c !== canvas)\n .map((c) => ({ condition: `canvas ${c}`, reason }));\n if (named !== undefined && universe.themes.includes(named)) {\n return {\n theme: named,\n conditions: {\n ...structuredClone(universe),\n ...(canvas !== undefined && { canvases: [canvas] }),\n },\n exclusions: canvasExclusions(\n `slot budgets are measured on the ${canvas} slide the template is drawn on; another shape lays out, but holds fewer words`\n ),\n };\n }\n // A theme object, or a name that is no bundled theme: the definitions are\n // authored against something only this template carries.\n const theme = named ?? 'inline';\n const why =\n named === undefined\n ? \"authored against the template's own theme object\"\n : `authored against \"${named}\", which is no bundled theme`;\n const exclusions: MatrixExclusion[] = [\n ...universe.themes.map((t) => ({ condition: `theme ${t}`, reason: why })),\n ...canvasExclusions(\n \"absolute geometry fitted to the template's own slide size\"\n ),\n ...(canvas === undefined\n ? universe.canvases\n .filter((c) => c !== 'A4')\n .map((c) => ({\n condition: `canvas ${c}`,\n reason: \"absolute geometry fitted to the template's own page\",\n }))\n : []),\n {\n condition: 'font fallback',\n reason:\n \"lines fitted to the template's own faces; a wider face is another design, not a boundary\",\n },\n ];\n return {\n theme,\n conditions: {\n themes: [theme],\n canvases: [canvas ?? 'A4'],\n fonts: ['design'],\n edges: [...universe.edges],\n },\n exclusions,\n };\n}\n\n/** The inventory over every template handed in, in the order given. */\nexport function buildMatrixInventory(\n templates: readonly MatrixTemplateSource[],\n options: MatrixInventoryOptions\n): MatrixInventory {\n const inventory: MatrixInventory = { templates: [], entries: [] };\n for (const source of templates) {\n const format = templateFormat(source.name, source.document);\n if (!format || !isRecord(source.document)) continue;\n const props = isRecord(source.document.props) ? source.document.props : {};\n const universe = matrixUniverse(format, options.themes[format]);\n const blueprint = options.blueprints?.find(\n (b) => b.format === format && b.definitions === source.name\n );\n const profile = blueprint\n ? options.profiles?.[blueprint.profile]\n : undefined;\n const roles = requiredRoles(format, profile);\n const { theme, conditions, exclusions } = templateConditions(\n format,\n props,\n universe\n );\n const definitions = Object.keys(readBlockDefinitions(source.document));\n const references = blockReferencesFromDocument(source.document, {\n template: source.name,\n format,\n });\n const templateExclusions = [...exclusions];\n // Discovery publishes nothing from a template whose definitions do not\n // validate; the matrix says so rather than covering fewer blocks.\n if (definitions.length > 0 && references.length === 0) {\n templateExclusions.push({\n condition: 'definitions',\n reason: 'the definitions do not validate, so discovery publishes none',\n });\n }\n inventory.templates.push({\n template: source.name,\n format,\n theme,\n ...(blueprint && { profile: blueprint.profile }),\n requiredRoles: roles,\n definitions,\n conditions,\n exclusions: templateExclusions,\n });\n for (const reference of references) {\n const slotless =\n Object.keys(reference.definition.slots ?? {}).length === 0;\n inventory.entries.push({\n id: `${source.name}#${reference.definitionPointer}`,\n template: source.name,\n format,\n name: reference.name,\n definitionPointer: reference.definitionPointer,\n dependencies: reference.dependencies,\n ...(blueprint && { profile: blueprint.profile }),\n requiredRoles: roles,\n themes: [...conditions.themes],\n canvases: [...conditions.canvases],\n fonts: [...conditions.fonts],\n edges: slotless ? ['max'] : [...conditions.edges],\n exclusions: [\n ...exclusions,\n ...(slotless\n ? [\n {\n condition: 'edge min',\n reason: 'no slots, so min and max are the same document',\n },\n ]\n : []),\n ],\n });\n }\n }\n return inventory;\n}\n\n/**\n * Conditions of the universe an entry neither gets nor excludes, and\n * conditions it both gets and excludes. Empty for a complete inventory.\n */\nexport function inventoryGaps(\n entry: MatrixConditions & { exclusions: readonly MatrixExclusion[] },\n universe: MatrixConditions\n): string[] {\n const excluded = new Set(entry.exclusions.map((e) => e.condition));\n const gaps: string[] = [];\n const check = (\n kind: string,\n all: readonly string[],\n got: readonly string[]\n ) => {\n for (const value of all) {\n const condition = `${kind} ${value}`;\n const has = got.includes(value);\n if (has && excluded.has(condition)) gaps.push(`${condition}: both`);\n if (!has && !excluded.has(condition)) gaps.push(`${condition}: neither`);\n }\n };\n check('theme', universe.themes, entry.themes);\n check('canvas', universe.canvases, entry.canvases);\n check('font', universe.fonts, entry.fonts);\n check('edge', universe.edges, entry.edges);\n return gaps;\n}\n\n/** One line per template and entry, for a suite's log. */\nexport function describeInventory(inventory: MatrixInventory): string {\n const lines: string[] = [];\n for (const template of inventory.templates) {\n lines.push(\n `${template.template} (${template.format}, theme ${template.theme}${\n template.profile ? `, profile ${template.profile}` : ''\n }): ${template.definitions.length} definition(s)`\n );\n for (const entry of inventory.entries.filter(\n (e) => e.template === template.template\n )) {\n lines.push(\n ` ${entry.name}: themes ${entry.themes.join(' ')} · canvases ${entry.canvases.join(\n ' '\n )} · fonts ${entry.fonts.join(' ')} · edges ${entry.edges.join(' ')}`\n );\n const reasons = new Map<string, string[]>();\n for (const exclusion of entry.exclusions) {\n reasons.set(exclusion.reason, [\n ...(reasons.get(exclusion.reason) ?? []),\n exclusion.condition,\n ]);\n }\n for (const [reason, conditions] of reasons)\n lines.push(` not ${conditions.join(', ')}: ${reason}`);\n }\n }\n return lines.join('\\n');\n}\n"],"mappings":"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","names":["path","fs","fs","path","fs","path","synthesizeFamilyName","rewriteFontFamilyName","path","fs","synthesizeFamilyName","rewriteFontFamilyName","fs","path","synthesizeFamilyName","rewriteFontFamilyName","path","fs","synthesizeFamilyName","rewriteFontFamilyName","resolve","path","fs","cached","execFile","path","words","resolve","execFile","fs","os","path","pdftoppmCandidates","run","resolve","pdftoppmPromise","resolvePdftoppm","execFile","path","run","resolve","order","words","path","cached","words","worst","occurrence","path","path","blockReferencesFromDocument","readBlockDefinitions","conditions","digits","isRecord","_","requiredRoles","blockReferencesFromDocument","readBlockDefinitions","props","conditions"]}