@json-to-office/jto-ops 4.4.4 → 4.5.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/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/** 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\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/** Area the two word boxes share, in square points; 0 when they miss. */\nfunction intersects(a: PdfTextWord, b: PdfTextWord): number {\n const w = Math.min(a.xMax, b.xMax) - Math.max(a.xMin, b.xMin);\n const h = Math.min(a.yMax, b.yMax) - Math.max(a.yMin, b.yMin);\n return w > 0 && h > 0 ? w * h : 0;\n}\n\nfunction area(w: PdfTextWord): number {\n return Math.max(0, w.xMax - w.xMin) * Math.max(0, w.yMax - w.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.\n for (const match of matches) {\n const box = match.entry.box;\n if (!box || match.status !== 'mapped') continue;\n for (const o of match.occurrences) {\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 .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 || pageIndex === pages.length - 1) {\n return;\n }\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 >= 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 const section = owner\n ? /^\\/children\\/\\d+/.exec(owner.path)?.[0]\n : undefined;\n findings.push(\n finding({\n ruleId: 'rendered/page-underfilled',\n mapping: section ? 'mapped' : 'unmapped',\n page: pageIndex + 1,\n path: section ?? '',\n message: `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:\n 'Let the next section continue on this page, or give the page the table or chart its argument owes; a short section on its own page reads as unfinished.',\n evidence: {\n summary: 'Share of the body area the page paints, header to footer',\n expected: `≥ ${Math.round(MINIMUM_PAGE_FILL * 100)}%`,\n actual: fill,\n },\n context: { fill },\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\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 * 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/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, other than the first and the last, whose ink stops less than halfway down the body area before the next page begins (`context.fill` is the share used): a section that ends early on its own page, argued in a paragraph where a table or chart was owed.',\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/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 */\n\nimport type { PdfTextWord, PdfTextPage } 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 /** 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}\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 */\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 pages.forEach((page, pageIndex) => {\n for (const index of readingOrder(page.words)) {\n const word = page.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 });\n return { pages, refs, fragments, positions, stream: fragments.join('') };\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 */\nexport function readingOrder(words: readonly PdfTextWord[]): number[] {\n // Rotated text — a chart's value-axis title — comes as boxes taller than\n // they are wide; rows and columns mean nothing to it, so it keeps the\n // order poppler gave it, in the slots it had.\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 const upright = uprightOrder(\n words,\n words.map((_, i) => i).filter((i) => !rotated.has(i))\n );\n const order: number[] = [];\n let next = 0;\n for (let i = 0; i < words.length; i++) {\n if (rotated.has(i)) order.push(i);\n else order.push(upright[next++]);\n }\n return order;\n}\n\nfunction uprightOrder(\n words: readonly PdfTextWord[],\n indices: readonly number[]\n): number[] {\n const rows: number[][] = [];\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 row = rows[rows.length - 1];\n if (row && sameRow(words[row[0]], words[i])) row.push(i);\n else rows.push([i]);\n }\n const fragmentsOf = (row: number[]): RowFragment[] => {\n const sorted = [...row].sort((a, b) => words[a].xMin - words[b].xMin);\n const out: RowFragment[] = [];\n for (const i of sorted) {\n const last = out[out.length - 1];\n const w = words[i];\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.\n const height = w.yMax - w.yMin;\n if (last && w.xMin - last.xMax <= height / 2) {\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 = rows.map((row) => ({\n yMin: Math.min(...row.map((i) => words[i].yMin)),\n yMax: Math.max(...row.map((i) => words[i].yMax)),\n }));\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 fragment across two columns is prose\n // under the table: it closes the cluster and opens the next.\n interface Column {\n xMin: number;\n xMax: number;\n fragments: RowFragment[];\n }\n const order: number[] = [];\n let cluster: { rows: number[]; columns: Column[]; multi: boolean } | null =\n null;\n const flush = (): void => {\n if (!cluster) return;\n if (!cluster.multi) {\n for (const r of cluster.rows)\n for (const f of fragments[r]) order.push(...f.words);\n } else {\n cluster.columns.sort((a, b) => a.xMin - b.xMin);\n for (const column of cluster.columns) {\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) order.push(...f.words);\n }\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: [], multi: false };\n cluster.rows.push(r);\n if (fragments[r].length > 1) cluster.multi = true;\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 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 hits.push(occurrence(index, at, end));\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\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.\n */\nexport function longestRenderedPrefix(\n index: StreamIndex,\n needle: string\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 let length = MIN_PARTIAL_PREFIX;\n while (\n length < needle.length &&\n index.stream.charCodeAt(at + length) === needle.charCodeAt(length)\n ) {\n length++;\n }\n if (!best || length > best.length) best = { length, at };\n }\n return best;\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\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 */\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 // A row of digits alone in a band is a page number, whatever its footer\n // said around it — including a footer that was nothing but the field.\n pages.forEach((page, pageIndex) => {\n page.words.forEach((word, i) => {\n if (chromeWords.has(`${pageIndex}:${i}`)) return;\n if (!inChromeBand(page, word)) return;\n const row = page.words.filter((w) => sameRow(w, word));\n if (row.every((w) => PAGE_NUMBER_ROW.test(w.text))) {\n claimRow(pageIndex, word);\n }\n });\n });\n\n const body = indexDocument(pages, chromeWords);\n /** Stream offset → the entry that claimed the occurrence there. */\n const claimed = new Map<number, T>();\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);\n let partial: InventoryMatch<T>['partial'];\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(body, needle);\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) => !claimed.has(o.at));\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) => claimed.get(o.at)?.optional);\n if (held) {\n const holder = claimed.get(held.at) as T;\n claimed.delete(held.at);\n results.set(holder, {\n entry: holder,\n needle: results.get(holder)?.needle ?? '',\n status: 'skipped',\n occurrences: [],\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 = free.find((o) => o.at >= cursor) ?? free[0];\n claimed.set(chosen.at, entry);\n 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 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":";;;;;;;;AAAA,YAAYA,WAAU;AACtB,YAAYC,SAAQ;AAgBpB,SAAS,oCAAoC;AAC7C,SAAS,YAAY,oBAAoB;;;ACazC,SAAS,gBAAgB;AACzB,SAAS,YAAYC,WAAU;AAC/B,OAAO,QAAQ;AACf,OAAOC,WAAU;AACjB,OAAO,YAAY;AACnB;AAAA,EACE;AAAA,OAQK;AACP,SAAS,8BAA8B;;;AC5ChC,IAAM,iBAAN,MAA2C;AAAA;AAAA;AAAA,EAGhD,MAAM,MACJ,QACA,UACA,UAC0B;AAC1B,WAAO;AAAA,MACL,cAAc,CAAC;AAAA,MACf,SAAS,YAAY;AAAA,MAAC;AAAA,IACxB;AAAA,EACF;AACF;;;ACHA,SAAS,YAAY,UAAU;AAC/B,OAAO,UAAU;AAEjB;AAAA,EACE;AAAA,EACA;AAAA,OACK;;;AC2BP,IAAI,UAAU;AACP,SAAS,gBAAwB;AACtC,aAAW;AACX,SAAO,GAAG,QAAQ,GAAG,IAAI,OAAO;AAClC;AAGO,SAAS,iBAAiB,GAAmB;AAClD,SAAO,EAAE,QAAQ,oBAAoB,GAAG,EAAE,MAAM,GAAG,EAAE;AACvD;;;ADhCA,IAAM,+BAA+B;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,mBAAN,MAA6C;AAAA,EAClD,MAAM,MACJ,OACA,SAGA,UAC0B;AAC1B,UAAM,KAAK,cAAc;AACzB,UAAM,WAAW,KAAK,KAAK,SAAS,OAAO;AAC3C,UAAM,GAAG,MAAM,UAAU,EAAE,WAAW,KAAK,CAAC;AAE5C,QAAI,SAAS;AACb,eAAW,KAAK,OAAO;AACrB,UAAI,EAAE,QAAQ,WAAW,EAAG;AAC5B,iBAAW,KAAK,EAAE,SAAS;AACzB,kBAAU;AACV,cAAM,SAAS,EAAE,SAAS,MAAM;AAShC,cAAM,QAAQ,qBAAqB,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM;AAC/D,cAAM,OACJ,MAAM,WAAW,EAAE,SACf,EAAE,OACF,sBAAsB,EAAE,MAAM,MAAM,MAAM;AAChD,cAAM,OAAO,GAAG,iBAAiB,MAAM,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,IAAI,EAAE,IAAI,MAAM;AACnF,cAAM,GAAG,UAAU,KAAK,KAAK,UAAU,IAAI,GAAG,IAAI;AAAA,MACpD;AAAA,IACF;AAQA,UAAM,GAAG,MAAM,UAAU,GAAK,EAAE,MAAM,MAAM;AAAA,IAI5C,CAAC;AACD,UAAM,eAAe,MAAM,KAAK,mBAAmB;AAMnD,UAAM,WAAW,KAAK,KAAK,SAAS,UAAU;AAC9C,UAAM,GAAG,MAAM,UAAU,EAAE,WAAW,KAAK,CAAC;AAC5C,UAAM,aAAa,KAAK,KAAK,SAAS,gBAAgB;AACtD,UAAM,YAAY;AAAA,MAChB;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU,UAAU,QAAQ,CAAC;AAAA,MAC7B,eAAe,UAAU,QAAQ,CAAC;AAAA,MAClC,GAAG;AAAA,MACH;AAAA,MACA;AAAA,IACF,EAAE,KAAK,IAAI;AACX,UAAM,GAAG,UAAU,YAAY,WAAW,MAAM;AAEhD,WAAO;AAAA,MACL,cAAc;AAAA,QACZ,iBAAiB;AAAA,QACjB,gBAAgB;AAAA,MAClB;AAAA,MACA,SAAS,YAAY;AAOnB,cAAM,GAAG,MAAM,UAAU,GAAK,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,MAChD;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,qBAAwC;AACpD,eAAW,aAAa,8BAA8B;AACpD,UAAI;AACF,cAAM,GAAG,OAAO,SAAS;AACzB,eAAO;AAAA,UACL,mCAAmC,UAAU,SAAS,CAAC;AAAA,QACzD;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AAEA,WAAO,CAAC,iEAAiE;AAAA,EAC3E;AACF;AAEA,SAAS,UAAU,GAAmB;AACpC,SAAO,EACJ,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,QAAQ;AAC3B;;;AE1GA,SAAS,YAAYC,WAAU;AAC/B,OAAOC,WAAU;AAEjB;AAAA,EACE,wBAAAC;AAAA,EACA,yBAAAC;AAAA,OACK;AAYP,IAAI,iBAGO;AAEX,eAAe,iBAAiB;AAC9B,MAAI,eAAgB,QAAO;AAC3B,QAAM,QAAS,MAAM,OAAO,OAAO;AAGnC,QAAM,MAAM,MAAM,WAAW;AAC7B,QAAM,QAAQ,IAAI,KAAK,WAAW;AAClC,mBAAiB;AAAA,IACf,SAAS,MAAM,KAAK,uCAAuC;AAAA,IAG3D,YAAY,MAAM,KAAK,2CAA2C;AAAA,EAGpE;AACA,SAAO;AACT;AAEO,IAAM,oBAAN,MAA8C;AAAA,EACnD,MAAM,MACJ,OACA,SAIA,UAC0B;AAC1B,UAAM,KAAK,cAAc;AACzB,UAAM,WAAWC,MAAK,KAAK,SAAS,OAAO;AAC3C,UAAMC,IAAG,MAAM,UAAU,EAAE,WAAW,KAAK,CAAC;AAE5C,UAAM,cAAwB,CAAC;AAC/B,QAAI,SAAS;AACb,eAAW,KAAK,OAAO;AACrB,UAAI,EAAE,QAAQ,WAAW,EAAG;AAC5B,iBAAW,KAAK,EAAE,SAAS;AACzB,kBAAU;AACV,cAAM,SAAS,EAAE,SAAS,MAAM;AAQhC,cAAM,QAAQC,sBAAqB,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM;AAC/D,cAAM,OACJ,MAAM,WAAW,EAAE,SACf,EAAE,OACFC,uBAAsB,EAAE,MAAM,MAAM,MAAM;AAChD,cAAM,OAAO,GAAG,iBAAiB,MAAM,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,IAAI,EAAE,IAAI,MAAM;AACnF,cAAM,WAAWH,MAAK,KAAK,UAAU,IAAI;AACzC,cAAMC,IAAG,UAAU,UAAU,IAAI;AACjC,oBAAY,KAAK,QAAQ;AAAA,MAC3B;AAAA,IACF;AAEA,QAAI,YAAY,WAAW,GAAG;AAC5B,aAAO,EAAE,cAAc,CAAC,GAAG,SAAS,YAAY;AAAA,MAAC,EAAE;AAAA,IACrD;AAEA,UAAM,EAAE,SAAS,WAAW,IAAI,MAAM,eAAe;AACrD,UAAM,aAAuB,CAAC;AAC9B,eAAW,KAAK,aAAa;AAC3B,YAAM,QAAQ,QAAQ,CAAC;AACvB,UAAI,QAAQ,EAAG,YAAW,KAAK,CAAC;AAAA,IAClC;AAEA,QAAI,UAAU;AACd,WAAO;AAAA,MACL,cAAc;AAAA;AAAA;AAAA;AAAA,QAIZ,kBAAkB;AAAA,MACpB;AAAA,MACA,SAAS,YAAY;AACnB,YAAI,QAAS;AACb,kBAAU;AACV,mBAAW,KAAK,YAAY;AAC1B,cAAI;AACF,uBAAW,CAAC;AAAA,UACd,QAAQ;AAAA,UAER;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;ACvEA,SAAS,YAAYG,WAAU;AAC/B,OAAOC,WAAU;AAEjB;AAAA,EACE,wBAAAC;AAAA,EACA,yBAAAC;AAAA,OACK;;;AC5EP,SAAS,yBAAyB;AAYlC,IAAM,QAAQ,IAAI,kBAAkC;AAM7C,SAAS,sBACd,MACA,UACG;AACH,SAAO,MAAM,IAAI,MAAM,QAAQ;AACjC;AAMO,SAAS,eACd,MACA,OAAuB,SACjB;AACN,QAAM,SAAS,IAAI,MAAM,IAAI;AAC/B;AAMO,IAAM,uBAAuC,CAAC,SAAS;AAC5D,UAAQ,OAAO,MAAM,GAAG,IAAI;AAAA,CAAI;AAClC;;;AD8CA,IAAM,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkFrB,IAAM,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiBlB,IAAM,sBAAN,MAAgD;AAAA,EACrD,MAAM,MACJ,OACA,SACA,SAC0B;AAC1B,UAAM,aAAa,MAAM,OAAO,CAAC,MAAM,EAAE,QAAQ,SAAS,CAAC;AAC3D,QAAI,WAAW,WAAW,GAAG;AAC3B,aAAO,EAAE,cAAc,CAAC,GAAG,SAAS,YAAY;AAAA,MAAC,EAAE;AAAA,IACrD;AAGA,UAAM,WAAWC,MAAK,KAAK,SAAS,OAAO;AAC3C,UAAMC,IAAG,MAAM,UAAU,EAAE,WAAW,KAAK,CAAC;AAE5C,UAAM,KAAK,cAAc;AACzB,UAAM,YAAsB,CAAC;AAC7B,UAAM,SAKA,CAAC;AACP,QAAI,SAAS;AACb,eAAW,KAAK,YAAY;AAC1B,iBAAW,KAAK,EAAE,SAAS;AACzB,kBAAU;AACV,cAAM,SAAS,EAAE,SAAS,MAAM;AAUhC,cAAM,QAAQC,sBAAqB,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM;AAC/D,cAAM,OACJ,MAAM,WAAW,EAAE,SACf,EAAE,OACFC,uBAAsB,EAAE,MAAM,MAAM,MAAM;AAChD,cAAM,OAAO,GAAG,iBAAiB,MAAM,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,IAAI,EAAE,IAAI,MAAM;AACnF,cAAM,OAAOH,MAAK,KAAK,UAAU,IAAI;AACrC,cAAMC,IAAG,UAAU,MAAM,IAAI;AAC7B,kBAAU,KAAK,IAAI;AACnB,eAAO,KAAK;AAAA,UACV,QAAQ,MAAM;AAAA,UACd,QAAQ,EAAE;AAAA,UACV,QAAQ,EAAE;AAAA,UACV,MAAM;AAAA,QACR,CAAC;AAAA,MACH;AAAA,IACF;AAEA,QAAI,QAAQ,IAAI,oBAAoB,KAAK;AAIvC;AAAA,QACE,+BACE,OAAO,SACP,oEACA,UAAU,SACV,eACA,OACG;AAAA,UACC,CAAC,MACC,KAAK,EAAE,MAAM,OAAO,EAAE,MAAM,GAAG,EAAE,SAAS,YAAY,EAAE,YAAOD,MAAK,SAAS,EAAE,IAAI,CAAC;AAAA,QACxF,EACC,KAAK,IAAI;AAAA,MAChB;AAAA,IACF;AAOA,UAAM,cAAc,SAAS,aAAa,SACtC,QAAQ,cACR,CAACA,MAAK,KAAK,SAAS,cAAc,CAAC;AACvC,eAAW,cAAc,aAAa;AACpC,YAAM,cAAcA,MAAK,KAAK,YAAY,MAAM;AAChD,YAAM,aAAaA,MAAK,KAAK,aAAa,WAAW,QAAQ;AAC7D,YAAMC,IAAG,MAAM,YAAY,EAAE,WAAW,KAAK,CAAC;AAC9C,YAAMA,IAAG;AAAA,QACPD,MAAK,KAAK,YAAY,oBAAoB;AAAA,QAC1C;AAAA,MACF;AACA,YAAMC,IAAG;AAAA,QACPD,MAAK,KAAK,aAAa,2BAA2B;AAAA,QAClD;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,MACL,cAAc;AAAA;AAAA;AAAA,QAGZ,gBAAgB,UAAU,KAAK,GAAG;AAAA;AAAA;AAAA,QAGlC,kBAAkB;AAAA,MACpB;AAAA,MACA,SAAS,YAAY;AAAA,MAGrB;AAAA,IACF;AAAA,EACF;AACF;;;AE3RA,IAAM,SAAS,oBAAI,IAAiC;AAE7C,SAAS,cACd,WAA4B,QAAQ,UACxB;AACZ,QAAM,MAAM,OAAO,IAAI,QAAQ;AAC/B,MAAI,IAAK,QAAO;AAChB,MAAI;AACJ,UAAQ,UAAU;AAAA,IAChB,KAAK;AACH,eAAS,IAAI,kBAAkB;AAC/B;AAAA,IACF,KAAK;AAQH,eAAS,IAAI,oBAAoB;AACjC;AAAA,IACF,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,eAAS,IAAI,iBAAiB;AAC9B;AAAA,IACF;AACE,eAAS,IAAI,eAAe;AAAA,EAChC;AACA,SAAO,IAAI,UAAU,MAAM;AAC3B,SAAO;AACT;;;APGA,IAAM,qBAAqB;AAE3B,IAAM,mCAAmC;AAEzC,IAAM,+BAA+B;AAOrC,IAAM,uBAAuB;AAC7B,IAAM,sBAAsB;AAC5B,IAAM,mBAAmB;AACzB,IAAM,aAAa,KAAK,OAAO;AAE/B,SAAS,KACP,QACA,MACA,WAQA,KACe;AACf,SAAO,IAAI,QAAQ,CAACI,UAAS,WAAW;AACtC;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,QACE,SAAS;AAAA,QACT,WAAW;AAAA,QACX,aAAa;AAAA,QACb,KAAK,MAAM,EAAE,GAAG,QAAQ,KAAK,GAAG,IAAI,IAAI,QAAQ;AAAA,MAClD;AAAA,MACA,CAAC,UAAW,QAAQ,OAAO,KAAK,IAAIA,SAAQ;AAAA,IAC9C;AAAA,EACF,CAAC;AACH;AAEA,eAAe,YAAY,QAAkC;AAC3D,MAAI,OAAO,SAASC,MAAK,GAAG,GAAG;AAC7B,QAAI;AACF,YAAMC,IAAG,OAAO,MAAM;AAAA,IACxB,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACA,MAAI;AACF,UAAM,KAAK,QAAQ,CAAC,WAAW,GAAG,gBAAgB;AAClD,WAAO;AAAA,EACT,SAAS,OAAO;AACd,UAAM,OAAQ,MAAgC;AAE9C,WAAO,SAAS,YAAY,SAAS;AAAA,EACvC;AACF;AAEA,SAAS,oBAA8B;AACrC,QAAM,aAAuB,CAAC;AAC9B,QAAM,aAAa,QAAQ,IAAI,kBAAkB,KAAK;AACtD,MAAI,WAAY,YAAW,KAAK,UAAU;AAC1C,MAAI,QAAQ,aAAa,UAAU;AACjC,eAAW,KAAK,sDAAsD;AAAA,EACxE,WAAW,QAAQ,aAAa,SAAS;AACvC,eAAW,KAAK,sDAAsD;AACtE,eAAW;AAAA,MACT;AAAA,IACF;AAAA,EACF;AACA,aAAW,KAAK,WAAW,aAAa;AACxC,SAAO,CAAC,GAAG,IAAI,IAAI,UAAU,CAAC;AAChC;AAEA,SAAS,qBAA+B;AACtC,QAAM,aAAuB,CAAC;AAC9B,QAAM,aAAa,QAAQ,IAAI,eAAe,KAAK;AACnD,MAAI,WAAY,YAAW,KAAK,UAAU;AAC1C,aAAW,KAAK,UAAU;AAC1B,SAAO,CAAC,GAAG,IAAI,IAAI,UAAU,CAAC;AAChC;AAEA,eAAe,cACb,YACA,OACA,SACiB;AACjB,aAAW,aAAa,YAAY;AAClC,QAAI,MAAM,YAAY,SAAS,EAAG,QAAO;AAAA,EAC3C;AACA,QAAM,IAAI;AAAA,IACR,8BAA8B,KAAK,0BAA0B,OAAO,eACpD,WAAW,KAAK,IAAI,CAAC;AAAA,EACvC;AACF;AAKA,IAAI;AACJ,IAAI;AACJ,SAAS,iBAAkC;AACzC,MAAI,CAAC,gBAAgB;AACnB,qBAAiB;AAAA,MACf,kBAAkB;AAAA,MAClB;AAAA,MACA;AAAA,IACF,EAAE,MAAM,CAAC,UAAU;AACjB,uBAAiB;AACjB,YAAM;AAAA,IACR,CAAC;AAAA,EACH;AACA,SAAO;AACT;AACA,SAAS,kBAAmC;AAC1C,MAAI,CAAC,iBAAiB;AACpB,sBAAkB;AAAA,MAChB,mBAAmB;AAAA,MACnB;AAAA,MACA;AAAA,IACF,EAAE,MAAM,CAAC,UAAU;AACjB,wBAAkB;AAClB,YAAM;AAAA,IACR,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAwBA,IAAM,qBAAqB;AAAA,EACzB,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,iBAAiB;AAAA,EACjB,UAAU;AAAA,EACV,QAAQ;AACV;AAGA,IAAM,iBAAiB,oBAAI,IAAY;AAOvC,eAAsB,0BAAyD;AAC7E,QAAM,OAAO,IAAI,IAAI,cAAc;AACnC,QAAM,aAAa,gBAAgB;AACnC,MAAI,WAAY,MAAK,IAAI,UAAU;AAEnC,MAAI,UAAU;AACd,MAAI,QAAQ;AACZ,aAAW,OAAO,MAAM;AACtB,QAAI;AACF,YAAM,QAAQ,MAAMA,IAAG,QAAQ,GAAG;AAClC,iBAAW,QAAQ,OAAO;AACxB,YAAI,CAAC,KAAK,SAAS,MAAM,EAAG;AAC5B,YAAI;AACF,gBAAM,OAAO,MAAMA,IAAG,KAAKD,MAAK,KAAK,KAAK,IAAI,CAAC;AAC/C;AACA,mBAAS,KAAK;AAAA,QAChB,QAAQ;AAAA,QAAC;AAAA,MACX;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,QAAM,UAAU,mBAAmB,WAAW,mBAAmB;AACjE,SAAO;AAAA,IACL,GAAG;AAAA,IACH,SAAS,UAAU,IAAI,mBAAmB,WAAW,UAAU;AAAA,IAC/D;AAAA,IACA;AAAA,EACF;AACF;AAOA,eAAsB,uBAAsC;AAC1D,QAAM,OAAO,IAAI,IAAI,cAAc;AACnC,QAAM,aAAa,gBAAgB;AACnC,MAAI,WAAY,MAAK,IAAI,UAAU;AAEnC,aAAW,OAAO,MAAM;AACtB,QAAI;AACF,YAAM,QAAQ,MAAMC,IAAG,QAAQ,GAAG;AAClC,YAAM,QAAQ;AAAA,QACZ,MACG,OAAO,CAAC,SAAS,KAAK,SAAS,MAAM,CAAC,EACtC,IAAI,CAAC,SAASA,IAAG,GAAGD,MAAK,KAAK,KAAK,IAAI,GAAG,EAAE,OAAO,KAAK,CAAC,CAAC;AAAA,MAC/D;AAAA,IACF,QAAQ;AAAA,IAAC;AAAA,EACX;AAEA,qBAAmB,WAAW;AAC9B,qBAAmB,aAAa;AAChC,qBAAmB,kBAAkB;AACrC,qBAAmB,WAAW;AAC9B,qBAAmB,SAAS;AAC9B;AAEA,IAAM,gBAAgB,OAAO,KAAK;AAAA,EAChC;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAC5C,CAAC;AAQD,SAAS,aAAa,KAAuD;AAE3E,MAAI,IAAI,SAAS,GAAI,QAAO;AAC5B,MAAI,CAAC,IAAI,SAAS,GAAG,CAAC,EAAE,OAAO,aAAa,EAAG,QAAO;AACtD,MAAI,IAAI,SAAS,SAAS,IAAI,EAAE,MAAM,OAAQ,QAAO;AACrD,QAAM,QAAQ,IAAI,aAAa,EAAE;AACjC,QAAM,SAAS,IAAI,aAAa,EAAE;AAClC,MAAI,SAAS,KAAK,UAAU,EAAG,QAAO;AACtC,SAAO,EAAE,OAAO,OAAO;AACzB;AAEA,IAAI,aAAa;AAMjB,eAAe,iBACb,UACA,WACA,KACe;AACf,QAAM,MAAM,GAAG,SAAS,QAAQ,QAAQ,GAAG,IAAI,YAAY;AAC3D,MAAI;AACF,UAAMC,IAAG,MAAM,UAAU,EAAE,WAAW,KAAK,CAAC;AAC5C,UAAMA,IAAG,UAAU,KAAK,GAAG;AAC3B,UAAMA,IAAG,OAAO,KAAK,SAAS;AAAA,EAChC,QAAQ;AACN,UAAMA,IAAG,GAAG,KAAK,EAAE,OAAO,KAAK,CAAC,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EAClD;AACF;AAQO,SAAS,YACd,OACoB;AACpB,MAAI,CAAC,SAAS,MAAM,WAAW,EAAG,QAAO;AACzC,QAAM,QAAQ,MACX;AAAA,IACC,CAAC,MACC,GAAG,EAAE,MAAM,IAAI,EAAE,MAAM,IAAI,EAAE,SAAS,MAAM,GAAG,MAC/C,OACG,WAAW,QAAQ,EAQnB,OAAO,OAAO,KAAK,EAAE,MAAM,QAAQ,CAAC,EACpC,OAAO,KAAK;AAAA,EACnB,EACC,KAAK;AACR,SAAO,OAAO,WAAW,QAAQ,EAAE,OAAO,MAAM,KAAK,IAAI,CAAC,EAAE,OAAO,KAAK;AAC1E;AAEO,SAAS,SAAS,SAKd;AACT,SACE,OACG,WAAW,QAAQ,EAUnB;AAAA,IACC,KAAK,UAAU;AAAA,MACb,GAAG,QAAQ;AAAA,MACX,KAAK,QAAQ;AAAA,MACb,MAAM,QAAQ;AAAA,MACd,GAAG,QAAQ,YAAY;AAAA,IACzB,CAAC;AAAA,EACH,EACC,OAAO,KAAK;AAEnB;AAEA,SAAS,UAAU,KAAqB;AACtC,SAAO,yBAAyB,IAAI,SAAS,QAAQ,CAAC;AACxD;AAEA,SAAS,aAAa,OAAwB;AAC5C,SAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC9D;AAEA,eAAe,WAAW,UAAoC;AAC5D,MAAI;AACF,UAAMA,IAAG,OAAO,QAAQ;AACxB,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAiBA,IAAM,cAAc,CAClB,YACA,QACA,UACa;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,gCAAgC,WAAW,QAAQ,OAAO,GAAG,CAAC;AAAA,EAC9D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL;AAOA,eAAe,0BACb,QACA,SACA,UACA,OAC8B;AAC9B,QAAM,UAA+B,IAAI,MAAM,OAAO,MAAM;AAC5D,MAAI,SAAU,gBAAe,IAAI,QAAQ;AAIzC,QAAM,WAAW,YAAY,KAAK;AAIlC,QAAM,YAAY,oBAAI,IAAsB;AAC5C,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,QAAQ,OAAO,CAAC;AACtB,UAAM,MAAM,SAAS;AAAA,MACnB,cAAc,MAAM;AAAA,MACpB,KAAK,MAAM;AAAA,MACX;AAAA,MACA;AAAA,IACF,CAAC;AACD,UAAM,WAAW,UAAU,IAAI,GAAG;AAClC,QAAI,UAAU;AACZ,eAAS,QAAQ,KAAK,CAAC;AAAA,IACzB,OAAO;AACL,gBAAU,IAAI,KAAK;AAAA,QACjB,cAAc,MAAM;AAAA,QACpB,KAAK,MAAM;AAAA,QACX,SAAS,CAAC,CAAC;AAAA,QACX,WAAW,WAAWD,MAAK,KAAK,UAAU,GAAG,GAAG,MAAM,IAAI;AAAA,QAC1D,UAAU;AAAA,QACV,SAAS;AAAA,QACT,WAAW;AAAA,MACb,CAAC;AAAA,IACH;AAAA,EACF;AAEA,qBAAmB,mBAAmB,OAAO,SAAS,UAAU;AAEhE,QAAM,OAAO,CACX,KACA,OACA,OACA,UACG;AACH,uBAAmB;AACnB,eAAW,KAAK,IAAI;AAClB,cAAQ,CAAC,IAAI,EAAE,IAAI,OAAO,OAAO,OAAO,MAAM;AAAA,EAClD;AACA,QAAM,UAAU,CAAC,KAAe,WAAgC;AAC9D,eAAW,KAAK,IAAI,QAAS,SAAQ,CAAC,IAAI,EAAE,IAAI,MAAM,GAAG,OAAO;AAAA,EAClE;AAGA,QAAM,WAAuB,CAAC;AAC9B,QAAM,QAAQ;AAAA,IACZ,CAAC,GAAG,UAAU,OAAO,CAAC,EAAE,IAAI,OAAO,QAAQ;AACzC,UAAI,IAAI,WAAW;AACjB,cAAME,UAAS,MAAMD,IAAG,SAAS,IAAI,SAAS,EAAE,MAAM,MAAM,IAAI;AAChE,YAAIC,SAAQ;AACV,gBAAM,OAAO,aAAaA,OAAM;AAChC,cAAI,MAAM;AACR,+BAAmB;AACnB,oBAAQ,KAAK,EAAE,eAAe,UAAUA,OAAM,GAAG,GAAG,KAAK,CAAC;AAC1D;AAAA,UACF;AAGA,gBAAMD,IAAG,GAAG,IAAI,WAAW,EAAE,OAAO,KAAK,CAAC,EAAE,MAAM,MAAM;AAAA,UAAC,CAAC;AAAA,QAC5D;AACA,2BAAmB;AAAA,MACrB;AACA,eAAS,KAAK,GAAG;AAAA,IACnB,CAAC;AAAA,EACH;AACA,MAAI,SAAS,WAAW,EAAG,QAAO;AAIlC,QAAM,WAAW,MAAM,OAAO,2BAA2B;AACzD,QAAM,UAAU,MAAMA,IAAG,QAAQD,MAAK,KAAK,GAAG,OAAO,GAAG,aAAa,CAAC;AAGtE,MAAI,cAAsC;AAC1C,MAAI;AACF,UAAM,QAAoB,CAAC;AAC3B,aAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,YAAM,MAAM,SAAS,CAAC;AACtB,UAAI,WAAWA,MAAK,KAAK,SAAS,SAAS,CAAC,OAAO;AACnD,UAAI,UAAUA,MAAK,KAAK,SAAS,SAAS,CAAC,MAAM;AACjD,UAAI,YAAYA,MAAK,KAAK,SAAS,SAAS,CAAC,EAAE;AAC/C,UAAI;AACF,cAAM,aAAa,MAAM,SAAS;AAAA,UAChC,IAAI;AAAA,UACJ,EAAE,QAAQ;AAAA,QACZ;AACA,cAAMC,IAAG,UAAU,IAAI,UAAU,UAAU;AAC3C,cAAM,KAAK,GAAG;AAAA,MAChB,SAAS,OAAO;AACd,aAAK,KAAK,SAAS,aAAa,KAAK,GAAG,KAAK;AAAA,MAC/C;AAAA,IACF;AACA,QAAI,MAAM,WAAW,EAAG,QAAO;AAY/B,UAAM,cAAc;AAAA,MAClBD,MAAK,KAAK,SAAS,SAAS;AAAA,MAC5B,GAAG,MAAM;AAAA,QAAK,EAAE,QAAQ,qBAAqB;AAAA,QAAG,CAAC,GAAG,MAClDA,MAAK,KAAK,SAAS,iBAAiB,CAAC,EAAE;AAAA,MACzC;AAAA,IACF;AACA,UAAM,aAAa,OAAO,SAAS,uBAAuB,KAAK,IAAI,CAAC;AACpE,QAAI,WAAW,SAAS,GAAG;AACzB,oBAAc,MAAM,cAAc,EAAE,MAAM,YAAY,SAAS;AAAA,QAC7D;AAAA,MACF,CAAC;AAAA,IACH;AACA,UAAM,aAAa,aAAa;AAEhC,UAAM,CAAC,SAAS,QAAQ,IAAI,MAAM,QAAQ,IAAI;AAAA,MAC5C,eAAe;AAAA,MACf,gBAAgB;AAAA,IAClB,CAAC;AAYD,UAAM,iBAAiB,KAAK;AAAA,MAC1B,qBACE,oCAAoC,MAAM,SAAS;AAAA,MACrD;AAAA,IACF;AACA,UAAM,aACJ,KAAK,IAAI,IAAI,iBAAiB,sBAAsB,MAAM;AAC5D,UAAM,cAAc,MAAM,aAAa,KAAK,IAAI;AAEhD,QAAI;AACJ,QAAI;AACF,YAAM;AAAA,QACJ;AAAA,QACA;AAAA,UACE,YAAY,CAAC;AAAA,UACb;AAAA,UACA,MAAM,IAAI,CAAC,QAAQ,IAAI,QAAQ;AAAA,QACjC;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AACd,mBAAa;AAAA,IACf;AASA,UAAM,YAAwB,CAAC;AAC/B,UAAM,UAAsB,CAAC;AAC7B,eAAW,OAAO,OAAO;AACvB,OAAE,MAAM,WAAW,IAAI,OAAO,IAAK,YAAY,SAAS,KAAK,GAAG;AAAA,IAClE;AAEA,QAAI,cACF,QAAQ,SAAS,KACjB,MAAM,SAAS,MACd,eAAe,UAAa,UAAU,SAAS,KAC5C,uBACA;AACN,eAAW,CAAC,GAAG,GAAG,KAAK,QAAQ,QAAQ,GAAG;AACxC,YAAM,cAAc,KAAK,IAAI,oBAAoB,YAAY,CAAC;AAC9D,UAAI,cAAc,KAAK,cAAc,KAAM;AACzC;AACA,YAAI;AACJ,YAAI;AACF,gBAAM;AAAA,YACJ;AAAA;AAAA;AAAA;AAAA,YAIA;AAAA,cACE,YAAY,IAAI,CAAC,KAAKA,MAAK,KAAK,SAAS,iBAAiB,CAAC,EAAE;AAAA,cAC7D;AAAA,cACA,CAAC,IAAI,QAAQ;AAAA,YACf;AAAA,YACA;AAAA,YACA;AAAA,UACF;AAAA,QACF,SAAS,OAAO;AACd,uBAAa;AAAA,QACf;AACA,YAAI,MAAM,WAAW,IAAI,OAAO,GAAG;AACjC,oBAAU,KAAK,GAAG;AAClB;AAAA,QACF;AACA,uBAAe;AAAA,MACjB;AACA,YAAM,QAAQ;AACd;AAAA,QACE;AAAA,QACA;AAAA,QACA,iDACE,QAAQ,KAAK,aAAa,KAAK,CAAC,KAAK,GACvC;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAKA,eAAW,OAAO,WAAW;AAC3B,YAAM,SAAS,KAAK,IAAI,qBAAqB,YAAY,CAAC;AAC1D,UAAI,UAAU,KAAM;AAClB;AAAA,UACE;AAAA,UACA;AAAA,UACA;AAAA,QACF;AACA;AAAA,MACF;AACA,UAAI;AACF,cAAM;AAAA,UACJ;AAAA,UACA;AAAA,YACE;AAAA,YACA,OAAO,IAAI,GAAG;AAAA,YACd;AAAA,YACA;AAAA,YACA,IAAI;AAAA,YACJ,IAAI;AAAA,UACN;AAAA,UACA;AAAA,QACF;AACA,cAAM,MAAM,MAAMC,IAAG,SAAS,GAAG,IAAI,SAAS,MAAM;AACpD,cAAM,OAAO,aAAa,GAAG;AAC7B,YAAI,CAAC,MAAM;AACT,gBAAM,IAAI;AAAA,YACR;AAAA,UACF;AAAA,QACF;AACA,YAAI,IAAI,WAAW;AACjB,gBAAM,iBAAiB,UAAW,IAAI,WAAW,GAAG;AAAA,QACtD;AACA,2BAAmB;AACnB,gBAAQ,KAAK,EAAE,eAAe,UAAU,GAAG,GAAG,GAAG,KAAK,CAAC;AAAA,MACzD,SAAS,OAAO;AACd,aAAK,KAAK,aAAa,aAAa,KAAK,GAAG,KAAK;AAAA,MACnD;AAAA,IACF;AAEA,WAAO;AAAA,EACT,UAAE;AAKA,QAAI,YAAa,OAAM,YAAY,QAAQ,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAC3D,UAAMA,IAAG,GAAG,SAAS,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACvE;AACF;AAEA,SAAS,gBAAgB,SAEP;AAChB,SAAO,SAAS,aAAa,SACzBD,MAAK,KAAK,GAAG,OAAO,GAAG,kBAAkB,IACzC,QAAQ;AACd;AAQO,SAAS,gCAAgC,SAE7B;AACjB,QAAM,WAAW,gBAAgB,OAAO;AAExC,SAAO,eAAe,UACpB,SAC8B;AAC9B,UAAM,CAAC,MAAM,IAAI,MAAM;AAAA,MACrB,CAAC,EAAE,cAAc,QAAQ,cAAc,KAAK,QAAQ,IAAI,CAAC;AAAA,MACzD,QAAQ;AAAA,MACR;AAAA,MACA,QAAQ;AAAA,IACV;AACA,QAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,mCAAmC;AAChE,QAAI,CAAC,OAAO,IAAI;AACd,YAAM,QAAQ,IAAI,MAAM,OAAO,KAAK;AAIpC,UAAI,OAAO,UAAU,OAAW,OAAM,QAAQ,OAAO;AACrD,YAAM;AAAA,IACR;AACA,WAAO;AAAA,MACL,eAAe,OAAO;AAAA,MACtB,OAAO,OAAO;AAAA,MACd,QAAQ,OAAO;AAAA,IACjB;AAAA,EACF;AACF;AAQO,SAAS,qCAAqC,SAE7B;AACtB,QAAM,WAAW,gBAAgB,OAAO;AAExC,SAAO,eAAe,eAAe,SAAS;AAC5C,UAAM,gBAAgB,MAAM;AAAA,MAC1B,QAAQ,OAAO,IAAI,CAAC,WAAW;AAAA,QAC7B,cAAc,MAAM;AAAA,QACpB,KAAK,MAAM,OAAO;AAAA,MACpB,EAAE;AAAA,MACF,QAAQ;AAAA,MACR;AAAA,MACA,QAAQ;AAAA,IACV;AAEA,WAAO;AAAA,MACL,SAAS,cAAc;AAAA,QAAI,CAAC,WAC1B,OAAO,KACH,SACA,EAAE,IAAI,OAAO,OAAO,OAAO,OAAO,OAAO,OAAO,MAAM;AAAA,MAC5D;AAAA,IACF;AAAA,EACF;AACF;;;ADhwBA,SAAS,uBAAuB,UAAqC;AACnE,aAAW,WAAW,UAAU;AAC9B;AAAA,MACE,GAAG,QAAQ,SAAS,KAAK,QAAQ,OAAO;AAAA,MACxC,QAAQ,aAAa,SAAS,SAAS;AAAA,IACzC;AAAA,EACF;AACF;AAWA,SAAS,qBACP,KACqB;AACrB,UAAQ,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO;AAAA,IAC7B,WAAW,GAAG,aAAa;AAAA,IAC3B,SAAS,OAAO,GAAG,WAAW,EAAE;AAAA,IAChC,UAAW,GAAG,aAAa,SAAS,SAAS;AAAA,IAG7C,SAAS;AAAA,MACP,GAAI,GAAG,WAAW,OAAO,EAAE,YAAY,WAAW,EAAE,UAAU,CAAC;AAAA,MAC/D,GAAI,GAAG,SAAS,UAAa,EAAE,MAAM,EAAE,KAAK;AAAA,MAC5C,GAAI,GAAG,UAAU,UAAa,EAAE,OAAO,EAAE,MAAM;AAAA,IACjD;AAAA,EACF,EAAE;AACJ;AAEA,SAAS,mBAAmB,UAAgD;AAC1E,QAAM,QAAQ,SAAS,UAAU;AACjC,SAAO,OAAO,UAAU,WAAW,QAAQ;AAC7C;AAUA,IAAM,kBAAkB,OAAO,qBAAqB;AACpD,IAAM,oBAAoB,OAAO,uBAAuB;AAQxD,SAAS,cACP,UACA,QACA,UACG;AACH,SAAO,OAAO,OAAO,UAAU;AAAA,IAC7B,CAAC,eAAe,GAAG;AAAA,IACnB,CAAC,iBAAiB,GAAG;AAAA,EACvB,CAAC;AACH;AASA,SAAS,YACP,UACA,UACe;AACf,MAAI,CAAC,SAAU,QAAO;AACtB,QAAM,SAAU,SAA+B,eAAe;AAC9D,SAAO,WAAW,UAAa,WAAW,WAAW,WAAW;AAClE;AAGA,SAAS,WAAW,SAAoC;AACtD,SAAO,GAAG,QAAQ,SAAS,IAAI,QAAQ,SAAS,QAAQ,EAAE,IAAI,QAAQ,OAAO;AAC/E;AAUA,SAAS,wBACP,UACA,UACqB;AACrB,QAAM,UAAW,WACf,iBACF;AACA,MAAI,CAAC,WAAW,QAAQ,WAAW,EAAG,QAAO;AAC7C,QAAM,OAAO,IAAI,IAAI,QAAQ,IAAI,UAAU,CAAC;AAC5C,SAAO,SAAS,OAAO,CAAC,YAAY,CAAC,KAAK,IAAI,WAAW,OAAO,CAAC,CAAC;AACpE;AAQA,SAAS,iBAAiB,UAAuC;AAC/D,QAAM,WAAY,UACd;AACJ,SAAO,OAAO,aAAa,WAAW,WAAW;AACnD;AAEA,IAAM,cAAc,oBAAI,IAAI,CAAC,aAAa,eAAe,WAAW,CAAC;AACrE,SAAS,aAAa,MAAkC;AACtD,SAAO,QAAQ,CAAC,YAAY,IAAI,IAAI,IAAI,OAAO;AACjD;AAGA,IAAM,gBAAgB;AAStB,SAAS,mBACP,UACA,OACA,cACkE;AAClE,MAAI,CAAC,SAAS,OAAO,aAAa,YAAY,aAAa,MAAM;AAC/D,WAAO,EAAE,UAAU,aAAa;AAAA,EAClC;AACA,SAAO;AAAA,IACL,UAAU;AAAA,MACR,GAAG;AAAA,MACH,OAAO,EAAE,GAAG,SAAS,OAAO,OAAO,cAAc;AAAA,IACnD;AAAA,IACA,cAAc,EAAE,GAAG,cAAc,CAAC,aAAa,GAAG,MAAM;AAAA,EAC1D;AACF;AAIA,SAAS,uBAAmD;AAC1D,QAAM,YAAY,QAAQ,IAAI;AAC9B,QAAM,SAAS,QAAQ,IAAI;AAC3B,QAAM,eAAe,QAAQ,IAAI,6BAA6B;AAG9D,QAAM,cAAc,kBAAkB;AAAA,IACpC,QAAQ,IAAI,2BAA2B;AAAA,EACzC;AAEA,MAAI,CAAC,aAAa,CAAC,UAAU,CAAC,YAAa,QAAO;AAElD,SAAO;AAAA,IACL,YAAY;AAAA,MACV;AAAA,MACA,GAAI,UAAU,EAAE,SAAS,EAAE,CAAC,YAAY,GAAG,OAAO,EAAE;AAAA,MACpD,GAAI,eAAe,EAAE,YAAY;AAAA,IACnC;AAAA,EACF;AACF;AAMA,IAAI;AACJ,SAAS,oBAAoC;AAC3C,MAAI,CAAC,kBAAkB;AACrB,uBAAmB,gCAAgC;AAAA,EACrD;AACA,SAAO;AACT;AACA,IAAI;AACJ,SAAS,yBAA8C;AACrD,MAAI,CAAC,uBAAuB;AAC1B,4BAAwB,qCAAqC;AAAA,EAC/D;AACA,SAAO;AACT;AAcA,SAAS,sBACP,MACA,WAC4B;AAC5B,MAAI,CAAC,UAAW,QAAO;AACvB,SAAO;AAAA,IACL,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAI,MAAM,cAAc,UAAU,aAC9B,EAAE,YAAY,EAAE,GAAG,MAAM,YAAY,GAAG,UAAU,WAAW,EAAE,IAC/D,CAAC;AAAA,EACP;AACF;AAEA,SAAS,oBAAoC;AAC3C,QAAM,OAAO,qBAAqB,KAAK,CAAC;AACxC,QAAM,YAAY,QAAQ,IAAI,yBAAyB,KAAK;AAC5D,QAAM,SACJ,QAAQ,IAAI,+BAA+B,QAAQ,IAAI;AACzD,QAAM,eACJ,QAAQ,IAAI,sCACZ,QAAQ,IAAI,6BACZ;AACF,SAAO;AAAA,IACL,GAAG;AAAA,IACH,MAAM,YACF;AAAA,MACE;AAAA,MACA,GAAI,UAAU,EAAE,SAAS,EAAE,CAAC,YAAY,GAAG,OAAO,EAAE;AAAA,IACtD,IACA;AAAA,MACE,QAAQ,kBAAkB;AAAA,MAC1B,aAAa,uBAAuB;AAAA,IACtC;AAAA,EACN;AACF;AA0NO,IAAM,oBAAN,MAAiD;AAAA,EACtD,OAAmB;AAAA,EACnB,YAAY;AAAA,EACZ,QAAQ;AAAA,EACR,cAAc;AAAA,EAEd,MAAM,cAA0C;AAC9C,UAAM,OAAO,MAAM,OAAO,2BAA2B;AACrD,WAAO,KAAK,gBAAgB;AAAA,EAC9B;AAAA,EAEA,MAAM,mBAAuD;AAC3D,UAAM,OAAO,MAAM,OAAO,2BAA2B;AACrD,WAAO,KAAK,qBAAqB;AAAA,EACnC;AAAA,EAEA,MAAM,eACJ,MACA,SACiB;AACjB,UAAM,OAAO,MAAM,OAAO,2BAA2B;AACrD,UAAM,SAAS,OAAO,SAAS,WAAW,KAAK,MAAM,IAAc,IAAI;AACvE,UAAM,WAAW;AAAA,MACf,QAAQ,UAAU,WAAW,SACxB,QAAQ,WAGT;AAAA,MACJ;AAAA,IACF;AACA,QAAI;AACJ,QAAI;AACJ,QAAI,UAAU;AACZ,sBAAgB,SAAS,MAAM;AAAA,IACjC,OAAO;AACL,YAAM,WAAW,MAAM,KAAK,cAAc,OAAO;AACjD,YAAM,aAAa;AAAA,QACjB;AAAA,QACA,SAAS;AAAA,QACT,SAAS;AAAA,MACX;AACA,sBAAgB,WAAW;AAC3B,qBAAe,WAAW;AAAA,IAC5B;AACA,UAAM,WAAW;AAAA,MACf,kBAAkB;AAAA,MAClB,QAAQ;AAAA,IACV;AAIA,UAAM,WAAgC,CAAC;AACvC,UAAM,SAAS,MAAM,KAAK,uBAAuB,eAAsB;AAAA,MACrE;AAAA,MACA;AAAA,MACA,OAAO,QAAQ;AAAA,MACf,YAAY;AAAA,QACV,oBAAoB,QAAQ,YAAY;AAAA,MAC1C;AAAA,MACA,eAAe,QAAQ;AAAA,MACvB,aAAa,QAAQ;AAAA,MACrB,SAAS,QAAQ;AAAA,MACjB,UAAU,QAAQ;AAAA,MAClB,mBAAmB,QAAQ;AAAA,MAC3B;AAAA,MACA;AAAA,IACF,CAAC;AACD,UAAM,UAAU,wBAAwB,UAAU,QAAQ;AAC1D,2BAAuB,OAAO;AAC9B,YAAQ,UAAU,KAAK,GAAG,qBAAqB,OAAO,CAAC;AACvD,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,gBACJ,SACA,SAC0B;AAC1B,UAAM,OAAO,MAAM,OAAO,2BAA2B;AACrD,UAAM,aAAa,QAAQ,SAAS;AACpC,UAAM,cAAc,QAAQ,IAAI,CAAC,MAAM,EAAE,IAAI;AAC7C,UAAM,WAAW;AAAA,MACf,kBAAkB;AAAA,MAClB,QAAQ;AAAA,IACV;AAGA,UAAM,WACJ,CAAC,cAAc,QAAQ,UAAU,WAAW,SACvC,QAAQ,WAGT;AACN,UAAM,WAAW,WAAW,SAAY,MAAM,KAAK,cAAc,OAAO;AACxE,UAAM,iBAAiB,UAAU;AACjC,UAAM,eAAe,UAAU;AAC/B,UAAM,aAAa,WACf,mBAAmB,QAAQ,IAC3B,UAAU;AAEd,QAAI,CAAC,YAAY;AAIf,UAAI;AACJ,YAAM,YAAY,MACf,mBAAmB,WAChB,QAAQ,QAAQ,QAAQ,IACxB,KAAK,cAAc,OAAO;AAChC,aAAO;AAAA,QACL,gBAAgB,OAAO,aAAkB;AACvC,gBAAM,SACJ,OAAO,aAAa,WAAW,KAAK,MAAM,QAAQ,IAAI;AACxD,gBAAM,SAAS,YAAY,UAAU,MAAM;AAC3C,gBAAM,SAAS,SAAS,SAAY,MAAM,UAAU;AACpD,gBAAM,aAAa,SACf,EAAE,UAAU,OAAO,MAAM,UAAU,cAAc,OAAU,IAC3D;AAAA,YACE;AAAA,YACA,QAAQ;AAAA,YACR,QAAQ;AAAA,UACV;AACJ,gBAAM,WAAgC,CAAC;AACvC,gBAAM,SAAS,MAAM,KAAK;AAAA,YACxB,WAAW;AAAA,YACX;AAAA,cACE,cAAc,WAAW;AAAA,cACzB;AAAA,cACA,OAAO,QAAQ;AAAA,cACf,YAAY;AAAA,gBACV,oBAAoB,QAAQ,YAAY;AAAA,cAC1C;AAAA,cACA,eAAe,QAAQ;AAAA,cACvB,aAAa,QAAQ;AAAA,cACrB,SAAS,QAAQ;AAAA,cACjB,UAAU,QAAQ;AAAA,cAClB,mBAAmB,QAAQ;AAAA,cAC3B,UAAU;AAAA,cACV;AAAA,YACF;AAAA,UACF;AACA,gBAAM,UAAU,wBAAwB,UAAU,MAAM;AACxD,iCAAuB,OAAO;AAC9B,kBAAQ,UAAU,KAAK,GAAG,qBAAqB,OAAO,CAAC;AACvD,iBAAO;AAAA,QACT;AAAA,QACA,YAAY;AAAA,QACZ,aAAa,CAAC;AAAA,QACd;AAAA,MACF;AAAA,IACF;AAEA,QAAI,YAA8B,KAAK,wBAAwB;AAAA;AAAA;AAAA;AAAA,MAI7D,OAAO;AAAA,MACP,cAAc,iBACV,EAAE,GAAG,cAAc,CAAC,aAAa,GAAG,eAAe,IACnD;AAAA,MACJ,OAAO,QAAQ,IAAI,UAAU;AAAA,MAC7B;AAAA,MACA,OAAO,QAAQ;AAAA,MACf,YAAY;AAAA,QACV,oBAAoB,QAAQ,YAAY;AAAA,MAC1C;AAAA,MACA,eAAe,QAAQ;AAAA,MACvB,aAAa,QAAQ;AAAA,MACrB,SAAS,QAAQ;AAAA,MACjB,UAAU,QAAQ;AAAA,MAClB,mBAAmB,QAAQ;AAAA,IAC7B,CAAC;AAED,eAAW,UAAU,SAAS;AAC5B,kBAAY,UAAU,aAAa,MAAM;AAAA,IAC3C;AAEA,WAAO;AAAA,MACL,gBAAgB,OAAO,aAAkB;AACvC,cAAM,SACJ,OAAO,aAAa,WAAW,KAAK,MAAM,QAAQ,IAAI;AACxD,cAAM,EAAE,UAAU,cAAc,IAAI;AAAA,UAClC;AAAA,UACA;AAAA,UACA;AAAA,QACF;AACA,cAAM,SAAS,MAAM,UAAU,eAAe,eAAe;AAAA,UAC3D,YAAY;AAAA,YACV,oBAAoB,QAAQ,YAAY;AAAA,UAC1C;AAAA,UACA,eAAe,QAAQ;AAAA,UACvB,aAAa,QAAQ;AAAA,UACrB,SAAS,QAAQ;AAAA,UACjB,UAAU,QAAQ;AAAA,UAClB,mBAAmB,QAAQ;AAAA,QAC7B,CAAC;AACD,+BAAuB,OAAO,YAAY,CAAC,CAAC;AAC5C,gBAAQ,UAAU,KAAK,GAAG,qBAAqB,OAAO,QAAQ,CAAC;AAC/D,eAAO,OAAO;AAAA,MAChB;AAAA,MACA,uBAAuB,UAAU,2BAC7B,OAAO,WAAgB;AACrB,cAAM,SACJ,OAAO,WAAW,WAAW,KAAK,MAAM,MAAM,IAAI;AACpD,cAAM,EAAE,UAAU,cAAc,IAAI;AAAA,UAClC;AAAA,UACA;AAAA,UACA;AAAA,QACF;AACA,cAAM,SAAS,MAAM,UAAU;AAAA,UAC7B;AAAA,UACA;AAAA,YACE,YAAY;AAAA,cACV,oBAAoB,QAAQ,YAAY;AAAA,YAC1C;AAAA,UACF;AAAA,QACF;AACA,+BAAuB,OAAO,YAAY,CAAC,CAAC;AAC5C,eAAO,OAAO;AAAA,MAChB,IACA;AAAA,MACJ,YAAY;AAAA,MACZ;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EAEA,UAAU,OAAiC;AACzC,WAAO,OAAO,UAAU,WAAW,KAAK,MAAM,KAAK,IAAI;AAAA,EACzD;AAAA,EAEA,iBAAiB,KAAkD;AACjE,UAAM,SAAS,aAAa,aAAa,GAAa;AACtD,WAAO;AAAA,MACL,OAAO,OAAO;AAAA,MACd,GAAI,OAAO,OAAO,SAAS,KAAK,EAAE,QAAQ,OAAO,OAAO;AAAA,IAC1D;AAAA,EACF;AAAA,EAEA,MAAM,4BACJ,KACA,SAC6C;AAC7C,UAAM,OAAO,MAAM,OAAO,2BAA2B;AACrD,UAAM,SAAS,OAAO,QAAQ,WAAW,KAAK,MAAM,GAAG,IAAI;AAC3D,UAAM,SAAS,KAAK,iBAAiB,QAAe,OAAO;AAC3D,UAAM,SAAS,OAAO,UAAU,CAAC;AACjC,WAAO;AAAA,MACL,OAAO,OAAO;AAAA,MACd,GAAI,OAAO,SAAS,KAAK,EAAE,OAAO;AAAA,IACpC;AAAA,EACF;AAAA,EAEA,MAAM,eACJ,KACA,UAA4B,CAAC,GACH;AAC1B,UAAM,OAAO,MAAM,OAAO,2BAA2B;AACrD,UAAM,SAAS,OAAO,QAAQ,WAAW,KAAK,MAAM,GAAG,IAAI;AAC3D,QAAI,WAAW;AAAA,MACb,QAAQ,UAAU,WAAW,SACxB,QAAQ,WAGT;AAAA,MACJ;AAAA,IACF;AACA,QAAI,CAAC,UAAU;AACb,UAAI;AACF,mBAAY,MAAM,KAAK,aAAa,QAAQ,SAAS,CAAC,CAAC;AAAA,MAGzD,QAAQ;AAAA,MAIR;AAAA,IACF;AACA,WAAO,KAAK,mBAAmB,UAAU,MAAM,YAAY,QAAQ;AAAA,MACjE,GAAI,YAAY,EAAE,SAAS;AAAA,MAC3B,UAAU,QAAQ,YAAY,iBAAiB,MAAM;AAAA,MACrD,SAAS,QAAQ,SAAS;AAAA,MAC1B,QAAQ,QAAQ,SAAS;AAAA,IAC3B,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,gBACJ,KACA,UAA4B,CAAC,GACF;AAG3B,UAAM,WAAgC,CAAC;AACvC,UAAM,WAAW,MAAM,KAAK,aAAa,KAAK,SAAS,QAAQ;AAC/D,2BAAuB,QAAQ;AAC/B,YAAQ,UAAU,KAAK,GAAG,qBAAqB,QAAQ,CAAC;AACxD,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,MAAc,aACZ,KACA,SACA,UAC2B;AAC3B,UAAM,OAAO,MAAM,OAAO,2BAA2B;AACrD,UAAM,SAAS,OAAO,QAAQ,WAAW,KAAK,MAAM,GAAG,IAAI;AAC3D,UAAM,WAAW,MAAM,KAAK,cAAc,OAAO;AACjD,UAAM,aAAa;AAAA,MACjB;AAAA,MACA,SAAS;AAAA,MACT,SAAS;AAAA,IACX;AACA,UAAM,WAAW,KAAK;AAAA,MACpB,WAAW;AAAA,MACX;AAAA,QACE,cAAc,WAAW;AAAA,QACzB,OAAO,QAAQ;AAAA,QACf,UAAU,QAAQ,YAAY,iBAAiB,MAAM;AAAA,QACrD;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,MACL;AAAA,QACE,GAAG;AAAA,QACH,UAAU;AAAA,UACR,GAAG,SAAS;AAAA,UACZ,GAAI,SAAS,SAAS,EAAE,YAAY,SAAS,MAAM;AAAA,QACrD;AAAA,MACF;AAAA,MACA;AAAA,MACA,qBAAqB,QAAQ;AAAA,IAC/B;AAAA,EACF;AAAA,EAEA,eAAe,UAAqB;AAElC,WAAO;AAAA,EACT;AAAA,EAEA,mBAAwC;AACtC,QAAI;AACF,YAAM,OAAO,UAAQ,2BAA2B;AAChD,aAAO,KAAK,UAAU,CAAC;AAAA,IACzB,QAAQ;AACN,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AAAA,EAEA,MAAM,wBAAsD;AAC1D,UAAM,OAAO,MAAM,OAAO,2BAA2B;AACrD,WAAO,KAAK,UAAU,CAAC;AAAA,EACzB;AAAA,EAEA,MAAM,aAAa,SAAyC;AAC1D,UAAM,OAAO,MAAM,OAAO,2BAA2B;AACrD,UAAM,EAAE,UAAU,IAAI,MAAM,KAAK,cAAc,OAAO;AACtD,WAAO,aAAc,KAAK,QAAgB,WAAW,CAAC;AAAA,EACxD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,cACZ,SACyB;AACzB,UAAM,OAAO,MAAM,OAAO,2BAA2B;AAErD,UAAM,WAAgC,EAAE,GAAG,QAAQ,aAAa;AAEhE,QAAI,OAAO,QAAQ,UAAU,YAAY,QAAQ,UAAU,MAAM;AAC/D,eAAS,aAAa,QAAQ,MAAM,IAAI,CAAC,IAAI,QAAQ;AAAA,IACvD;AAEA,QAAI;AACJ,QAAI,QAAQ,WAAW;AACrB,UAAI;AACF,YAAI,QAAQ,UAAU,SAAS,OAAO,GAAG;AACvC,sBAAY,MAAM,KAAK,kBAAkB,QAAQ,SAAS;AAAA,QAC5D,OAAO;AACL,gBAAM,YAAiB,cAAQ,QAAQ,IAAI,GAAG,QAAQ,SAAS;AAC/D,gBAAM,cAAc,MAAM,OAAO;AACjC,sBAAY,YAAY,WAAW,YAAY;AAAA,QACjD;AAAA,MACF,SAAS,OAAY;AACnB;AAAA,UACE,6BAA6B,QAAQ,SAAS,KAAK,MAAM,OAAO;AAAA,UAChE;AAAA,QACF;AAAA,MACF;AACA,UAAI,WAAW;AACb,iBAAS,aAAa,UAAU,IAAI,CAAC,IAAI;AAAA,MAC3C;AAAA,IACF;AAEA,UAAM,eACJ,OAAO,KAAK,QAAQ,EAAE,SAAS,IAAI,WAAW;AAEhD,QAAI,WAAW;AACb,aAAO,EAAE,WAAW,WAAW,cAAc,OAAO,QAAQ,UAAU;AAAA,IACxE;AAEA,QAAI,OAAO,QAAQ,UAAU,UAAU;AACrC,YAAM,QACJ,QAAQ,eAAe,QAAQ,KAAK,KACnC,KAAK,SAAiC,QAAQ,KAAK;AACtD,UAAI;AACF,eAAO,EAAE,WAAW,OAAO,cAAc,OAAO,QAAQ,MAAM;AAEhE,UAAI,QAAQ,MAAM,SAAS,OAAO,KAAQ,eAAW,QAAQ,KAAK,GAAG;AACnE,YAAI;AACF,iBAAO;AAAA,YACL,WAAW,MAAM,KAAK,kBAAkB,QAAQ,KAAK;AAAA,YACrD;AAAA,YACA,OAAO,QAAQ;AAAA,UACjB;AAAA,QACF,QAAQ;AAAA,QAAC;AAAA,MACX;AAEA,UAAI;AACF,cAAM,SAAS,MAAM,KAAK,kBAAkB,QAAQ,KAAK;AACzD,eAAO;AAAA,UACL,WAAW;AAAA,UACX;AAAA,UACA,OAAQ,QAAgB,QAAQ,QAAQ;AAAA,QAC1C;AAAA,MACF,QAAQ;AAAA,MAAC;AAET;AAAA,QACE,kBAAkB,QAAQ,KAAK;AAAA,QAC/B;AAAA,MACF;AAAA,IACF;AAEA,QAAI,OAAO,QAAQ,UAAU,YAAY,QAAQ,UAAU,MAAM;AAC/D,aAAO;AAAA,QACL,WAAW,QAAQ;AAAA,QACnB;AAAA,QACA,OAAO,aAAa,QAAQ,MAAM,IAAI;AAAA,MACxC;AAAA,IACF;AAEA,WAAO,EAAE,WAAW,QAAW,cAAc,OAAO,OAAU;AAAA,EAChE;AAAA,EAEA,MAAM,iBACJ,SAC0C;AAC1C,YAAQ,MAAM,KAAK,cAAc,OAAO,GAAG;AAAA,EAC7C;AAAA,EAEA,MAAM,wBAAsC;AAC1C,QAAI;AACF,YAAM,OAAO,MAAM,OAAO,2BAA2B;AACrD,aAAO,KAAK,wBAAwB,KAAK;AAAA,IAC3C,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,kBAAiC;AACrC,QAAI;AACF,YAAM,OAAO,MAAM,OAAO,2BAA2B;AACrD,WAAK,0BAA0B;AAAA,IACjC,QAAQ;AAAA,IAER;AAAA,EACF;AACF;AAEO,IAAM,oBAAN,MAAiD;AAAA,EACtD,OAAmB;AAAA,EACnB,YAAY;AAAA,EACZ,QAAQ;AAAA,EACR,cAAc;AAAA,EAEd,MAAM,cAA0C;AAC9C,UAAM,OAAO,MAAM,OAAO,2BAA2B;AACrD,WAAO,KAAK,gBAAgB;AAAA,EAC9B;AAAA,EAEA,MAAM,mBAAuD;AAC3D,UAAM,OAAO,MAAM,OAAO,2BAA2B;AACrD,WAAO,KAAK,qBAAqB;AAAA,EACnC;AAAA,EAEA,MAAM,eACJ,MACA,SACiB;AACjB,UAAM,OAAO,MAAM,OAAO,2BAA2B;AACrD,UAAM,SAAS,OAAO,SAAS,WAAW,KAAK,MAAM,IAAc,IAAI;AACvE,UAAM,WAAW;AAAA,MACf,QAAQ,UAAU,WAAW,SACxB,QAAQ,WAGT;AAAA,MACJ;AAAA,IACF;AACA,QAAI;AACJ,QAAI;AACJ,QAAI,UAAU;AACZ,sBAAgB,SAAS,MAAM;AAAA,IACjC,OAAO;AACL,YAAM,WAAW,MAAM,KAAK,cAAc,OAAO;AACjD,YAAM,aAAa;AAAA,QACjB;AAAA,QACA,SAAS;AAAA,QACT,SAAS;AAAA,MACX;AACA,sBAAgB,WAAW;AAC3B,qBAAe,WAAW;AAAA,IAC5B;AACA,UAAM,WAAW;AAAA,MACf,qBAAqB;AAAA,MACrB,QAAQ;AAAA,IACV;AAKA,UAAM,SAAS,MAAM,KAAK,2BAA2B,eAAsB;AAAA,MACzE;AAAA,MACA;AAAA,MACA,OAAO,QAAQ;AAAA,MACf,YAAY;AAAA,QACV,oBAAoB,QAAQ,YAAY;AAAA,MAC1C;AAAA,MACA,eAAe,QAAQ;AAAA,MACvB,aAAa,QAAQ;AAAA,MACrB,SAAS,QAAQ;AAAA,MACjB,UAAU,QAAQ;AAAA,MAClB;AAAA,IACF,CAAC;AACD,UAAM,UAAU;AAAA,MACd,qBAAqB,OAAO,QAAQ;AAAA,MACpC;AAAA,IACF;AACA,2BAAuB,OAAO;AAC9B,YAAQ,UAAU,KAAK,GAAG,OAAO;AACjC,WAAO,OAAO;AAAA,EAChB;AAAA,EAEA,MAAM,gBACJ,SACA,SAC0B;AAC1B,UAAM,OAAO,MAAM,OAAO,2BAA2B;AACrD,UAAM,aAAa,QAAQ,SAAS;AACpC,UAAM,cAAc,QAAQ,IAAI,CAAC,MAAM,EAAE,IAAI;AAC7C,UAAM,WAAW;AAAA,MACf,qBAAqB;AAAA,MACrB,QAAQ;AAAA,IACV;AAGA,UAAM,WACJ,CAAC,cAAc,QAAQ,UAAU,WAAW,SACvC,QAAQ,WAGT;AACN,UAAM,WAAW,WAAW,SAAY,MAAM,KAAK,cAAc,OAAO;AACxE,UAAM,iBAAiB,UAAU;AACjC,UAAM,eAAe,UAAU;AAC/B,UAAM,aAAa,WACf,mBAAmB,QAAQ,IAC3B,UAAU;AAEd,QAAI,CAAC,YAAY;AAIf,UAAI;AACJ,YAAM,YAAY,MACf,mBAAmB,WAChB,QAAQ,QAAQ,QAAQ,IACxB,KAAK,cAAc,OAAO;AAChC,aAAO;AAAA,QACL,gBAAgB,OAAO,aAAkB;AACvC,gBAAM,SACJ,OAAO,aAAa,WAAW,KAAK,MAAM,QAAQ,IAAI;AACxD,gBAAM,SAAS,YAAY,UAAU,MAAM;AAC3C,gBAAM,SAAS,SAAS,SAAY,MAAM,UAAU;AACpD,gBAAM,aAAa,SACf,EAAE,UAAU,OAAO,MAAM,UAAU,cAAc,OAAU,IAC3D;AAAA,YACE;AAAA,YACA,QAAQ;AAAA,YACR,QAAQ;AAAA,UACV;AACJ,gBAAM,SAAS,MAAM,KAAK;AAAA,YACxB,WAAW;AAAA,YACX;AAAA,cACE,cAAc,WAAW;AAAA,cACzB;AAAA,cACA,OAAO,QAAQ;AAAA,cACf,YAAY;AAAA,gBACV,oBAAoB,QAAQ,YAAY;AAAA,cAC1C;AAAA,cACA,eAAe,QAAQ;AAAA,cACvB,aAAa,QAAQ;AAAA,cACrB,SAAS,QAAQ;AAAA,cACjB,UAAU,QAAQ;AAAA,cAClB,UAAU;AAAA,YACZ;AAAA,UACF;AACA,gBAAM,WAAW;AAAA,YACf,qBAAqB,OAAO,QAAQ;AAAA,YACpC;AAAA,UACF;AACA,iCAAuB,QAAQ;AAC/B,kBAAQ,UAAU,KAAK,GAAG,QAAQ;AAClC,iBAAO,OAAO;AAAA,QAChB;AAAA,QACA,YAAY;AAAA,QACZ,aAAa,CAAC;AAAA,QACd;AAAA,MACF;AAAA,IACF;AAEA,QAAI,YAA8B,KAAK,4BAA4B;AAAA;AAAA;AAAA;AAAA,MAIjE,OAAO;AAAA,MACP,cAAc,iBACV,EAAE,GAAG,cAAc,CAAC,aAAa,GAAG,eAAe,IACnD;AAAA,MACJ,OAAO,QAAQ,IAAI,UAAU;AAAA,MAC7B;AAAA,MACA,OAAO,QAAQ;AAAA,MACf,YAAY;AAAA,QACV,oBAAoB,QAAQ,YAAY;AAAA,MAC1C;AAAA,MACA,eAAe,QAAQ;AAAA,MACvB,aAAa,QAAQ;AAAA,MACrB,SAAS,QAAQ;AAAA,MACjB,UAAU,QAAQ;AAAA,IACpB,CAAC;AAED,eAAW,UAAU,SAAS;AAC5B,kBAAY,UAAU,aAAa,MAAM;AAAA,IAC3C;AAEA,WAAO;AAAA,MACL,gBAAgB,OAAO,aAAkB;AACvC,cAAM,SACJ,OAAO,aAAa,WAAW,KAAK,MAAM,QAAQ,IAAI;AACxD,cAAM,EAAE,UAAU,cAAc,IAAI;AAAA,UAClC;AAAA,UACA;AAAA,UACA;AAAA,QACF;AACA,cAAM,SAAS,MAAM,UAAU,eAAe,eAAe;AAAA,UAC3D,YAAY;AAAA,YACV,oBAAoB,QAAQ,YAAY;AAAA,UAC1C;AAAA,UACA,eAAe,QAAQ;AAAA,UACvB,aAAa,QAAQ;AAAA,UACrB,SAAS,QAAQ;AAAA,UACjB,UAAU,QAAQ;AAAA,QACpB,CAAC;AACD,cAAM,aAAa,qBAAqB,OAAO,QAAQ;AACvD,+BAAuB,UAAU;AACjC,gBAAQ,UAAU,KAAK,GAAG,UAAU;AACpC,eAAO,OAAO;AAAA,MAChB;AAAA,MACA,YAAY;AAAA,MACZ;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EAEA,UAAU,OAAiC;AACzC,WAAO,OAAO,UAAU,WAAW,KAAK,MAAM,KAAK,IAAI;AAAA,EACzD;AAAA,EAEA,iBAAiB,KAAkD;AACjE,UAAM,SAAS,6BAA6B,GAAG;AAC/C,WAAO;AAAA,MACL,OAAO,OAAO;AAAA,MACd,GAAI,OAAO,OAAO,SAAS,KAAK,EAAE,QAAQ,OAAO,OAAO;AAAA,IAC1D;AAAA,EACF;AAAA,EAEA,MAAM,4BACJ,KACA,SAC6C;AAC7C,UAAM,OAAO,MAAM,OAAO,2BAA2B;AACrD,UAAM,SAAS,OAAO,QAAQ,WAAW,KAAK,MAAM,GAAG,IAAI;AAC3D,UAAM,SAAS,KAAK,qBAAqB,QAAe,OAAO;AAC/D,WAAO;AAAA,MACL,OAAO,OAAO;AAAA,MACd,GAAI,OAAO,OAAO,SAAS,KAAK,EAAE,QAAQ,OAAO,OAAO;AAAA,IAC1D;AAAA,EACF;AAAA,EAEA,MAAM,eACJ,KACA,UAA4B,CAAC,GACH;AAC1B,UAAM,OAAO,MAAM,OAAO,2BAA2B;AACrD,UAAM,SAAS,OAAO,QAAQ,WAAW,KAAK,MAAM,GAAG,IAAI;AAC3D,QAAI,WAAW;AAAA,MACb,QAAQ,UAAU,WAAW,SACxB,QAAQ,WAGT;AAAA,MACJ;AAAA,IACF;AACA,QAAI,CAAC,UAAU;AACb,UAAI;AACF,mBAAY,MAAM,KAAK,aAAa,QAAQ,SAAS,CAAC,CAAC;AAAA,MAGzD,QAAQ;AAAA,MAIR;AAAA,IACF;AACA,WAAO,KAAK,mBAAmB,UAAU,MAAM,YAAY,QAAQ;AAAA,MACjE,GAAI,YAAY,EAAE,SAAS;AAAA,MAC3B,UAAU,QAAQ,YAAY,iBAAiB,MAAM;AAAA,MACrD,SAAS,QAAQ,SAAS;AAAA,MAC1B,QAAQ,QAAQ,SAAS;AAAA,IAC3B,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,gBACJ,KACA,UAA4B,CAAC,GACF;AAG3B,UAAM,WAAkB,CAAC;AACzB,UAAM,WAAW,MAAM,KAAK,aAAa,KAAK,SAAS,QAAQ;AAC/D,UAAM,aAAa,qBAAqB,QAAQ;AAChD,2BAAuB,UAAU;AACjC,YAAQ,UAAU,KAAK,GAAG,UAAU;AACpC,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,MAAc,aACZ,KACA,SACA,UAC2B;AAC3B,UAAM,OAAO,MAAM,OAAO,2BAA2B;AACrD,UAAM,SAAS,OAAO,QAAQ,WAAW,KAAK,MAAM,GAAG,IAAI;AAC3D,UAAM,WAAW,MAAM,KAAK,cAAc,OAAO;AACjD,UAAM,aAAa;AAAA,MACjB;AAAA,MACA,SAAS;AAAA,MACT,SAAS;AAAA,IACX;AACA,UAAM,WAAW,KAAK;AAAA,MACpB,WAAW;AAAA,MACX;AAAA,QACE,cAAc,WAAW;AAAA,QACzB,OAAO,QAAQ;AAAA,QACf,UAAU;AAAA,UACR,qBAAqB;AAAA,UACrB,QAAQ;AAAA,QACV;AAAA,QACA,UAAU,QAAQ,YAAY,iBAAiB,MAAM;AAAA,QACrD;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,MACL;AAAA,QACE,GAAG;AAAA,QACH,UAAU;AAAA,UACR,GAAG,SAAS;AAAA,UACZ,GAAI,SAAS,SAAS,EAAE,YAAY,SAAS,MAAM;AAAA,QACrD;AAAA,MACF;AAAA,MACA;AAAA,MACA,qBAAqB,QAAQ;AAAA,IAC/B;AAAA,EACF;AAAA,EAEA,eAAe,UAAqB;AAClC,WAAO;AAAA,EACT;AAAA,EAEA,mBAAwC;AACtC,QAAI;AACF,YAAM,OAAO,UAAQ,2BAA2B;AAChD,aAAO,KAAK,cAAc,CAAC;AAAA,IAC7B,QAAQ;AACN,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AAAA,EAEA,MAAM,wBAAsD;AAC1D,UAAM,OAAO,MAAM,OAAO,2BAA2B;AACrD,WAAO,KAAK,cAAc,CAAC;AAAA,EAC7B;AAAA,EAEA,MAAM,aAAa,SAAyC;AAC1D,UAAM,OAAO,MAAM,OAAO,2BAA2B;AACrD,UAAM,SAAU,KAAa,cAAc,CAAC;AAC5C,UAAM,EAAE,UAAU,IAAI,MAAM,KAAK,cAAc,OAAO;AACtD,WAAO,aAAa,OAAO,WAAW,CAAC;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,cACZ,SACyB;AACzB,UAAM,OAAO,MAAM,OAAO,2BAA2B;AACrD,UAAM,SAAU,KAAa,cAAc,CAAC;AAE5C,UAAM,WAAgC,EAAE,GAAG,QAAQ,aAAa;AAEhE,QAAI,OAAO,QAAQ,UAAU,YAAY,QAAQ,UAAU,MAAM;AAC/D,eAAS,aAAa,QAAQ,MAAM,IAAI,CAAC,IAAI,QAAQ;AAAA,IACvD;AAEA,QAAI;AACJ,QAAI,QAAQ,WAAW;AACrB,UAAI;AACF,YAAI,QAAQ,UAAU,SAAS,OAAO,GAAG;AACvC,gBAAM,UAAa;AAAA,YACZ,cAAQ,QAAQ,IAAI,GAAG,QAAQ,SAAS;AAAA,YAC7C;AAAA,UACF;AAOA,gBAAM,SAAS,MAAM,OAAO,6BAA6B;AACzD,gBAAM,UAAU,OAAO,kBAAkB,KAAK,MAAM,OAAO,CAAC;AAC5D,cAAI,CAAC,QAAQ,OAAO;AAClB,kBAAM,SAAS,QAAQ,OACpB,MAAM,GAAG,CAAC,EACV;AAAA,cAAI,CAAC,UACJ,MAAM,OAAO,GAAG,MAAM,IAAI,KAAK,MAAM,OAAO,KAAK,MAAM;AAAA,YACzD,EACC,KAAK,IAAI;AACZ,kBAAM,IAAI;AAAA,cACR,iCAA4B,MAAM,GAChC,QAAQ,OAAO,SAAS,IACpB,SAAS,QAAQ,OAAO,SAAS,CAAC,WAClC,EACN;AAAA,YACF;AAAA,UACF;AACA,sBAAY,QAAQ;AAAA,QACtB,OAAO;AACL,gBAAM,YAAiB,cAAQ,QAAQ,IAAI,GAAG,QAAQ,SAAS;AAC/D,gBAAM,cAAc,MAAM,OAAO;AACjC,sBAAY,YAAY,WAAW,YAAY;AAAA,QACjD;AAAA,MACF,SAAS,OAAY;AACnB;AAAA,UACE,6BAA6B,QAAQ,SAAS,KAAK,MAAM,OAAO;AAAA,UAChE;AAAA,QACF;AAAA,MACF;AACA,UAAI,WAAW;AACb,iBAAS,aAAa,UAAU,IAAI,CAAC,IAAI;AAAA,MAC3C;AAAA,IACF;AAEA,UAAM,eACJ,OAAO,KAAK,QAAQ,EAAE,SAAS,IAAI,WAAW;AAEhD,QAAI,WAAW;AACb,aAAO,EAAE,WAAW,WAAW,cAAc,OAAO,QAAQ,UAAU;AAAA,IACxE;AAEA,QAAI,OAAO,QAAQ,UAAU,UAAU;AAIrC,YAAM,QACJ,QAAQ,eAAe,QAAQ,KAAK,KAAK,OAAO,QAAQ,KAAK;AAC/D,UAAI;AACF,eAAO,EAAE,WAAW,OAAO,cAAc,OAAO,QAAQ,MAAM;AAEhE,UAAI,QAAQ,MAAM,SAAS,OAAO,KAAQ,eAAW,QAAQ,KAAK,GAAG;AACnE,YAAI;AACF,gBAAM,UAAa;AAAA,YACZ,cAAQ,QAAQ,IAAI,GAAG,QAAQ,KAAK;AAAA,YACzC;AAAA,UACF;AACA,iBAAO;AAAA,YACL,WAAW,KAAK,MAAM,OAAO;AAAA,YAC7B;AAAA,YACA,OAAO,QAAQ;AAAA,UACjB;AAAA,QACF,QAAQ;AAAA,QAAC;AAAA,MACX;AAEA;AAAA,QACE,kBAAkB,QAAQ,KAAK;AAAA,QAC/B;AAAA,MACF;AAAA,IACF;AAEA,QAAI,OAAO,QAAQ,UAAU,YAAY,QAAQ,UAAU,MAAM;AAC/D,aAAO;AAAA,QACL,WAAW,QAAQ;AAAA,QACnB;AAAA,QACA,OAAO,aAAa,QAAQ,MAAM,IAAI;AAAA,MACxC;AAAA,IACF;AAEA,WAAO,EAAE,WAAW,QAAW,cAAc,OAAO,OAAU;AAAA,EAChE;AAAA,EAEA,MAAM,iBACJ,SAC0C;AAC1C,YAAQ,MAAM,KAAK,cAAc,OAAO,GAAG;AAAA,EAC7C;AACF;AAEO,SAAS,cAAc,QAAmC;AAC/D,UAAQ,QAAQ;AAAA,IACd,KAAK;AACH,aAAO,IAAI,kBAAkB;AAAA,IAC/B,KAAK;AACH,aAAO,IAAI,kBAAkB;AAAA,IAC/B;AACE,YAAM,IAAI,MAAM,mBAAmB,MAAM,EAAE;AAAA,EAC/C;AACF;;;AS93CA,SAAS,YAAAG,iBAAgB;AAEzB,YAAYC,WAAU;AAsCtB,IAAM,WAA6C;AAAA,EACjD,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,UAAU;AAAA,EACV,SAAS;AAAA,EACT,SAAS;AACX;AAEA,SAAS,eAAe,OAAuB;AAC7C,SAAO,MAAM;AAAA,IACX;AAAA,IACA,CAAC,WAAW,SAAS,MAAM,KAAK;AAAA,EAClC;AACF;AAEA,IAAM,eACJ;AAGF,IAAM,uBACJ;AAOK,SAAS,iBAAiB,SAAgC;AAC/D,QAAM,QAAuB,CAAC;AAC9B,aAAW,aAAa,QAAQ,SAAS,YAAY,GAAG;AACtD,UAAMC,SAAuB,CAAC;AAC9B,UAAM,QAAuB,CAAC;AAC9B,QAAI;AACJ,eAAW,SAAS,UAAU,CAAC,EAAE,SAAS,oBAAoB,GAAG;AAC/D,UAAI,MAAM,CAAC,MAAM,WAAW;AAC1B,YAAI,KAAM,OAAM,KAAK,IAAI;AACzB,eAAO;AAAA,MACT,WAAW,MAAM,CAAC,MAAM,QAAW;AACjC,eAAO;AAAA,UACL,MAAM,OAAO,MAAM,CAAC,CAAC;AAAA,UACrB,MAAM,OAAO,MAAM,CAAC,CAAC;AAAA,UACrB,MAAM,OAAO,MAAM,CAAC,CAAC;AAAA,UACrB,MAAM,OAAO,MAAM,CAAC,CAAC;AAAA,UACrB,OAAO,CAAC;AAAA,QACV;AAAA,MACF,OAAO;AACL,cAAM,MAAM,KAAKA,OAAM,MAAM;AAC7B,QAAAA,OAAM,KAAK;AAAA,UACT,MAAM,OAAO,MAAM,CAAC,CAAC;AAAA,UACrB,MAAM,OAAO,MAAM,CAAC,CAAC;AAAA,UACrB,MAAM,OAAO,MAAM,CAAC,CAAC;AAAA,UACrB,MAAM,OAAO,MAAM,CAAC,CAAC;AAAA,UACrB,MAAM,eAAe,MAAM,CAAC,CAAC;AAAA,QAC/B,CAAC;AAAA,MACH;AAAA,IACF;AACA,UAAM,KAAK;AAAA,MACT,SAAS,OAAO,UAAU,CAAC,CAAC;AAAA,MAC5B,UAAU,OAAO,UAAU,CAAC,CAAC;AAAA,MAC7B,OAAAA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAEA,SAAS,sBAAgC;AACvC,QAAM,aAAuB,CAAC;AAC9B,QAAM,aAAa,QAAQ,IAAI,gBAAgB,KAAK;AACpD,MAAI,WAAY,YAAW,KAAK,UAAU;AAC1C,aAAW,KAAK,WAAW;AAC3B,SAAO,CAAC,GAAG,IAAI,IAAI,UAAU,CAAC;AAChC;AAEA,eAAe,IACb,QACA,MACA,WACiB;AACjB,SAAO,IAAI,QAAQ,CAACC,UAAS,WAAW;AACtC,IAAAH;AAAA,MACE;AAAA,MACA;AAAA,MACA,EAAE,SAAS,WAAW,WAAW,KAAK,OAAO,KAAK;AAAA,MAClD,CAAC,OAAO,WAAW;AACjB,YAAI,MAAO,QAAO,KAAK;AAAA,YAClB,CAAAG,SAAQ,MAAM;AAAA,MACrB;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAIA,IAAI;AACJ,eAAe,mBAAoC;AACjD,MAAI,CAAC,kBAAkB;AACrB,wBAAoB,YAAY;AAC9B,iBAAW,aAAa,oBAAoB,GAAG;AAC7C,YAAI,UAAU,SAAc,SAAG,GAAG;AAChC,cAAI;AACF,kBAAM,IAAI,WAAW,CAAC,IAAI,GAAG,GAAM;AACnC,mBAAO;AAAA,UACT,QAAQ;AACN;AAAA,UACF;AAAA,QACF;AACA,YAAI;AACF,gBAAM,IAAI,WAAW,CAAC,IAAI,GAAG,GAAM;AACnC,iBAAO;AAAA,QACT,SAAS,OAAO;AACd,gBAAM,OAAQ,MAAgC;AAG9C,cAAI,SAAS,YAAY,SAAS,SAAU,QAAO;AAAA,QACrD;AAAA,MACF;AACA,YAAM,IAAI;AAAA,QACR,mIAEgB,oBAAoB,EAAE,KAAK,IAAI,CAAC;AAAA,MAClD;AAAA,IACF,GAAG,EAAE,MAAM,CAAC,UAAU;AACpB,yBAAmB;AACnB,YAAM;AAAA,IACR,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAGA,eAAsB,qBAAuC;AAC3D,MAAI;AACF,UAAM,iBAAiB;AACvB,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAOA,eAAsB,uBACpB,SACA,UAAoD,CAAC,GAC7B;AACxB,QAAM,SAAS,MAAM,iBAAiB;AACtC,QAAM,SAAS,MAAM;AAAA,IACnB;AAAA,IACA,CAAC,QAAQ,SAAS,iBAAiB,SAAS,SAAS,GAAG;AAAA,IACxD,QAAQ,aAAa;AAAA,EACvB;AACA,SAAO,iBAAiB,MAAM;AAChC;;;AC/MA,SAAS,YAAAC,iBAAgB;AACzB,SAAS,YAAYC,WAAU;AAC/B,OAAOC,SAAQ;AACf,OAAOC,WAAU;AAmBV,IAAM,gBAAgB;AAGtB,IAAM,UAAU;AAGhB,SAAS,SAAS,QAAqB;AAC5C,QAAM,SAAS,iDAAiD;AAAA,IAC9D,OAAO,SAAS,GAAG,EAAE,EAAE,SAAS,QAAQ;AAAA,EAC1C;AACA,MAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,8BAA8B;AAC3D,QAAM,QAAQ,OAAO,OAAO,CAAC,CAAC;AAC9B,QAAM,SAAS,OAAO,OAAO,CAAC,CAAC;AAC/B,QAAM,WAAW,OAAO,OAAO,CAAC,CAAC;AACjC,MAAI,WAAW,IAAK,OAAM,IAAI,MAAM,8BAA8B;AAClE,QAAM,QAAQ,OAAO,CAAC,EAAE;AAGxB,MAAI,OAAO,SAAS,QAAQ,QAAQ,QAAQ;AAC1C,UAAM,IAAI,MAAM,qCAAqC;AAAA,EACvD;AACA,QAAM,SAAS,IAAI;AAAA,IACjB,OAAO;AAAA,IACP,OAAO,aAAa;AAAA,IACpB,QAAQ;AAAA,EACV;AACA,SAAO,EAAE,OAAO,QAAQ,UAAU,OAAO;AAC3C;AAGO,SAAS,UAAU,KAAU,YAAY,eAAyB;AACvE,QAAM,OAAiB,CAAC;AACxB,WAAS,MAAM,GAAG,MAAM,IAAI,QAAQ,OAAO,GAAG;AAC5C,UAAM,QAAQ,MAAM,IAAI;AACxB,UAAM,MAAM,QAAQ,IAAI;AACxB,aAAS,IAAI,OAAO,IAAI,KAAK,KAAK,GAAG;AACnC,UAAI,IAAI,OAAO,CAAC,KAAK,WAAW;AAC9B,aAAK,KAAK,GAAG;AACb;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,eAAe,QAAgB,WAAgC;AAC7E,QAAM,MAAM,SAAS,MAAM;AAC3B,SAAO,EAAE,MAAM,IAAI,QAAQ,OAAO,UAAU,KAAK,SAAS,EAAE;AAC9D;AAEA,SAASC,sBAA+B;AACtC,QAAM,aAAa,QAAQ,IAAI,eAAe,KAAK;AACnD,SAAO,CAAC,GAAG,oBAAI,IAAI,CAAC,GAAI,aAAa,CAAC,UAAU,IAAI,CAAC,GAAI,UAAU,CAAC,CAAC;AACvE;AAEA,eAAeC,KACb,QACA,MACA,WACA,QACA;AACA,SAAO,IAAI,QAAc,CAACC,UAAS,WAAW;AAC5C,IAAAN;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,QACE,SAAS;AAAA,QACT,WAAW,KAAK,OAAO;AAAA,QACvB,aAAa;AAAA,QACb,GAAI,UAAU,EAAE,OAAO;AAAA,MACzB;AAAA,MACA,CAAC,UAAW,QAAQ,OAAO,KAAK,IAAIM,SAAQ;AAAA,IAC9C;AAAA,EACF,CAAC;AACH;AAIA,IAAIC;AACJ,eAAeC,mBAAmC;AAChD,MAAI,CAACD,kBAAiB;AACpB,IAAAA,oBAAmB,YAAY;AAC7B,iBAAW,aAAaH,oBAAmB,GAAG;AAC5C,YAAI;AACF,gBAAMC,KAAI,WAAW,CAAC,IAAI,GAAG,GAAM;AACnC,iBAAO;AAAA,QACT,SAAS,OAAO;AACd,gBAAM,OAAQ,MAAgC;AAG9C,cAAI,SAAS,YAAY,SAAS,SAAU,QAAO;AAAA,QACrD;AAAA,MACF;AACA,YAAM,IAAI;AAAA,QACR,4HAC2DD,oBAAmB,EAAE,KAAK,IAAI,CAAC;AAAA,MAC5F;AAAA,IACF,GAAG,EAAE,MAAM,CAAC,UAAU;AACpB,MAAAG,mBAAkB;AAClB,YAAM;AAAA,IACR,CAAC;AAAA,EACH;AACA,SAAOA;AACT;AAkBA,eAAsB,kBACpB,SACA,UAAoC,CAAC,GACd;AACvB,QAAM,SAAS,QAAQ,UAAW,MAAMC,iBAAgB;AACxD,QAAM,MAAM,QAAQ,OAAO;AAC3B,QAAM,MAAM,MAAMP,IAAG,QAAQE,MAAK,KAAKD,IAAG,OAAO,GAAG,eAAe,CAAC;AACpE,MAAI;AACF,UAAM,SAASC,MAAK,KAAK,KAAK,MAAM;AACpC,UAAME;AAAA,MACJ;AAAA,MACA,CAAC,SAAS,MAAM,OAAO,GAAG,GAAG,SAAS,MAAM;AAAA,MAC5C,QAAQ,aAAa;AAAA,MACrB,QAAQ;AAAA,IACV;AACA,UAAM,SAAS,MAAMJ,IAAG,QAAQ,GAAG,GAChC,OAAO,CAAC,SAAS,kBAAkB,KAAK,IAAI,CAAC,EAC7C;AAAA,MACC,CAAC,GAAG,MACF,OAAO,MAAM,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,OAAO,MAAM,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;AAAA,IACpE;AACF,UAAM,QAAsB,CAAC;AAC7B,eAAW,QAAQ,OAAO;AACxB,YAAM;AAAA,QACJ;AAAA,UACE,MAAMA,IAAG,SAASE,MAAK,KAAK,KAAK,IAAI,CAAC;AAAA,UACtC,QAAQ;AAAA,QACV;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,EACT,UAAE;AACA,UAAMF,IAAG,GAAG,KAAK,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,EACnD;AACF;;;AC/KA,SAAS,YAAAQ,iBAAgB;AACzB,YAAYC,WAAU;AAYtB,IAAM,cACJ;AAMK,SAAS,cAAc,QAA+B;AAC3D,QAAM,QAAuB,CAAC;AAC9B,aAAW,QAAQ,OAAO,MAAM,OAAO,GAAG;AACxC,QAAI,KAAK,WAAW,OAAO,KAAK,KAAK,WAAW,KAAK,KAAK,CAAC,KAAK,KAAK,GAAG;AACtE;AAAA,IACF;AACA,UAAM,QAAQ,YAAY,KAAK,IAAI;AACnC,QAAI,CAAC,MAAO;AACZ,UAAM,OAAO,MAAM,CAAC;AACpB,UAAM,KAAK;AAAA,MACT;AAAA,MACA,UAAU,KAAK,QAAQ,eAAe,EAAE;AAAA,MACxC,MAAM,MAAM,CAAC,EAAE,KAAK;AAAA,MACpB,UAAU,MAAM,CAAC,MAAM;AAAA,IACzB,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAGO,SAAS,aAAa,OAAuB;AAClD,SAAO,MAAM,YAAY,EAAE,QAAQ,cAAc,EAAE;AACrD;AAQO,SAAS,eACd,QACA,OACS;AACT,QAAM,SAAS,aAAa,MAAM;AAClC,MAAI,WAAW,GAAI,QAAO;AAC1B,SAAO,MAAM,KAAK,CAAC,SAAS,aAAa,KAAK,QAAQ,EAAE,WAAW,MAAM,CAAC;AAC5E;AAGA,SAAS,qBAA+B;AACtC,QAAM,aAAuB,CAAC;AAC9B,QAAM,aAAa,QAAQ,IAAI,eAAe,KAAK;AACnD,MAAI,WAAY,YAAW,KAAK,UAAU;AAE1C,QAAM,UAAU,QAAQ,IAAI,gBAAgB,KAAK;AACjD,MAAI,SAAS;AACX,eAAW;AAAA,MACJ;AAAA,QACE,cAAQ,OAAO;AAAA,QACpB,QAAQ,aAAa,UAAU,iBAAiB;AAAA,MAClD;AAAA,IACF;AAAA,EACF;AACA,aAAW,KAAK,UAAU;AAC1B,SAAO,CAAC,GAAG,IAAI,IAAI,UAAU,CAAC;AAChC;AAGA,eAAeC,KACb,QACA,MACA,WACiB;AACjB,SAAO,IAAI,QAAQ,CAACC,UAAS,WAAW;AACtC,IAAAH;AAAA,MACE;AAAA,MACA;AAAA,MACA,EAAE,SAAS,WAAW,WAAW,KAAK,OAAO,KAAK;AAAA,MAClD,CAAC,OAAO,WAAW;AACjB,YAAI,MAAO,QAAO,KAAK;AAAA,YAClB,CAAAG,SAAQ,MAAM;AAAA,MACrB;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAEA,IAAI;AAEJ,eAAe,kBAAmC;AAChD,MAAI,CAAC,iBAAiB;AACpB,uBAAmB,YAAY;AAC7B,iBAAW,aAAa,mBAAmB,GAAG;AAC5C,YAAI;AACF,gBAAMD,KAAI,WAAW,CAAC,IAAI,GAAG,GAAM;AACnC,iBAAO;AAAA,QACT,SAAS,OAAO;AACd,gBAAM,OAAQ,MAAgC;AAC9C,cAAI,SAAS,YAAY,SAAS,SAAU,QAAO;AAAA,QACrD;AAAA,MACF;AACA,YAAM,IAAI;AAAA,QACR,wHAEgB,mBAAmB,EAAE,KAAK,IAAI,CAAC;AAAA,MACjD;AAAA,IACF,GAAG,EAAE,MAAM,CAAC,UAAU;AACpB,wBAAkB;AAClB,YAAM;AAAA,IACR,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAGA,eAAsB,oBAAsC;AAC1D,MAAI;AACF,UAAM,gBAAgB;AACtB,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,eAAsB,gBACpB,SACA,UAAkC,CAAC,GACX;AACxB,QAAM,SAAS,MAAM,gBAAgB;AACrC,QAAM,SAAS,MAAMA,KAAI,QAAQ,CAAC,OAAO,GAAG,QAAQ,aAAa,GAAM;AACvE,SAAO,cAAc,MAAM;AAC7B;;;AChIA;AAAA,EACE;AAAA,OAMK;;;ACxBP;AAAA,EACE;AAAA,OAIK;AAuBP,SAAS,aAAa,MAAqC;AACzD,SAAO;AAAA,IACL,IAAI,KAAK;AAAA,IACT,MAAM,KAAK;AAAA,IACX,aAAa,KAAK;AAAA,IAClB,UAAU,KAAK;AAAA,IACf,iBAAiB,KAAK;AAAA,IACtB,kBAAkB;AAAA,IAClB,GAAI,KAAK,WAAW,EAAE,SAAS,KAAK,QAAQ;AAAA,IAC5C,SAAS,EAAE,MAAM,GAAkC;AACjD,YAAM,OAAO,MAAM,KAAK,sBAAsB;AAC9C,UAAI,CAAC,KAAM,QAAO,CAAC;AAEnB,aAAO,iBAAiB,IAAI,EAAE,OAAO,IAAI,KAAK,EAAE,KAAK,CAAC;AAAA,IACxD;AAAA,EACF;AACF;AAEO,IAAM,yBAA0C;AAAA,EACrD,IAAI;AAAA,EACJ,OAAO;AAAA,IACL,aAAa;AAAA,MACX,IAAI;AAAA,MACJ,MAAM,cAAc;AAAA,MACpB,aACE;AAAA,MACF,UAAU;AAAA,MACV,iBAAiB;AAAA,IACnB,CAAC;AAAA,IACD,aAAa;AAAA,MACX,IAAI;AAAA,MACJ,MAAM,cAAc;AAAA,MACpB,aACE;AAAA,MACF,UAAU;AAAA,MACV,iBAAiB;AAAA,IACnB,CAAC;AAAA,IACD,aAAa;AAAA,MACX,IAAI;AAAA,MACJ,MAAM,cAAc;AAAA,MACpB,aACE;AAAA,MACF,UAAU;AAAA,MACV,iBAAiB;AAAA,IACnB,CAAC;AAAA,IACD,aAAa;AAAA,MACX,IAAI;AAAA,MACJ,MAAM,cAAc;AAAA,MACpB,aACE;AAAA,MACF,UAAU;AAAA,MACV,iBAAiB;AAAA,IACnB,CAAC;AAAA,IACD,aAAa;AAAA,MACX,IAAI;AAAA,MACJ,MAAM,cAAc;AAAA,MACpB,aACE;AAAA,MACF,UAAU;AAAA,MACV,iBAAiB;AAAA,IACnB,CAAC;AAAA,IACD,aAAa;AAAA,MACX,IAAI;AAAA,MACJ,MAAM,cAAc;AAAA,MACpB,aACE;AAAA,MACF,UAAU;AAAA,MACV,iBAAiB;AAAA,IACnB,CAAC;AAAA,IACD,aAAa;AAAA,MACX,IAAI;AAAA,MACJ,MAAM,cAAc;AAAA,MACpB,aACE;AAAA,MACF,UAAU;AAAA,MACV,iBAAiB;AAAA,MACjB,SAAS,CAAC,MAAM;AAAA,IAClB,CAAC;AAAA,IACD,aAAa;AAAA,MACX,IAAI;AAAA,MACJ,MAAM,cAAc;AAAA,MACpB,aAAa;AAAA,MACb,UAAU;AAAA,MACV,iBAAiB;AAAA,MACjB,SAAS,CAAC,MAAM;AAAA,IAClB,CAAC;AAAA,IACD,aAAa;AAAA,MACX,IAAI;AAAA,MACJ,MAAM,cAAc;AAAA,MACpB,aACE;AAAA,MACF,UAAU;AAAA,MACV,iBAAiB;AAAA,MACjB,SAAS,CAAC,MAAM;AAAA,IAClB,CAAC;AAAA,EACH;AACF;;;ACvGO,SAAS,kBAAkB,OAAuB;AACvD,SAAO,MACJ,UAAU,MAAM,EAChB,YAAY,EACZ,QAAQ,cAAc,EAAE;AAC7B;AAGA,IAAM,QAAQ;AAQP,SAAS,qBAAqB,MAAsB;AACzD,SAAO,KACJ,QAAQ,gBAAgB,KAAK,EAC7B,QAAQ,mBAAmB,KAAK,EAChC,QAAQ,kBAAkB,KAAK,EAC/B,QAAQ,0BAA0B,IAAI,EACtC,QAAQ,WAAW,EAAE;AAC1B;AAGO,SAAS,eAAe,MAAwB;AACrD,SAAO,qBAAqB,IAAI,EAC7B,MAAM,KAAK,EACX,IAAI,iBAAiB,EACrB,OAAO,CAAC,YAAY,YAAY,EAAE;AACvC;AA0CO,SAAS,cACd,OACA,UAA+B,oBAAI,IAAI,GAC1B;AACb,QAAM,OAAkB,CAAC;AACzB,QAAM,YAAsB,CAAC;AAC7B,QAAM,YAAsB,CAAC;AAC7B,MAAI,SAAS;AACb,QAAM,QAAQ,CAAC,MAAM,cAAc;AACjC,eAAW,SAAS,aAAa,KAAK,KAAK,GAAG;AAC5C,YAAM,OAAO,KAAK,MAAM,KAAK;AAC7B,YAAM,MAAM,GAAG,SAAS,IAAI,KAAK;AACjC,YAAM,WAAW,QAAQ,IAAI,GAAG,IAAI,KAAK,kBAAkB,KAAK,IAAI;AACpE,WAAK,KAAK,EAAE,WAAW,MAAM,MAAM,CAAC;AACpC,gBAAU,KAAK,QAAQ;AACvB,gBAAU,KAAK,MAAM;AACrB,gBAAU,SAAS;AAAA,IACrB;AAAA,EACF,CAAC;AACD,SAAO,EAAE,OAAO,MAAM,WAAW,WAAW,QAAQ,UAAU,KAAK,EAAE,EAAE;AACzE;AAwBO,SAAS,aAAaE,QAAyC;AAIpE,QAAM,UAAU,IAAI;AAAA,IAClBA,OACG,IAAI,CAAC,GAAG,MAAM,CAAC,EACf,OAAO,CAAC,MAAM;AACb,YAAM,IAAIA,OAAM,CAAC;AAEjB,aAAO,EAAE,KAAK,SAAS,KAAK,EAAE,OAAO,EAAE,OAAO,OAAO,EAAE,OAAO,EAAE;AAAA,IAClE,CAAC;AAAA,EACL;AACA,QAAM,UAAU;AAAA,IACdA;AAAA,IACAA,OAAM,IAAI,CAAC,GAAG,MAAM,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC;AAAA,EACtD;AACA,QAAM,QAAkB,CAAC;AACzB,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAIA,OAAM,QAAQ,KAAK;AACrC,QAAI,QAAQ,IAAI,CAAC,EAAG,OAAM,KAAK,CAAC;AAAA,QAC3B,OAAM,KAAK,QAAQ,MAAM,CAAC;AAAA,EACjC;AACA,SAAO;AACT;AAEA,SAAS,aACPA,QACA,SACU;AACV,QAAM,OAAmB,CAAC;AAC1B,QAAM,MAAM,CAAC,GAAG,OAAO,EAAE;AAAA,IACvB,CAAC,GAAG,MAAMA,OAAM,CAAC,EAAE,OAAOA,OAAM,CAAC,EAAE,QAAQA,OAAM,CAAC,EAAE,OAAOA,OAAM,CAAC,EAAE;AAAA,EACtE;AACA,aAAW,KAAK,KAAK;AACnB,UAAM,MAAM,KAAK,KAAK,SAAS,CAAC;AAChC,QAAI,OAAO,QAAQA,OAAM,IAAI,CAAC,CAAC,GAAGA,OAAM,CAAC,CAAC,EAAG,KAAI,KAAK,CAAC;AAAA,QAClD,MAAK,KAAK,CAAC,CAAC,CAAC;AAAA,EACpB;AACA,QAAM,cAAc,CAAC,QAAiC;AACpD,UAAM,SAAS,CAAC,GAAG,GAAG,EAAE,KAAK,CAAC,GAAG,MAAMA,OAAM,CAAC,EAAE,OAAOA,OAAM,CAAC,EAAE,IAAI;AACpE,UAAM,MAAqB,CAAC;AAC5B,eAAW,KAAK,QAAQ;AACtB,YAAM,OAAO,IAAI,IAAI,SAAS,CAAC;AAC/B,YAAM,IAAIA,OAAM,CAAC;AAGjB,YAAM,SAAS,EAAE,OAAO,EAAE;AAC1B,UAAI,QAAQ,EAAE,OAAO,KAAK,QAAQ,SAAS,GAAG;AAC5C,aAAK,MAAM,KAAK,CAAC;AACjB,aAAK,OAAO,KAAK,IAAI,KAAK,MAAM,EAAE,IAAI;AAAA,MACxC,MAAO,KAAI,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,EAAE,MAAM,OAAO,CAAC,CAAC,EAAE,CAAC;AAAA,IAC5D;AACA,WAAO;AAAA,EACT;AACA,QAAM,YAAY,KAAK,IAAI,WAAW;AACtC,QAAM,QAAQ,KAAK,IAAI,CAAC,SAAS;AAAA,IAC/B,MAAM,KAAK,IAAI,GAAG,IAAI,IAAI,CAAC,MAAMA,OAAM,CAAC,EAAE,IAAI,CAAC;AAAA,IAC/C,MAAM,KAAK,IAAI,GAAG,IAAI,IAAI,CAAC,MAAMA,OAAM,CAAC,EAAE,IAAI,CAAC;AAAA,EACjD,EAAE;AAaF,QAAM,QAAkB,CAAC;AACzB,MAAI,UACF;AACF,QAAM,QAAQ,MAAY;AACxB,QAAI,CAAC,QAAS;AACd,QAAI,CAAC,QAAQ,OAAO;AAClB,iBAAW,KAAK,QAAQ;AACtB,mBAAW,KAAK,UAAU,CAAC,EAAG,OAAM,KAAK,GAAG,EAAE,KAAK;AAAA,IACvD,OAAO;AACL,cAAQ,QAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,IAAI;AAC9C,iBAAW,UAAU,QAAQ,SAAS;AACpC,eAAO,UAAU;AAAA,UACf,CAAC,GAAG,MACFA,OAAM,EAAE,MAAM,CAAC,CAAC,EAAE,OAAOA,OAAM,EAAE,MAAM,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE;AAAA,QAClE;AACA,mBAAW,KAAK,OAAO,UAAW,OAAM,KAAK,GAAG,EAAE,KAAK;AAAA,MACzD;AAAA,IACF;AACA,cAAU;AAAA,EACZ;AACA,QAAM,cAAc,CAAC,SAAmB,MACtC,QAAQ;AAAA,IACN,CAAC,MAAM,KAAK,IAAI,EAAE,MAAM,EAAE,IAAI,IAAI,KAAK,IAAI,EAAE,MAAM,EAAE,IAAI,IAAI;AAAA,EAC/D;AACF,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,aAAa,MAAM,CAAC,EAAE,OAAO,MAAM,CAAC,EAAE;AAC5C,UAAM,WACJ,YAAY,QAAQ,MAAM,CAAC,EAAE,OAAO,MAAM,IAAI,CAAC,EAAE,QAAQ,aAAa;AACxE,UAAM,OACJ,YAAY,QACZ,UAAU,CAAC,EAAE,KAAK,CAAC,MAAM,YAAY,QAAS,SAAS,CAAC,EAAE,SAAS,CAAC;AACtE,QAAI,CAAC,YAAY,KAAM,OAAM;AAC7B,QAAI,CAAC,QAAS,WAAU,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC,GAAG,OAAO,MAAM;AAC9D,YAAQ,KAAK,KAAK,CAAC;AACnB,QAAI,UAAU,CAAC,EAAE,SAAS,EAAG,SAAQ,QAAQ;AAC7C,eAAW,KAAK,UAAU,CAAC,GAAG;AAC5B,YAAM,MAAM,YAAY,QAAQ,SAAS,CAAC,EAAE,CAAC;AAC7C,UAAI,KAAK;AACP,YAAI,OAAO,KAAK,IAAI,IAAI,MAAM,EAAE,IAAI;AACpC,YAAI,OAAO,KAAK,IAAI,IAAI,MAAM,EAAE,IAAI;AACpC,YAAI,UAAU,KAAK,CAAC;AAAA,MACtB;AACE,gBAAQ,QAAQ,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,EAAE,MAAM,WAAW,CAAC,CAAC,EAAE,CAAC;AAAA,IACvE;AAAA,EACF;AACA,QAAM;AACN,SAAO;AACT;AAGA,SAAS,WAAW,OAAoB,QAAwB;AAC9D,MAAI,MAAM;AACV,MAAI,OAAO,MAAM,UAAU,SAAS;AAGpC,SAAO,MAAM,MAAM;AACjB,UAAM,MAAM,KAAK,MAAM,MAAM,QAAQ,CAAC;AACtC,QAAI,MAAM,UAAU,GAAG,KAAK,OAAQ,OAAM;AAAA,QACrC,QAAO,MAAM;AAAA,EACpB;AACA,SAAO,MAAM,MAAM,UAAU,UAAU,MAAM,UAAU,GAAG,MAAM,GAAI;AACpE,SAAO;AACT;AAGA,SAAS,mBACP,OACA,IACA,KACS;AACT,QAAM,QAAQ,WAAW,OAAO,EAAE;AAClC,MAAI,SAAS,MAAM,UAAU,UAAU,MAAM,UAAU,KAAK,MAAM,IAAI;AACpE,WAAO;AAAA,EACT;AACA,MAAI,IAAI;AACR,SACE,IAAI,MAAM,UAAU,UACpB,MAAM,UAAU,CAAC,IAAI,MAAM,UAAU,CAAC,EAAE,SAAS,KACjD;AACA;AAAA,EACF;AACA,SACE,IAAI,MAAM,UAAU,UACpB,MAAM,UAAU,CAAC,IAAI,MAAM,UAAU,CAAC,EAAE,WAAW;AAEvD;AAOA,SAAS,QACP,OACA,IACA,KACkB;AAClB,QAAM,QAA0B,CAAC;AACjC,WAAS,IAAI,WAAW,OAAO,EAAE,GAAG,IAAI,MAAM,UAAU,QAAQ,KAAK;AACnE,UAAM,QAAQ,MAAM,UAAU,CAAC;AAC/B,QAAI,SAAS,IAAK;AAClB,QAAI,MAAM,UAAU,CAAC,MAAM,GAAI;AAC/B,UAAM,MAAM,MAAM,KAAK,CAAC;AACxB,UAAM,IAAI,MAAM,MAAM,IAAI,SAAS,EAAE,MAAM,IAAI,IAAI;AACnD,QAAI,OAAO,MAAM,MAAM,SAAS,CAAC;AACjC,QAAI,CAAC,QAAQ,KAAK,cAAc,IAAI,WAAW;AAC7C,aAAO;AAAA,QACL,WAAW,IAAI;AAAA,QACf,OAAO,CAAC;AAAA,QACR,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,MACR;AACA,YAAM,KAAK,IAAI;AAAA,IACjB;AACA,SAAK,MAAM,KAAK,IAAI,IAAI;AACxB,SAAK,OAAO,KAAK,IAAI,KAAK,MAAM,EAAE,IAAI;AACtC,SAAK,OAAO,KAAK,IAAI,KAAK,MAAM,EAAE,IAAI;AACtC,SAAK,OAAO,KAAK,IAAI,KAAK,MAAM,EAAE,IAAI;AACtC,SAAK,OAAO,KAAK,IAAI,KAAK,MAAM,EAAE,IAAI;AAAA,EACxC;AACA,SAAO;AACT;AAGA,SAAS,WACP,OACA,IACA,KACgB;AAChB,QAAM,QAAQ,QAAQ,OAAO,IAAI,GAAG;AACpC,SAAO;AAAA,IACL;AAAA,IACA,WAAW,MAAM,CAAC,EAAE;AAAA,IACpB,cAAc,MAAM,MAAM,SAAS,CAAC,EAAE;AAAA,IACtC;AAAA,EACF;AACF;AAGO,IAAM,gBAAgB;AAOtB,SAAS,gBACd,OACA,UACkB;AAClB,MAAI,SAAS,WAAW,KAAK,SAAS,CAAC,MAAM,GAAI,QAAO,CAAC;AACzD,QAAM,OAAyB,CAAC;AAChC,MAAI,aAAa;AACjB,aAAS;AACP,UAAM,KAAK,MAAM,OAAO,QAAQ,SAAS,CAAC,GAAG,UAAU;AACvD,QAAI,OAAO,GAAI;AACf,iBAAa,KAAK;AAClB,QAAI,MAAM,KAAK,SAAS,CAAC,EAAE;AAC3B,QAAI,WAAW;AACf,aAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,YAAM,OAAO,MAAM,OAAO,QAAQ,SAAS,CAAC,GAAG,GAAG;AAClD,UAAI,SAAS,MAAM,OAAO,MAAM,eAAe;AAC7C,mBAAW;AACX;AAAA,MACF;AACA,YAAM,OAAO,SAAS,CAAC,EAAE;AAAA,IAC3B;AACA,QAAI,CAAC,YAAY,CAAC,mBAAmB,OAAO,IAAI,GAAG,EAAG;AACtD,SAAK,KAAK,WAAW,OAAO,IAAI,GAAG,CAAC;AACpC,iBAAa;AAAA,EACf;AACA,SAAO;AACT;AAgCO,IAAM,oBAAoB;AAE1B,IAAM,qBAAqB;AAE3B,IAAM,cAAc;AASpB,SAAS,sBACd,OACA,QAC4C;AAC5C,MAAI,OAAO,SAAS,mBAAoB,QAAO;AAC/C,QAAM,SAAS,OAAO,MAAM,GAAG,kBAAkB;AACjD,MAAI;AACJ,MAAI,aAAa;AACjB,aAAS;AACP,UAAM,KAAK,MAAM,OAAO,QAAQ,QAAQ,UAAU;AAClD,QAAI,OAAO,GAAI;AACf,iBAAa,KAAK;AAClB,UAAM,QAAQ,WAAW,OAAO,EAAE;AAClC,QAAI,SAAS,MAAM,UAAU,UAAU,MAAM,UAAU,KAAK,MAAM,IAAI;AACpE;AAAA,IACF;AACA,QAAI,SAAS;AACb,WACE,SAAS,OAAO,UAChB,MAAM,OAAO,WAAW,KAAK,MAAM,MAAM,OAAO,WAAW,MAAM,GACjE;AACA;AAAA,IACF;AACA,QAAI,CAAC,QAAQ,SAAS,KAAK,OAAQ,QAAO,EAAE,QAAQ,GAAG;AAAA,EACzD;AACA,SAAO;AACT;AAGA,SAAS,QACP,GACA,GACS;AACT,QAAM,UAAU,KAAK,IAAI,EAAE,MAAM,EAAE,IAAI,IAAI,KAAK,IAAI,EAAE,MAAM,EAAE,IAAI;AAClE,QAAM,UAAU,KAAK,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI;AACzD,SAAO,UAAU,KAAK,UAAU,UAAU;AAC5C;AAGA,SAAS,aACP,MACA,KACS;AACT,SACE,IAAI,QAAQ,KAAK,WAAW,eAC5B,IAAI,QAAQ,KAAK,YAAY,IAAI;AAErC;AAEA,IAAM,kBAAkB;AAGxB,IAAM,0BAA0B;AAahC,SAAS,kBAEP,aAA2B,OAAoC;AAC/D,QAAM,iBAAiB,oBAAI,IAAe;AAC1C,QAAM,eAAe,CAAC,MACpB,KAAK,IAAI,EAAE,MAAM,CAAC,EAAE,MAAM,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,CAAC,EAAE,IAAI;AACzE,aAAW,KAAK,aAAa;AAC3B,UAAM,OAAO,eAAe,IAAI,EAAE,SAAS;AAC3C,QAAI,CAAC,QAAQ,aAAa,CAAC,IAAI,aAAa,IAAI,GAAG;AACjD,qBAAe,IAAI,EAAE,WAAW,CAAC;AAAA,IACnC;AAAA,EACF;AACA,QAAM,UAAU,CAAC,GAAG,eAAe,OAAO,CAAC;AAC3C,MAAI,QAAQ,SAAS,EAAG,QAAO;AAC/B,SAAO,QAAQ;AAAA,IAAO,CAAC,MACrB,QAAQ;AAAA,MACN,CAAC,UACC,MAAM,cAAc,EAAE,aACtB,KAAK,IAAI,MAAM,MAAM,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,IAAI,KAC5C;AAAA,IACN;AAAA,EACF;AACF;AAoBO,SAAS,gBACd,OACA,WACwB;AACxB,QAAM,UAAU,oBAAI,IAA0B;AAC9C,QAAM,cAAc,oBAAI,IAAY;AACpC,QAAM,WAAW,CACf,WACA,QACS;AACT,UAAM,SAAS,EAAE,MAAM,QAAQ,CAAC,GAAG,MAAM;AACvC,UAAI,QAAQ,GAAG,GAAG,EAAG,aAAY,IAAI,GAAG,SAAS,IAAI,CAAC,EAAE;AAAA,IAC1D,CAAC;AAAA,EACH;AAEA,QAAM,YAAY,UAAU,OAAO,CAAC,UAAU,MAAM,OAAO;AAC3D,MAAI,UAAU,SAAS,GAAG;AACxB,UAAM,OAAO,cAAc,KAAK;AAChC,eAAW,SAAS,WAAW;AAC7B,YAAM,WAAW,eAAe,MAAM,IAAI;AAC1C,YAAM,SAAS,SAAS,KAAK,EAAE;AAC/B,UAAI,OAAO,SAAS,mBAAmB;AACrC,gBAAQ,IAAI,OAAO;AAAA,UACjB;AAAA,UACA;AAAA,UACA,QAAQ;AAAA,UACR,aAAa,CAAC;AAAA,QAChB,CAAC;AACD;AAAA,MACF;AACA,YAAM,SAAS,gBAAgB,MAAM,QAAQ,EAAE;AAAA,QAC7C,CAAC,MACC,EAAE,cAAc,EAAE,gBAClB,aAAa,MAAM,EAAE,SAAS,GAAG,EAAE,MAAM,CAAC,CAAC;AAAA,MAC/C;AACA,YAAM,cAAc,kBAAkB,QAAQ,KAAK;AACnD,iBAAW,KAAK,YAAa,UAAS,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC;AAC7D,cAAQ,IAAI,OAAO;AAAA,QACjB;AAAA,QACA;AAAA,QACA,QAAQ,YAAY,SAAS,IAAI,WAAW;AAAA,QAC5C;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAGA,QAAM,QAAQ,CAAC,MAAM,cAAc;AACjC,SAAK,MAAM,QAAQ,CAAC,MAAM,MAAM;AAC9B,UAAI,YAAY,IAAI,GAAG,SAAS,IAAI,CAAC,EAAE,EAAG;AAC1C,UAAI,CAAC,aAAa,MAAM,IAAI,EAAG;AAC/B,YAAM,MAAM,KAAK,MAAM,OAAO,CAAC,MAAM,QAAQ,GAAG,IAAI,CAAC;AACrD,UAAI,IAAI,MAAM,CAAC,MAAM,gBAAgB,KAAK,EAAE,IAAI,CAAC,GAAG;AAClD,iBAAS,WAAW,IAAI;AAAA,MAC1B;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AAED,QAAM,OAAO,cAAc,OAAO,WAAW;AAE7C,QAAM,UAAU,oBAAI,IAAe;AACnC,MAAI,SAAS;AACb,aAAW,SAAS,WAAW;AAC7B,QAAI,MAAM,QAAS;AACnB,UAAM,WAAW,eAAe,MAAM,IAAI;AAC1C,UAAM,SAAS,SAAS,KAAK,EAAE;AAC/B,QAAI,OAAO,SAAS,mBAAmB;AACrC,cAAQ,IAAI,OAAO,EAAE,OAAO,QAAQ,QAAQ,WAAW,aAAa,CAAC,EAAE,CAAC;AACxE;AAAA,IACF;AACA,QAAI,MAAM,gBAAgB,MAAM,QAAQ;AACxC,QAAI;AAGJ,QAAI,IAAI,WAAW,KAAK,SAAS,WAAW,KAAK,CAAC,MAAM,UAAU;AAChE,YAAM,SAAS,sBAAsB,MAAM,MAAM;AACjD,UAAI,QAAQ;AACV,cAAM,CAAC,WAAW,MAAM,OAAO,IAAI,OAAO,KAAK,OAAO,MAAM,CAAC;AAC7D,kBAAU,EAAE,cAAc,OAAO,QAAQ,YAAY,OAAO,OAAO;AAAA,MACrE;AAAA,IACF;AACA,QAAI,IAAI,WAAW,GAAG;AACpB,cAAQ,IAAI,OAAO;AAAA,QACjB;AAAA,QACA;AAAA,QACA,QAAQ,MAAM,WAAW,YAAY;AAAA,QACrC,aAAa,CAAC;AAAA,MAChB,CAAC;AACD;AAAA,IACF;AACA,QAAI,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;AAC/C,QAAI,KAAK,WAAW,KAAK,CAAC,MAAM,UAAU;AAKxC,YAAM,OAAO,IAAI,KAAK,CAAC,MAAM,QAAQ,IAAI,EAAE,EAAE,GAAG,QAAQ;AACxD,UAAI,MAAM;AACR,cAAM,SAAS,QAAQ,IAAI,KAAK,EAAE;AAClC,gBAAQ,OAAO,KAAK,EAAE;AACtB,gBAAQ,IAAI,QAAQ;AAAA,UAClB,OAAO;AAAA,UACP,QAAQ,QAAQ,IAAI,MAAM,GAAG,UAAU;AAAA,UACvC,QAAQ;AAAA,UACR,aAAa,CAAC;AAAA,QAChB,CAAC;AACD,eAAO,CAAC,IAAI;AAAA,MACd;AAAA,IACF;AACA,QAAI,KAAK,WAAW,GAAG;AACrB,cAAQ,IAAI,OAAO;AAAA,QACjB;AAAA,QACA;AAAA,QACA,QAAQ;AAAA,QACR,aAAa;AAAA,MACf,CAAC;AACD;AAAA,IACF;AACA,UAAM,SAAS,KAAK,KAAK,CAAC,MAAM,EAAE,MAAM,MAAM,KAAK,KAAK,CAAC;AACzD,YAAQ,IAAI,OAAO,IAAI,KAAK;AAC5B,aAAS,OAAO;AAChB,YAAQ,IAAI,OAAO;AAAA,MACjB;AAAA,MACA;AAAA,MACA,QAAQ;AAAA,MACR,aAAa,CAAC,MAAM;AAAA,MACpB,GAAI,WAAW,EAAE,QAAQ;AAAA,IAC3B,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL,SAAS,UAAU,IAAI,CAAC,UAAU,QAAQ,IAAI,KAAK,CAAsB;AAAA,IACzE;AAAA,EACF;AACF;;;AFxkBO,IAAM,oBAAoB;AAyD1B,SAAS,uBACd,MAC8B;AAC9B,SAAO,KAAK,SAAS;AACvB;AAEA,IAAM,SAAS,oBAAI,QAA6C;AAGzD,SAAS,iBAAiB,MAA2C;AAC1E,MAAI,QAAQ,OAAO,IAAI,IAAI;AAC3B,MAAI,CAAC,OAAO;AACV,YAAQ,sBAAsB,KAAK,KAAK;AACxC,WAAO,IAAI,MAAM,KAAK;AAAA,EACxB;AACA,SAAO;AACT;AAGO,IAAM,mBAAmB;AAEhC,IAAM,mBAAmB;AAqBzB,SAAS,QAAQ,OAA2C;AAC1D,QAAM,EAAE,SAAS,MAAM,SAAS,GAAG,KAAK,IAAI;AAC5C,SAAO;AAAA,IACL,GAAG;AAAA,IACH,SAAS;AAAA,MACP,GAAG;AAAA,MACH;AAAA,MACA,GAAI,SAAS,UAAa,EAAE,KAAK;AAAA,IACnC;AAAA,EACF;AACF;AAEA,SAAS,MAAM,OAAuB;AACpC,SAAO,KAAK,MAAM,QAAQ,EAAE,IAAI;AAClC;AAGA,SAAS,WAAW,GAAgB,GAAwB;AAC1D,QAAM,IAAI,KAAK,IAAI,EAAE,MAAM,EAAE,IAAI,IAAI,KAAK,IAAI,EAAE,MAAM,EAAE,IAAI;AAC5D,QAAM,IAAI,KAAK,IAAI,EAAE,MAAM,EAAE,IAAI,IAAI,KAAK,IAAI,EAAE,MAAM,EAAE,IAAI;AAC5D,SAAO,IAAI,KAAK,IAAI,IAAI,IAAI,IAAI;AAClC;AAEA,SAAS,KAAK,GAAwB;AACpC,SAAO,KAAK,IAAI,GAAG,EAAE,OAAO,EAAE,IAAI,IAAI,KAAK,IAAI,GAAG,EAAE,OAAO,EAAE,IAAI;AACnE;AAGA,SAAS,QAAQ,MAAc,MAAM,IAAY;AAC/C,QAAM,OAAO,KAAK,QAAQ,QAAQ,GAAG,EAAE,KAAK;AAC5C,SAAO,KAAK,SAAS,MAAM,GAAG,KAAK,MAAM,GAAG,MAAM,CAAC,CAAC,WAAM;AAC5D;AAGA,SAAS,WACP,SACgD;AAChD,QAAM,SAAS,oBAAI,IAA+C;AAClE,aAAW,SAAS,SAAS;AAC3B,QAAI,MAAM,WAAW,SAAU;AAC/B,eAAW,KAAK,MAAM,aAAa;AACjC,iBAAW,QAAQ,EAAE,OAAO;AAC1B,mBAAW,KAAK,KAAK,MAAO,QAAO,IAAI,GAAG,KAAK,SAAS,IAAI,CAAC,IAAI,KAAK;AAAA,MACxE;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAGA,IAAM,WAAW,oBAAI,QAA0C;AAC/D,SAAS,WAAW,MAAwC;AAC1D,QAAMC,UAAS,SAAS,IAAI,IAAI;AAChC,MAAIA,QAAQ,QAAOA;AACnB,QAAM,QAAQ,oBAAI,IAAoB;AACtC,OAAK,MAAM,QAAQ,CAAC,MAAM,UAAU;AAClC,eAAW,KAAK,KAAK,MAAO,OAAM,IAAI,GAAG,KAAK;AAAA,EAChD,CAAC;AACD,WAAS,IAAI,MAAM,KAAK;AACxB,SAAO;AACT;AAGA,SAAS,SAAS,OAAkC;AAClD,SAAO,MAAM;AAAA,IACX,CAAC,KAAK,UAAU;AAAA,MACd,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,IAAI;AAAA,MAClC,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,IAAI;AAAA,MAClC,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,IAAI;AAAA,MAClC,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,IAAI;AAAA,IACpC;AAAA,IACA,EAAE,MAAM,UAAU,MAAM,UAAU,MAAM,WAAW,MAAM,UAAU;AAAA,EACrE;AACF;AAOA,SAAS,sBAAsB,OAA6C;AAC1E,QAAM,EAAE,OAAO,OAAO,IAAI;AAC1B,QAAM,EAAE,SAAS,YAAY,IAAI,gBAAgB,OAAO,MAAM,SAAS;AACvE,QAAM,SAAS,WAAW,OAAO;AACjC,QAAM,WAAmC,CAAC;AAE1C,QAAM,UAAU,CAAC,WAAmB,SAClC,OAAO,IAAI,GAAG,SAAS,IAAI,IAAI,EAAE;AAInC,QAAM,UAAU,oBAAI,IAQlB;AACF,QAAM,QAAQ,CAAC,MAAM,cAAc;AACjC,SAAK,MAAM,QAAQ,CAAC,MAAM,UAAU;AAClC,YAAM,SAAS,KAAK;AAAA,QAClB,CAAC,KAAK;AAAA,QACN,KAAK,OAAO,KAAK;AAAA,QACjB,CAAC,KAAK;AAAA,QACN,KAAK,OAAO,KAAK;AAAA,MACnB;AACA,UAAI,UAAU,iBAAkB;AAChC,YAAM,QAAQ,QAAQ,WAAW,KAAK;AACtC,YAAM,MAAM,QAAQ,SAAS,MAAM,MAAM,IAAI,KAAK,QAAQ,SAAS;AACnE,YAAM,SAAS,QAAQ,IAAI,GAAG,KAAK;AAAA,QACjC,OAAO;AAAA,QACP,OAAO,CAAC;AAAA,QACR,MAAM,YAAY;AAAA,QAClB,OAAO;AAAA,MACT;AACA,aAAO,MAAM,KAAK,IAAI;AACtB,aAAO,QAAQ,KAAK,IAAI,OAAO,OAAO,MAAM;AAC5C,cAAQ,IAAI,KAAK,MAAM;AAAA,IACzB,CAAC;AAAA,EACH,CAAC;AACD,aAAW,UAAU,QAAQ,OAAO,GAAG;AACrC,UAAM,SAAS,QAAQ,OAAO,MAAM,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,GAAG,CAAC;AAChE,aAAS;AAAA,MACP,QAAQ;AAAA,QACN,QAAQ;AAAA,QACR,SAAS,OAAO,QAAQ,WAAW;AAAA,QACnC,MAAM,OAAO;AAAA,QACb,MAAM,OAAO,OAAO,MAAM,QAAQ;AAAA,QAClC,SAAS,GAAG,OAAO,MAAM,MAAM,QAAQ,OAAO,MAAM,WAAW,IAAI,KAAK,GAAG,wCAAwC,OAAO,IAAI,aAAa,MAAM,OAAO,KAAK,CAAC,SAAS,MAAM;AAAA,QAC7K,YACE;AAAA,QACF,UAAU;AAAA,UACR,SAAS;AAAA,UACT,QAAQ,MAAM,OAAO,KAAK;AAAA,UAC1B,UAAU;AAAA,UACV,MAAM;AAAA,UACN,QAAQ,EAAE,OAAO,OAAO,MAAM,OAAO;AAAA,QACvC;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAIA,aAAW,SAAS,SAAS;AAC3B,QAAI,CAAC,MAAM,WAAW,MAAM,WAAW,SAAU;AACjD,UAAM,IAAI,MAAM,YAAY,CAAC;AAC7B,UAAM,QAAQ,KAAK;AAAA,MAChB,MAAM,MAAM,QAAQ,eAAgB,MAAM,QAAQ;AAAA,IACrD;AACA,aAAS;AAAA,MACP,QAAQ;AAAA,QACN,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,MAAM,EAAE,eAAe;AAAA,QACvB,MAAM,MAAM,MAAM;AAAA,QAClB,SAAS,IAAI,QAAQ,MAAM,MAAM,IAAI,CAAC,uBAAuB,KAAK,4BAA4B,EAAE,eAAe,CAAC;AAAA,QAChH,YACE;AAAA,QACF,UAAU;AAAA,UACR,SAAS;AAAA,UACT,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,MAAM;AAAA,UACN,QAAQ,MAAM;AAAA,QAChB;AAAA,QACA,SAAS,EAAE,MAAM,YAAY;AAAA,MAC/B,CAAC;AAAA,IACH;AAAA,EACF;AAGA,aAAW,SAAS,SAAS;AAC3B,UAAM,MAAM,MAAM,MAAM;AACxB,QAAI,CAAC,OAAO,MAAM,WAAW,SAAU;AACvC,eAAW,KAAK,MAAM,aAAa;AACjC,YAAM,SAAS,SAAS,EAAE,KAAK;AAC/B,YAAM,WAAW;AAAA,QACf,UAAU,OAAO,OAAO,OAAO;AAAA,QAC/B,SAAS,OAAO,OAAO,OAAO;AAAA,MAChC;AACA,YAAM,QAAS,CAAC,YAAY,SAAS,EAClC,OAAO,CAAC,SAAS,IAAI,IAAI,MAAM,MAAS,EACxC,IAAI,CAAC,UAAU;AAAA,QACd;AAAA,QACA,UAAU,IAAI,IAAI;AAAA,QAClB,UAAU,SAAS,IAAI;AAAA,QACvB,MAAM,SAAS,IAAI,IAAK,IAAI,IAAI;AAAA,MAClC,EAAE,EACD,KAAK,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AACpC,UAAI,CAAC,SAAS,MAAM,QAAQ,iBAAkB;AAC9C,YAAM,OAAO,MAAM,SAAS,aAAa,WAAW;AACpD,eAAS;AAAA,QACP,QAAQ;AAAA,UACN,QAAQ;AAAA,UACR,SAAS;AAAA,UACT,MAAM,EAAE,YAAY;AAAA,UACpB,MAAM,MAAM,MAAM;AAAA,UAClB,SAAS,IAAI,QAAQ,MAAM,MAAM,IAAI,CAAC,cAAc,MAAM,MAAM,IAAI,CAAC,OAAO,IAAI,aAAa,MAAM,MAAM,QAAQ,CAAC,mBAAmB,EAAE,YAAY,CAAC;AAAA,UACpJ,YACE;AAAA,UACF,UAAU;AAAA,YACR,SAAS;AAAA,YACT,QAAQ,MAAM,MAAM,QAAQ;AAAA,YAC5B,UAAU,MAAM,MAAM,QAAQ;AAAA,YAC9B,MAAM;AAAA,YACN,QAAQ,EAAE,UAAU,MAAM,CAAC,MAAM,IAAI,EAAE;AAAA,UACzC;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAGA,QAAM,QAAQ,CAAC,MAAM,cAAc;AACjC,UAAM,QAAQ,WAAW,IAAI;AAC7B,UAAM,OAAO,oBAAI,IAAY;AAC7B,UAAMC,SAAQ,KAAK;AAGnB,UAAM,QAAQA,OACX,IAAI,CAAC,GAAG,MAAM,CAAC,EACf,KAAK,CAAC,GAAG,MAAMA,OAAM,CAAC,EAAE,OAAOA,OAAM,CAAC,EAAE,IAAI;AAC/C,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,YAAM,IAAI,MAAM,CAAC;AACjB,YAAM,IAAIA,OAAM,CAAC;AACjB,eAAS,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACzC,cAAM,IAAI,MAAM,CAAC;AACjB,cAAM,IAAIA,OAAM,CAAC;AACjB,YAAI,EAAE,QAAQ,EAAE,KAAM;AACtB,cAAM,SAAS,WAAW,GAAG,CAAC;AAC9B,YAAI,UAAU,EAAG;AACjB,cAAM,QAAQ,MAAM,IAAI,CAAC;AACzB,YAAI,UAAU,UAAa,UAAU,MAAM,IAAI,CAAC,EAAG;AACnD,cAAM,UAAU,KAAK,IAAI,KAAK,CAAC,GAAG,KAAK,CAAC,CAAC;AACzC,YAAI,WAAW,KAAK,SAAS,UAAU,iBAAkB;AACzD,cAAM,SAAS,QAAQ,WAAW,CAAC;AACnC,cAAM,SAAS,QAAQ,WAAW,CAAC;AACnC,cAAM,QAAQ,QAAQ,MAAM,QAAQ;AACpC,cAAM,QAAQ,QAAQ,MAAM,QAAQ;AAEpC,cAAM,MACJ,SAAS,QACL,CAAC,OAAO,KAAK,EAAE,KAAK,EAAE,KAAK,GAAG,IAC9B,GAAG,KAAK,IAAI,GAAG,CAAC,CAAC,IAAI,KAAK,IAAI,GAAG,CAAC,CAAC;AACzC,YAAI,KAAK,IAAI,GAAG,EAAG;AACnB,aAAK,IAAI,GAAG;AACZ,cAAM,UACJ,UAAU,SACN,WACA,UAAU,SACR,cACA;AACR,iBAAS;AAAA,UACP,QAAQ;AAAA,YACN,QAAQ;AAAA,YACR;AAAA,YACA,MAAM,YAAY;AAAA,YAClB,MAAM,SAAS;AAAA,YACf,GAAI,SAAS,SAAS,UAAU,SAAS,EAAE,cAAc,CAAC,KAAK,EAAE;AAAA,YACjE,SAAS,IAAI,EAAE,IAAI,UAAU,EAAE,IAAI,uCAAuC,YAAY,CAAC;AAAA,YACvF,YACE;AAAA,YACF,UAAU;AAAA,cACR,SAAS;AAAA,cACT,QAAQ,MAAO,MAAM,SAAU,OAAO;AAAA,cACtC,UAAU;AAAA,cACV,MAAM;AAAA,YACR;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AAGD,aAAW,SAAS,SAAS;AAC3B,QAAI,MAAM,WAAW,UAAW;AAChC,aAAS;AAAA,MACP,QAAQ;AAAA,QACN,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,MAAM,MAAM,MAAM;AAAA,QAClB,SAAS,IAAI,QAAQ,MAAM,MAAM,IAAI,CAAC;AAAA,QACtC,YACE;AAAA,QACF,UAAU,EAAE,SAAS,8CAA8C;AAAA,QACnE,SAAS,EAAE,MAAM,MAAM,MAAM,KAAK;AAAA,MACpC,CAAC;AAAA,IACH;AAAA,EACF;AAGA,MAAI,cAAc;AAClB,MAAI,MAAM,SAAS,MAAM,MAAM,SAAS,KAAK,MAAM,gBAAgB;AACjE,UAAM,OAAO,oBAAI,IAAY;AAC7B,eAAW,aAAa,MAAM,gBAAgB;AAC5C,YAAM,SAAS,UAAU,OAAO,KAAK;AACrC,UAAI,WAAW,MAAM,KAAK,IAAI,OAAO,YAAY,CAAC,EAAG;AACrD,WAAK,IAAI,OAAO,YAAY,CAAC;AAC7B,UAAI,eAAe,QAAQ,MAAM,KAAK,EAAG;AACzC,qBAAe;AACf,eAAS;AAAA,QACP,QAAQ;AAAA,UACN,QAAQ;AAAA,UACR,UAAU,UAAU,WAAW,YAAY;AAAA,UAC3C,SAAS,UAAU,OAAO,WAAW;AAAA,UACrC,MAAM,UAAU,QAAQ;AAAA,UACxB,SAAS,UAAU,WACf,IAAI,MAAM,qHACV,IAAI,MAAM;AAAA,UACd,YAAY,UAAU,WAClB,yIACA;AAAA,UACJ,SAAS,EAAE,UAAU,UAAU,aAAa,KAAK;AAAA,UACjD,UAAU;AAAA,YACR,SAAS;AAAA,YACT,UAAU;AAAA,YACV,QAAQ,CAAC,GAAG,IAAI,IAAI,MAAM,MAAM,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,EAAE,KAAK,IAAI;AAAA,UACpE;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAOA,QAAM,QAAQ,CAAC,MAAM,cAAc;AACjC,UAAM,UACJ,WAAW,SACP,KAAK,MAAM,KAAK,CAAC,GAAG,MAAM,CAAC,YAAY,IAAI,GAAG,SAAS,IAAI,CAAC,EAAE,CAAC,IAC/D,KAAK,MAAM,SAAS;AAC1B,QAAI,QAAS;AACb,UAAM,OAAO,KAAK,MAAM,SAAS,IAAI,gBAAgB;AACrD,aAAS;AAAA,MACP,QAAQ;AAAA,QACN,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,MAAM,YAAY;AAAA,QAClB,MAAM;AAAA,QACN,SAAS,QAAQ,YAAY,CAAC,YAAY,SAAS,gBAAgB,oCAAoC,SAAS;AAAA,QAChH,YACE;AAAA,QACF,SAAS,EAAE,KAAK;AAAA,MAClB,CAAC;AAAA,IACH;AAAA,EACF,CAAC;AAMD,MAAI,WAAW,QAAQ;AACrB,UAAM,QAAQ,CAAC,MAAM,cAAc;AACjC,UAAI,CAAC,KAAK,OAAO,cAAc,KAAK,cAAc,MAAM,SAAS,GAAG;AAClE;AAAA,MACF;AACA,YAAM,QAAQ,KAAK,WAAW,KAAK,IAAI;AACvC,UAAI,eAAe;AACnB,UAAI,YAAY,KAAK;AACrB,WAAK,MAAM,QAAQ,CAAC,GAAG,MAAM;AAC3B,YAAI,CAAC,YAAY,IAAI,GAAG,SAAS,IAAI,CAAC,EAAE,EAAG;AAC3C,YAAI,EAAE,QAAQ,KAAK,WAAW,aAAa;AACzC,yBAAe,KAAK,IAAI,cAAc,EAAE,IAAI;AAAA,QAC9C,WAAW,EAAE,QAAQ,KAAK,YAAY,IAAI,cAAc;AACtD,sBAAY,KAAK,IAAI,WAAW,EAAE,IAAI;AAAA,QACxC;AAAA,MACF,CAAC;AAED,YAAM,OAAO,KAAK,IAAI,MAAM;AAAA,QAC1B,CAAC,QACC,MAAM,QAAQ,eAAe,UAAU,MAAM,KAAK,QAAQ;AAAA,MAC9D;AACA,UAAI,KAAK,WAAW,EAAG;AACvB,YAAM,MAAM,KAAK,CAAC,IAAI;AACtB,YAAM,UAAU,KAAK,KAAK,SAAS,CAAC,IAAI,KAAK;AAC7C,YAAM,OAAO,KAAK,OAAQ,SAAS,QAAQ,YAAY,OAAQ,GAAG,IAAI;AACtE,UAAI,QAAQ,kBAAmB;AAG/B,UAAI;AACJ,iBAAW,SAAS,SAAS;AAC3B,YAAI,MAAM,WAAW,YAAY,MAAM,MAAM,QAAS;AACtD,mBAAW,KAAK,MAAM,aAAa;AAGjC,qBAAW,QAAQ,EAAE,OAAO;AAC1B,gBAAI,KAAK,cAAc,UAAW;AAClC,gBAAI,CAAC,SAAS,KAAK,OAAO,MAAM,MAAM;AACpC,sBAAQ,EAAE,MAAM,MAAM,MAAM,MAAM,MAAM,KAAK,KAAK;AAAA,YACpD;AAAA,UACF;AAAA,QACF;AAAA,MACF;AACA,YAAM,UAAU,QACZ,mBAAmB,KAAK,MAAM,IAAI,IAAI,CAAC,IACvC;AACJ,eAAS;AAAA,QACP,QAAQ;AAAA,UACN,QAAQ;AAAA,UACR,SAAS,UAAU,WAAW;AAAA,UAC9B,MAAM,YAAY;AAAA,UAClB,MAAM,WAAW;AAAA,UACjB,SAAS,QAAQ,YAAY,CAAC,OAAO,KAAK,MAAM,OAAO,GAAG,CAAC,+BAA+B,KAAK,MAAM,YAAY,MAAM,CAAC;AAAA,UACxH,YACE;AAAA,UACF,UAAU;AAAA,YACR,SAAS;AAAA,YACT,UAAU,UAAK,KAAK,MAAM,oBAAoB,GAAG,CAAC;AAAA,YAClD,QAAQ;AAAA,UACV;AAAA,UACA,SAAS,EAAE,KAAK;AAAA,QAClB,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAAA,EACH;AAGA,MAAI,WAAW,QAAQ;AACrB,eAAW,SAAS,SAAS;AAC3B,UAAI,MAAM,MAAM,SAAS,aAAa,MAAM,WAAW,SAAU;AACjE,YAAM,IAAI,MAAM,YAAY,CAAC;AAC7B,YAAM,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACvC,UAAI,KAAK,aAAa,MAAM,SAAS,EAAG;AACxC,YAAM,OAAO,MAAM,KAAK,SAAS;AACjC,YAAM,QAAQ,KAAK,MAAM;AAAA,QACvB,CAAC,GAAG,MACF,CAAC,YAAY,IAAI,GAAG,KAAK,SAAS,IAAI,CAAC,EAAE,KACzC,CAAC,KAAK,MAAM,SAAS,CAAC,KACtB,EAAE,QAAQ,KAAK,OAAO;AAAA,MAC1B;AACA,UAAI,MAAO;AACX,eAAS;AAAA,QACP,QAAQ;AAAA,UACN,QAAQ;AAAA,UACR,SAAS;AAAA,UACT,MAAM,KAAK,YAAY;AAAA,UACvB,MAAM,MAAM,MAAM;AAAA,UAClB,SAAS,YAAY,QAAQ,MAAM,MAAM,IAAI,CAAC,8BAA8B,KAAK,YAAY,CAAC;AAAA,UAC9F,YACE;AAAA,QACJ,CAAC;AAAA,MACH;AAAA,IACF;AAGA,eAAW,SAAS,SAAS;AAC3B,YAAM,OAAO,MAAM,MAAM;AACzB,UACG,SAAS,UAAU,SAAS,eAC7B,MAAM,WAAW;AAEjB;AACF,YAAM,IAAI,MAAM,YAAY,CAAC;AAC7B,UAAI,EAAE,cAAc,EAAE,aAAc;AACpC,YAAM,QAAQ,EAAE,MAAM,CAAC;AACvB,YAAM,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACvC,YAAM,UAAU,CAAC,SAAyB;AACxC,cAAM,QAAQ,WAAW,MAAM,KAAK,SAAS,CAAC;AAC9C,cAAM,MAAM,oBAAI,IAAY;AAC5B,mBAAW,KAAK,KAAK,OAAO;AAC1B,gBAAM,OAAO,MAAM,IAAI,CAAC;AACxB,cAAI,SAAS,OAAW,KAAI,IAAI,IAAI;AAAA,QACtC;AACA,eAAO,IAAI;AAAA,MACb;AACA,YAAM,SAAS,QAAQ,KAAK;AAC5B,YAAM,QAAQ,QAAQ,IAAI;AAC1B,UAAI,WAAW,KAAK,UAAU,EAAG;AACjC,UAAI,SAAS,KAAK,QAAQ,EAAG;AAC7B,YAAM,QAAQ,WAAW,IAAI,WAAW;AACxC,eAAS;AAAA,QACP,QAAQ;AAAA,UACN,QAAQ;AAAA,UACR,SAAS;AAAA,UACT,OAAO,UAAU,WAAW,MAAM,YAAY,KAAK,aAAa;AAAA,UAChE,MAAM,MAAM,MAAM;AAAA,UAClB,SACE,UAAU,WACN,IAAI,QAAQ,MAAM,MAAM,IAAI,CAAC,+CAA+C,MAAM,YAAY,CAAC,MAC/F,IAAI,QAAQ,MAAM,MAAM,IAAI,CAAC,8CAA8C,KAAK,YAAY,CAAC;AAAA,UACnG,YACE;AAAA,UACF,UAAU;AAAA,YACR,SAAS;AAAA,YACT,QAAQ,EAAE,aAAa,QAAQ,YAAY,MAAM;AAAA,UACnD;AAAA,UACA,SAAS,EAAE,MAAM,MAAM;AAAA,QACzB,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAEA,QAAM,YAA2C;AAAA,IAC/C,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,SAAS;AAAA,IACT,SAAS;AAAA,EACX;AACA,aAAW,SAAS,QAAS,WAAU,MAAM,MAAM,KAAK;AAExD,QAAM,SAAS,oBAAI,IAA4C;AAC/D,aAAW,SAAS,UAAU;AAC5B,UAAM,SAAS,OAAO,IAAI,MAAM,MAAM;AACtC,QAAI,OAAQ,QAAO,KAAK,KAAK;AAAA,QACxB,QAAO,IAAI,MAAM,QAAQ,CAAC,KAAK,CAAC;AAAA,EACvC;AAEA,SAAO;AAAA,IACL,QAAQ;AAAA,IACR;AAAA,IACA;AAAA;AAAA;AAAA,IAGA,GAAI,MAAM,SACR,MAAM,MAAM,SAAS,KACrB,MAAM,kBAAkB;AAAA,MACtB,OAAO,EAAE,WAAW,MAAM,eAAe,QAAQ,YAAY;AAAA,IAC/D;AAAA,EACJ;AACF;AAEA,IAAI;AAGJ,SAAS,iBAAgC;AACvC,aAAW,IAAI,cAAc,uBAAuB,KAAK;AACzD,SAAO;AACT;AAQO,SAAS,wBACd,OACA,UAAiC,CAAC,GAChB;AAClB,QAAM,OAA6B;AAAA,IACjC,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN;AAAA,EACF;AACA,QAAM,WAAW,eAAe,EAAE;AAAA,IAChC;AAAA,MACE,QAAQ,MAAM;AAAA,MACd,OAAO;AAAA,MACP,OAAO,CAAC,IAAI;AAAA,MACZ,YAAY,CAAC;AAAA,MACb,GAAI,MAAM,aAAa,UAAa,EAAE,UAAU,MAAM,SAAS;AAAA,IACjE;AAAA,IACA;AAAA,EACF;AAGA,MAAI;AACJ,MAAI;AACF,YAAQ,iBAAiB,IAAI;AAAA,EAC/B,QAAQ;AACN,YAAQ;AAAA,EACV;AACA,QAAM,YAA6C;AAAA,IACjD,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,UAAU;AAAA,EACZ;AACA,aAAW,KAAK,SAAS,aAAa;AACpC,cAAW,EAAE,SAAS,WAA+B,UAAU,KAAK;AAAA,EACtE;AACA,SAAO;AAAA,IACL,UAAU,SAAS;AAAA,IACnB,SAAS;AAAA,MACP,OAAO,MAAM,MAAM;AAAA,MACnB,OAAO,MAAM,MAAM,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,MAAM,QAAQ,CAAC;AAAA,MACzD,WAAW,OAAO,aAAa;AAAA,QAC7B,QAAQ;AAAA,QACR,WAAW;AAAA,QACX,SAAS;AAAA,QACT,SAAS;AAAA,MACX;AAAA,MACA,UAAU;AAAA,MACV,GAAI,OAAO,SAAS,EAAE,OAAO,MAAM,MAAM;AAAA,MACzC,YAAY,SAAS;AAAA,MACrB,SAAS,SAAS;AAAA,MAClB,WAAW,SAAS;AAAA,MACpB,GAAI,SAAS,cAAc,UAAa;AAAA,QACtC,WAAW,SAAS;AAAA,MACtB;AAAA,IACF;AAAA,IACA;AAAA,EACF;AACF;;;AGrwBA;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AA4DA,IAAM,iBAAiB;AAAA,EAC5B,SAAS,EAAE,QAAQ,cAAc;AAAA,EACjC,MAAM,EAAE,QAAQ,cAAc;AAChC;AAGO,IAAM,eACX;AAMF,IAAM,QAAQ;AAAA,EACZ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AASA,SAAS,MAAM,OAAe,MAAc,UAA2B;AACrE,MAAI,SAAS,EAAG,QAAO;AACvB,QAAM,MAAgB,CAAC,OAAO,IAAI,EAAE;AACpC,WAAS,IAAI,GAAG,IAAI,OAAO,KAAK,EAAG,KAAI,KAAK,OAAO,OAAO,KAAK,MAAM,MAAM,CAAC;AAC5E,SAAO,WAAW,GAAG,IAAI,KAAK,GAAG,CAAC,MAAM,IAAI,KAAK,GAAG;AACtD;AAMO,SAAS,aAAa,QAAgB,OAAO,GAAW;AAC7D,MAAI,UAAU,EAAG,QAAO;AACxB,MAAI,WAAW,EAAG,QAAO;AACzB,MAAI,SAAS,EAAG,QAAO,SAAI,IAAI,OAAO,SAAS,CAAC,CAAC;AACjD,QAAM,QAAQ,CAACC,YACb,MAAM,KAAK,EAAE,QAAQA,QAAO,GAAG,CAAC,GAAG,MAAM,QAAS,IAAI,QAAQ,IAAK,CAAC,CAAC,EAClE,KAAK,EAAE,EACP,QAAQ,yBAAyB,GAAG;AAIzC,MAAI,SAAS;AACb,SAAO,MAAM,SAAS,CAAC,EAAE,SAAS,KAAK,OAAQ,WAAU;AACzD,QAAM,UAAU,MAAM,MAAM;AAC5B,SAAO,SAAI,OAAO,IAAI,IAAI,OAAO,SAAS,QAAQ,SAAS,CAAC,CAAC;AAC/D;AAEA,IAAM,WAAW,CAAC,MAChB,OAAO,MAAM,YAAY,MAAM,QAAQ,CAAC,MAAM,QAAQ,CAAC;AAGzD,IAAM,gBAAgB,oBAAI,IAAI,CAAC,SAAS,SAAS,OAAO,CAAC;AAEzD,SAAS,YACP,MACA,MACA,MACA,MACQ;AACR,MAAI,KAAK,QAAQ,KAAK,KAAK,SAAS,GAAG;AACrC,UAAM,SAAS,KAAK,KAAK,IAAI,MAAM;AACnC,WAAO,SAAS,QACZ,OAAO,OAAO,CAAC,GAAG,MAAO,EAAE,SAAS,EAAE,SAAS,IAAI,CAAE,IACrD,OAAO,CAAC;AAAA,EACd;AACA,MAAI,KAAK,cAAc,UAAa,cAAc,IAAI,IAAI,GAAG;AAC3D,WAAO;AAAA,MACL,SAAS,QAAQ,KAAK,YAAY,KAAK,IAAI,GAAG,KAAK,SAAS;AAAA,MAC5D;AAAA,IACF;AAAA,EACF;AACA,MAAI,KAAK,cAAc,UAAa,SAAS,QAAQ;AACnD,WAAO,SAAS,QACZ,UAAK,IAAI,OAAO,KAAK,IAAI,GAAG,KAAK,YAAY,CAAC,CAAC,CAAC,GAAG;AAAA,MACjD;AAAA,MACA,KAAK;AAAA,IACP,IACA;AAAA,EACN;AACA,MAAI,KAAK,cAAc,QAAW;AAChC,UAAM,SACJ,SAAS,QAAQ,KAAK,YAAY,KAAK,IAAI,GAAG,KAAK,aAAa,CAAC;AAGnE,QAAI,OAAO,OAAO,IAAI,GAAG,MAAM,GAAG,MAAM;AACxC,QAAI,IAAI;AACR,eAAS;AACP,YAAM,OAAO,OAAO,OAAO,KAAK,MAAM,MAAM;AAC5C,WAAK;AACL,UAAI,KAAK,SAAS,IAAI,KAAK,SAAS,QAAQ;AAC1C,YAAI,IAAI,MAAM,OAAQ;AACtB;AAAA,MACF;AACA,cAAQ,IAAI,IAAI;AAAA,IAClB;AACA,WAAO;AAAA,EACT;AACA,QAAM,SAAS,KAAK,YAAY;AAChC,QAAM,QAAQ,SAAS,QAAQ,SAAS,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,MAAM,CAAC;AACvE,QAAM,WAAW,CAAC,KAAK,WAAW,KAAK,SAAS;AAChD,MAAI,KAAK,SAAS,SAAU,QAAO,WAAW,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC;AAC3E,MAAI,SAAS;AACX,WAAO,SAAS,QAAQ,MAAM,OAAO,MAAM,KAAK,IAAI;AACtD,SAAO,MAAM,OAAO,MAAM,QAAQ;AACpC;AAEA,SAAS,eAAe,MAAc,MAAkB,MAAmB;AACzE,MAAI,SAAS,SAAS;AACpB,UAAM,aAAa,SAAS,QAAQ,IAAI;AACxC,UAAM,SAAS,SAAS,QAAQ,IAAI;AACpC,WAAO;AAAA,MACL,MAAM;AAAA,MACN,OAAO;AAAA,QACL,MAAM;AAAA,QACN,cACE,SAAS,QAAQ,GAAG,MAAM,GAAG,MAAM,KAAK,CAAC,eAAU;AAAA,QACrD,GAAI,SAAS,SAAS,EAAE,cAAc,MAAM,GAAG,OAAO,GAAG,KAAK,EAAE;AAAA,QAChE,aAAa,CAAC,WAAW,aAAa,QAAQ,EAAE,MAAM,GAAG,MAAM;AAAA,QAC/D,MAAM,MAAM,KAAK,EAAE,QAAQ,OAAO,GAAG,CAAC,GAAG,OAAO;AAAA,UAC9C,MAAM,MAAM,SAAS,QAAQ,IAAI,GAAG,OAAO,GAAG,KAAK;AAAA;AAAA;AAAA;AAAA,UAInD,QAAQ,MAAM;AAAA,YAAK,EAAE,QAAQ,WAAW;AAAA,YAAG,CAACC,IAAG,MAC7C,SAAS,QAAQ,MAAM,GAAG,OAAO,GAAG,KAAK,IAAI,IAAI,IAAI,CAAC;AAAA,UACxD;AAAA,UACA,QAAQ,MAAM;AAAA,YACZ,EAAE,QAAQ,WAAW;AAAA,YACrB,CAACA,IAAG,MAAM,OAAO,IAAI,KAAK;AAAA,UAC5B;AAAA,QACF,EAAE;AAAA,MACJ;AAAA,IACF;AAAA,EACF;AACA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA,MACL,QAAQ;AAAA,MACR,OAAO,SAAS,QAAS,SAAS,SAAS,QAAQ,SAAU;AAAA,MAC7D,KAAK,MAAM,GAAG,MAAM,KAAK;AAAA,IAC3B;AAAA,EACF;AACF;AAGO,SAAS,kBACd,MACA,MACA,MACA,OAAO,GACE;AACT,UAAQ,KAAK,MAAM;AAAA,IACjB,KAAK;AACH,aAAO,YAAY,MAAM,MAAM,MAAM,IAAI;AAAA,IAC3C,KAAK;AAAA,IACL,KAAK;AACH,aAAO,SAAS,QAAQ,KAAK,WAAW,OAAO,KAAK,WAAW;AAAA,IACjE,KAAK;AACH,aAAO,SAAS,QAAQ,OAAO,KAAK,WAAW;AAAA,IACjD,KAAK;AACH,aAAO,eAAe,MAAM,MAAM,IAAI;AAAA,IACxC,KAAK,UAAU;AACb,YAAM,MAAW,CAAC;AAClB,iBAAW,CAAC,KAAK,QAAQ,KAAK,OAAO,QAAQ,KAAK,cAAc,CAAC,CAAC,GAAG;AACnE,YAAI,SAAS,SAAS,CAAC,SAAS,SAAU;AAC1C,cAAM,QAAQ,kBAAkB,UAAU,KAAK,MAAM,OAAO,IAAI,MAAM;AACtE,YAAI,UAAU,OAAW,KAAI,GAAG,IAAI;AAAA,MACtC;AACA,aAAO;AAAA,IACT;AAAA,IACA,KAAK,SAAS;AACZ,YAAM,QACJ,SAAS,QACL,KAAK,YAAY,IACjB,KAAK,aAAa,KAAK,WAAW,IAAI;AAC5C,YAAM,OAAO,KAAK,SAAS,EAAE,MAAM,SAAS;AAC5C,aAAO,MAAM;AAAA,QAAK,EAAE,QAAQ,MAAM;AAAA,QAAG,CAAC,GAAG,MACvC,kBAAkB,MAAM,MAAM,MAAM,OAAO,CAAC;AAAA,MAC9C;AAAA,IACF;AAAA,IACA;AACE,aAAO;AAAA,EACX;AACF;AASO,SAAS,mBACd,MACA,YACA,MACA,OAAO,GACP,gBAAmC,CAAC,GAC/B;AACL,QAAM,QAAa,CAAC;AACpB,MAAI,SAAS;AACb,aAAW,CAAC,UAAU,IAAI,KAAK,OAAO,QAAQ,WAAW,KAAK,GAAG;AAC/D,cAAU,SAAS;AACnB,UAAM,WACJ,KAAK,aAAa,QACjB,KAAK,SAAS,UAAa,cAAc,SAAS,KAAK,IAAI;AAC9D,QAAI,SAAS,SAAS,CAAC,SAAU;AACjC,UAAM,QAAQ,kBAAkB,MAAM,UAAU,MAAM,MAAM;AAC5D,QAAI,UAAU,OAAW,OAAM,QAAQ,IAAI;AAAA,EAC7C;AACA,QAAM,SAAS,MAAM;AACrB,QAAM,UAAU,MAAM;AACtB,MAAI,MAAM,QAAQ,MAAM,KAAK,MAAM,QAAQ,OAAO,GAAG;AACnD,UAAM,QAAQ,WAAW,MAAM,SAAS,OAAO,YAAY;AAC3D,eAAW,UAAU,SAAS;AAC5B,UAAI,SAAS,MAAM,KAAK,MAAM,QAAQ,OAAO,KAAK,KAAK,OAAO,OAAO;AACnE,eAAO,QAAQ,OAAO;AAAA,UAAI,CAAC,GAAG,MAC5B,kBAAkB,MAAM,OAAoB,SAAS,MAAM,SAAS,CAAC;AAAA,QACvE;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO,EAAE,MAAM,SAAS,OAAO,EAAE,KAAK,MAAM,MAAM,EAAE;AACtD;AAOO,SAAS,qBACd,MACA,YACA,OAAO,GACiE;AACxE,QAAM,aAAa,mBAAmB,MAAM,YAAY,OAAO,IAAI;AACnE,QAAM,QAAS,WAAW,MAAc;AACxC,aAAW,CAAC,UAAU,IAAI,KAAK,OAAO,QAAQ,WAAW,KAAK,GAAG;AAC/D,QAAI,KAAK,SAAS,YAAY,KAAK,aAAa,QAAW;AACzD,YAAM,QAAQ,IAAI,GAAG,MAAM,KAAK,WAAW,GAAG,MAAM,CAAC,KAAK,OAAO,CAAC;AAClE,aAAO,EAAE,YAAY,MAAM,UAAU,MAAM,QAAQ;AAAA,IACrD;AAAA,EACF;AACA,aAAW,CAAC,UAAU,IAAI,KAAK,OAAO,QAAQ,WAAW,KAAK,GAAG;AAC/D,QACE,KAAK,SAAS,WACd,KAAK,aAAa,UAClB,MAAM,QAAQ,MAAM,QAAQ,CAAC,GAC7B;AACA,YAAM,QAAQ,MAAM,QAAQ;AAC5B,YAAM,QAAQ,IAAI,CAAC,GAAG,OAAO,MAAM,CAAC,CAAC;AACrC,aAAO,EAAE,YAAY,MAAM,UAAU,MAAM,QAAQ;AAAA,IACrD;AAAA,EACF;AACA,SAAO;AACT;AAGO,SAAS,0BACd,UACA,cACyB;AACzB,QAAM,cAAc,qBAAqB,QAAQ;AACjD,QAAM,WAAW,oBAAI,IAAiB;AACtC,QAAM,OAAO,CAAC,SAAwB;AACpC,QAAI,MAAM,QAAQ,IAAI,GAAG;AACvB,WAAK,QAAQ,IAAI;AACjB;AAAA,IACF;AACA,QAAI,CAAC,SAAS,IAAI,EAAG;AACrB,UAAM,QAAQ,KAAK;AACnB,QACE,KAAK,SAAS,WACd,SAAS,KAAK,KACd,OAAO,MAAM,QAAQ,YACrB,CAAC,SAAS,IAAI,MAAM,GAAG,GACvB;AACA,eAAS,IAAI,MAAM,KAAK,SAAS,MAAM,KAAK,IAAI,MAAM,QAAQ,CAAC,CAAC;AAAA,IAClE;AACA,SAAK,KAAK,QAAQ;AAAA,EACpB;AACA,OAAK,SAAS,QAAQ,IAAI,SAAS,WAAW,MAAS;AACvD,SAAO,OAAO,QAAQ,WAAW,EAAE,IAAI,CAAC,CAAC,MAAM,UAAU,OAAO;AAAA,IAC9D;AAAA,IACA,UAAU;AAAA,IACV,SAAS,iBAAiB,KAAK,QAAQ,MAAM,IAAI,EAAE,QAAQ,OAAO,IAAI,CAAC;AAAA,IACvE;AAAA,IACA,GAAI,SAAS,IAAI,IAAI,KAAK,EAAE,SAAS,SAAS,IAAI,IAAI,EAAE;AAAA,EAC1D,EAAE;AACJ;AAGA,IAAM,SAAS,CAAC,SAAS,gBAAgB,gBAAgB;AAEzD,SAAS,QACP,OACA,MACA,aACiB;AACjB,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI,MAAM,SAAS;AACjB,WAAO;AAAA,MACL,MAAM;AAAA,MACN,OAAO,EAAE,KAAK,MAAM,OAAO,gBAAgB,MAAM,OAAO,EAAE;AAAA,IAC5D;AAAA,EACF;AACA,SAAO,mBAAmB,MAAM,YAAY,IAAI,GAAG,KAAK;AAC1D;AAGA,SAAS,aAAa,OAAyB;AAC7C,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO,MAAM,IAAI,YAAY;AACvD,MAAI,CAAC,SAAS,KAAK,EAAG,QAAO;AAC7B,MACE,MAAM,SAAS,WACf,SAAS,MAAM,KAAK,KACpB,OAAO,MAAM,MAAM,SAAS,UAC5B;AACA,UAAM,QAAa,EAAE,GAAG,MAAM,OAAO,QAAQ,aAAa;AAC1D,WAAO,MAAM;AACb,WAAO,EAAE,GAAG,OAAO,MAAM;AAAA,EAC3B;AACA,MAAI,MAAM,SAAS,aAAc,QAAO,eAAe,SAAS,OAAO,CAAC;AACxE,SAAO,OAAO;AAAA,IACZ,OAAO,QAAQ,KAAK,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC;AAAA,EAC5D;AACF;AAEA,SAAS,gBACP,aACA,OACqC;AACrC,QAAM,MAA2C,CAAC;AAClD,aAAW,QAAQ,OAAO;AACxB,QAAI,EAAE,QAAQ,aAAc;AAC5B,QAAI,IAAI,IAAI,YAAY,IAAI;AAC5B,eAAW,cAAc,kBAAkB,aAAa,IAAI,GAAG;AAC7D,UAAI,cAAc,YAAa,KAAI,UAAU,IAAI,YAAY,UAAU;AAAA,IACzE;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,WACP,OACA,UACA,OACA,MACA,QACK;AAGL,QAAM,WAAW,SAAS,IAAI,CAAC,UAAU;AACvC,QAAI,CAAC,SAAS,KAAK,KAAK,MAAM,SAAS,UAAW,QAAO;AACzD,UAAMC,SAAQ,SAAS,MAAM,KAAK,IAAI,MAAM,QAAQ,CAAC;AACrD,UAAM,OAAO,SAASA,OAAM,IAAI,IAAIA,OAAM,OAAO,CAAC;AAClD,WAAO,EAAE,GAAG,OAAO,OAAO,EAAE,GAAGA,QAAO,MAAM,EAAE,GAAG,MAAM,MAAM,OAAO,EAAE,EAAE;AAAA,EAC1E,CAAC;AAED,QAAM,QAAQ,KAAK,UAAU,QAAQ,EAAE,SAAS,gBAAgB;AAChE,SAAO;AAAA,IACL,MAAM;AAAA,IACN,GAAI,SAAS,EAAE,UAAU,cAAc;AAAA,IACvC,OAAO;AAAA,MACL,GAAG;AAAA,MACH;AAAA,MACA,GAAI,SAAS,cAAc,EAAE,gBAAgB,EAAE,OAAO,eAAe,EAAE;AAAA,IACzE;AAAA,IACA,UAAU;AAAA,EACZ;AACF;AAKA,IAAM,YAAY,MAAM,IAAI,KAAM,IAAI;AAO/B,SAAS,kBACd,SACA,MACA,MACA,OACA,EAAE,OAAO,UAAU,SAAS,MAAM,gBAAgB,CAAC,EAAE,IAAoB,CAAC,GACrE;AACL,QAAM,cAAc,OAAO;AAAA,IACzB,QAAQ,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC;AAAA,EAC3C;AACA,QAAM,SAAS,IAAI,IAAI,QAAQ,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;AACtD,QAAM,QAAQ,CAAC,WACb,WAAW,OACP,mBAAmB,MAAM,YAAY,IAAI,GAAG,MAAM,GAAG,aAAa,IACjE,aAAa,QAAQ,OAAO,IAAI,MAAM,GAAG,QAAQ,WAAW,CAAC;AAGpE,QAAM,QAAQ,MAAM,OAAO;AAC3B,QAAM,cAAc,MAAM,cAAc;AACxC,QAAM,SAAS,MAAM,gBAAgB;AACrC,QAAM,OAAkB,CAAC;AACzB,MAAI,CAAC,OAAO,SAAS,IAA+B,GAAG;AAErD,QAAI,SAAS,eAAe,OAAO,IAAI,QAAQ,GAAG;AAChD,WAAK;AAAA,QACH,aAAa,QAAQ,OAAO,IAAI,QAAQ,GAAG,UAAU,WAAW,CAAC;AAAA,MACnE;AAAA,IACF;AACA,SAAK;AAAA,MACH,mBAAmB,MAAM,YAAY,IAAI,GAAG,MAAM,GAAG,aAAa;AAAA,IACpE;AAAA,EACF;AACA,OAAK,KAAK,EAAE,MAAM,aAAa,OAAO,EAAE,MAAM,UAAU,EAAE,CAAC;AAC3D,QAAM,OAAO,oBAAI,IAAY;AAAA,IAC3B;AAAA,IACA,GAAG;AAAA,IACH,GAAI,SAAS,cAAc,CAAC,QAAQ,IAAI,CAAC;AAAA,EAC3C,CAAC;AACD,QAAM,WAAsB,CAAC;AAC7B,MAAI,MAAO,UAAS,KAAK,EAAE,MAAM,WAAW,UAAU,CAAC,KAAK,EAAE,CAAC;AAC/D,WAAS,KAAK;AAAA,IACZ,MAAM;AAAA,IACN,UAAU,CAAC,aAAa,QAAQ,GAAG,IAAI,EAAE,OAAO,CAAC,MAAM,MAAM,MAAS;AAAA,EACxE,CAAC;AACD,SAAO;AAAA,IACL;AAAA,MACE,UAAU;AAAA,QACR,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,MAAM;AAAA,MACR;AAAA,MACA,QAAQ,gBAAgB,aAAa,IAAI;AAAA,IAC3C;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAGO,SAAS,mBACd,UACA,MACA,OACA,EAAE,OAAO,UAAU,SAAS,MAAM,gBAAgB,CAAC,EAAE,IAAoB,CAAC,GACrE;AACL,MAAI,CAAC,SAAS,QAAQ,KAAK,CAAC,SAAS,SAAS,KAAK;AACjD,UAAM,IAAI,MAAM,sCAAsC;AACxD,QAAM,cAAc,qBAAqB,QAAQ;AACjD,MAAI,OAAO;AACX,QAAM,UAAU,CAAC,SAA2B;AAC1C,QAAI,MAAM,QAAQ,IAAI,EAAG,QAAO,KAAK,IAAI,OAAO;AAChD,QAAI,CAAC,SAAS,IAAI,EAAG,QAAO;AAC5B,UAAMA,SAAQ,KAAK;AACnB,QACE,KAAK,SAAS,WACd,SAASA,MAAK,KACd,OAAOA,OAAM,QAAQ,YACrB,YAAYA,OAAM,GAAG,GACrB;AACA,cAAQ;AACR,aAAO;AAAA,QACLA,OAAM;AAAA,QACN,YAAYA,OAAM,GAAG;AAAA,QACrB;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA,QACE,KAAK,SAAS,eACd,SAASA,MAAK,KACd,OAAOA,OAAM,SAAS,UACtB;AACA,aAAO;AAAA,QACL,GAAG;AAAA,QACH,OAAO,EAAE,GAAGA,QAAO,MAAM,SAAS,QAAQ,YAAYA,OAAM,KAAK;AAAA,MACnE;AAAA,IACF;AACA,WAAO,MAAM,QAAQ,KAAK,QAAQ,IAC9B,EAAE,GAAG,MAAM,UAAU,QAAQ,KAAK,QAAQ,EAAE,IAC5C;AAAA,EACN;AACA,QAAM,QAAa,gBAAgB,SAAS,KAAK;AACjD,SAAO,MAAM;AACb,SAAO,MAAM;AACb,SAAO;AAAA,IACL;AAAA,IACA,QAAQ,SAAS,QAAQ;AAAA,IACzB;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAGO,SAAS,oBACd,UACA,cACA,SACmB;AACnB,QAAM,UAAU,0BAA0B,UAAU,YAAY;AAChE,QAAM,QAAQ,QAAQ,SAAS,CAAC,UAAU,UAAU;AACpD,QAAM,QAAQ,QAAQ,SAAS,CAAC,OAAO,KAAK;AAC5C,QAAM,WAAW,QAAQ,YAAY,CAAC,IAAI;AAC1C,QAAM,QAA2B,CAAC;AAClC,aAAW,SAAS,QAAQ;AAC1B,eAAW,QAAQ;AACjB,iBAAW,UAAU;AACnB,mBAAW,QAAQ,OAAO;AACxB,cAAI,QAAQ,WAAW;AACrB,uBAAW,SAAS;AAClB,oBAAM,KAAK;AAAA,gBACT,IAAI,GAAG,MAAM,IAAI,IAAI,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,MAAM;AAAA,gBACpD,OAAO,MAAM;AAAA,gBACb;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA,UAAU,kBAAkB,SAAS,MAAM,MAAM,MAAM,OAAO;AAAA,kBAC5D;AAAA,kBACA;AAAA,kBACA,eAAe,QAAQ;AAAA,gBACzB,CAAC;AAAA,cACH,CAAC;AACL,cAAI,QAAQ,WAAW;AACrB,kBAAM,KAAK;AAAA,cACT,IAAI,UAAU,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,MAAM;AAAA,cAC7C,OAAO;AAAA,cACP;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA,UAAU,mBAAmB,UAAU,MAAM,OAAO;AAAA,gBAClD;AAAA,gBACA;AAAA,gBACA,eAAe,QAAQ;AAAA,cACzB,CAAC;AAAA,YACH,CAAC;AAAA,QACL;AACN,SAAO;AACT;","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","cached","words","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/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\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/** Area the two word boxes share, in square points; 0 when they miss. */\nfunction intersects(a: PdfTextWord, b: PdfTextWord): number {\n const w = Math.min(a.xMax, b.xMax) - Math.max(a.xMin, b.xMin);\n const h = Math.min(a.yMax, b.yMax) - Math.max(a.yMin, b.yMin);\n return w > 0 && h > 0 ? w * h : 0;\n}\n\nfunction area(w: PdfTextWord): number {\n return Math.max(0, w.xMax - w.xMin) * Math.max(0, w.yMax - w.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.\n for (const match of matches) {\n const box = match.entry.box;\n if (!box || match.status !== 'mapped') continue;\n for (const o of match.occurrences) {\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 .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 const section = owner\n ? /^\\/children\\/\\d+/.exec(owner.path)?.[0]\n : undefined;\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 ? 'Keep the notes and sources with the paragraph before them, or tighten the page before so the document closes on a designed page rather than a stub.'\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\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 * 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/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/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 */\n\nimport type { PdfTextWord, PdfTextPage } 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 /** 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}\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 */\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 pages.forEach((page, pageIndex) => {\n for (const index of readingOrder(page.words)) {\n const word = page.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 });\n return { pages, refs, fragments, positions, stream: fragments.join('') };\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 */\nexport function readingOrder(words: readonly PdfTextWord[]): number[] {\n // Rotated text — a chart's value-axis title — comes as boxes taller than\n // they are wide; rows and columns mean nothing to it, so it keeps the\n // order poppler gave it, in the slots it had.\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 const upright = uprightOrder(\n words,\n words.map((_, i) => i).filter((i) => !rotated.has(i))\n );\n const order: number[] = [];\n let next = 0;\n for (let i = 0; i < words.length; i++) {\n if (rotated.has(i)) order.push(i);\n else order.push(upright[next++]);\n }\n return order;\n}\n\nfunction uprightOrder(\n words: readonly PdfTextWord[],\n indices: readonly number[]\n): number[] {\n const rows: number[][] = [];\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 row = rows[rows.length - 1];\n if (row && sameRow(words[row[0]], words[i])) row.push(i);\n else rows.push([i]);\n }\n const fragmentsOf = (row: number[]): RowFragment[] => {\n const sorted = [...row].sort((a, b) => words[a].xMin - words[b].xMin);\n const out: RowFragment[] = [];\n for (const i of sorted) {\n const last = out[out.length - 1];\n const w = words[i];\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.\n const height = w.yMax - w.yMin;\n if (last && w.xMin - last.xMax <= height / 2) {\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 = rows.map((row) => ({\n yMin: Math.min(...row.map((i) => words[i].yMin)),\n yMax: Math.max(...row.map((i) => words[i].yMax)),\n }));\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 fragment across two columns is prose\n // under the table: it closes the cluster and opens the next.\n interface Column {\n xMin: number;\n xMax: number;\n fragments: RowFragment[];\n }\n const order: number[] = [];\n let cluster: { rows: number[]; columns: Column[]; multi: boolean } | null =\n null;\n const flush = (): void => {\n if (!cluster) return;\n if (!cluster.multi) {\n for (const r of cluster.rows)\n for (const f of fragments[r]) order.push(...f.words);\n } else {\n cluster.columns.sort((a, b) => a.xMin - b.xMin);\n for (const column of cluster.columns) {\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) order.push(...f.words);\n }\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: [], multi: false };\n cluster.rows.push(r);\n if (fragments[r].length > 1) cluster.multi = true;\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 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 hits.push(occurrence(index, at, end));\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\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.\n */\nexport function longestRenderedPrefix(\n index: StreamIndex,\n needle: string\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 let length = MIN_PARTIAL_PREFIX;\n while (\n length < needle.length &&\n index.stream.charCodeAt(at + length) === needle.charCodeAt(length)\n ) {\n length++;\n }\n if (!best || length > best.length) best = { length, at };\n }\n return best;\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\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 */\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 // A row of digits alone in a band is a page number, whatever its footer\n // said around it — including a footer that was nothing but the field.\n pages.forEach((page, pageIndex) => {\n page.words.forEach((word, i) => {\n if (chromeWords.has(`${pageIndex}:${i}`)) return;\n if (!inChromeBand(page, word)) return;\n const row = page.words.filter((w) => sameRow(w, word));\n if (row.every((w) => PAGE_NUMBER_ROW.test(w.text))) {\n claimRow(pageIndex, word);\n }\n });\n });\n\n const body = indexDocument(pages, chromeWords);\n /** Stream offset → the entry that claimed the occurrence there. */\n const claimed = new Map<number, T>();\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);\n let partial: InventoryMatch<T>['partial'];\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(body, needle);\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) => !claimed.has(o.at));\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) => claimed.get(o.at)?.optional);\n if (held) {\n const holder = claimed.get(held.at) as T;\n claimed.delete(held.at);\n results.set(holder, {\n entry: holder,\n needle: results.get(holder)?.needle ?? '',\n status: 'skipped',\n occurrences: [],\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 = free.find((o) => o.at >= cursor) ?? free[0];\n claimed.set(chosen.at, entry);\n 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 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","cached","words","digits","_","props"]}