@json-to-office/jto-ops 1.2.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/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Wiseair srl
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
6
+ associated documentation files (the "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
9
+ following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all copies or substantial
12
+ portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
15
+ LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
16
+ EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
18
+ USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # @json-to-office/jto-ops
2
+
3
+ Host-side operations layer for [json-to-office](https://github.com/Wiseair-srl/json-to-office): the format adapters that validate and generate documents, the LibreOffice rasterizer behind the docx `visual` component, and the per-platform LibreOffice font stagers.
4
+
5
+ Extracted from [`@json-to-office/jto-cli`](https://www.npmjs.com/package/@json-to-office/jto-cli) so a host without a terminal — the MCP server, a serverless function, a job runner — can do the work without installing ink, react, commander or chalk. The CLI re-exports everything here, so nothing changes for its consumers.
6
+
7
+ ```ts
8
+ import {
9
+ createAdapter,
10
+ createLibreOfficePptxRasterizer,
11
+ getFontStager,
12
+ } from '@json-to-office/jto-ops';
13
+
14
+ const adapter = createAdapter('docx');
15
+ const result = await adapter.generate(document, { output: 'out.docx' });
16
+ ```
17
+
18
+ ## Diagnostics
19
+
20
+ This package writes to no stream of its own — a host may own stdout as a protocol channel. Warnings and traces go to a sink you scope around the call:
21
+
22
+ ```ts
23
+ import {
24
+ runWithDiagnosticSink,
25
+ stderrDiagnosticSink,
26
+ } from '@json-to-office/jto-ops';
27
+
28
+ await runWithDiagnosticSink(stderrDiagnosticSink, () =>
29
+ adapter.generate(document, options)
30
+ );
31
+ ```
32
+
33
+ With no sink installed the messages are dropped. The CLI installs an Ink-backed sink per task; `stderrDiagnosticSink` is the plain-text fallback for hosts with no UI.
34
+
35
+ ## License
36
+
37
+ MIT
@@ -0,0 +1,412 @@
1
+ import { FontRuntimeOpts, GenerationWarning, PptxBatchRasterizer, PptxRasterizer, ResolvedFont } from '@json-to-office/shared';
2
+
3
+ type FormatName = 'docx' | 'pptx';
4
+ interface GeneratorOptions {
5
+ theme?: string | any;
6
+ themePath?: string;
7
+ customThemes?: Record<string, any>;
8
+ validation?: {
9
+ strict?: boolean;
10
+ allowUnknownFields?: boolean;
11
+ };
12
+ fonts?: FontRuntimeOpts;
13
+ deterministic?: boolean;
14
+ generatedAt?: string | Date;
15
+ /**
16
+ * Directory that relative asset paths in the document resolve against —
17
+ * normally the input document's own directory (#142).
18
+ */
19
+ baseDir?: string;
20
+ /**
21
+ * Backend that turns the compiled document into bytes.
22
+ *
23
+ * Format-specific and validated by the core's renderer registry, which is
24
+ * why this is a bare string here: naming the id union would make this
25
+ * package import both cores statically, and they are loaded on demand.
26
+ * Undefined means the format's default (`docxjs` / `pptxgenjs`).
27
+ */
28
+ renderer?: string;
29
+ /**
30
+ * Optional sink for structured generation warnings (FONT_UNRESOLVED and
31
+ * friends). Mirrors core-docx's `JsonGenerationOptions.warnings`, and is the
32
+ * only delivery mechanism that works off the CLI: `emitGenerationWarnings`
33
+ * routes through an AsyncLocalStorage sink that is a no-op on the server.
34
+ *
35
+ * Adapters PUSH into it; they never replace it. Warnings therefore
36
+ * ACCUMULATE across repeated `generateBuffer` calls on one
37
+ * `GeneratorResult` — allocate one array per logical request.
38
+ */
39
+ warnings?: GenerationWarning[];
40
+ }
41
+ interface GeneratorResult {
42
+ generateBuffer: (document: any) => Promise<Buffer>;
43
+ /**
44
+ * Post-expansion standard JSON tree without any rendering work — no fonts,
45
+ * no layout, no visual rasterization. Present when the underlying generator
46
+ * supports it (plugin-aware DOCX generation does).
47
+ */
48
+ getStandardDefinition?: (config: any) => Promise<any>;
49
+ hasPlugins: boolean;
50
+ pluginNames: string[];
51
+ /**
52
+ * Identity of the theme this generator forces on every document, or
53
+ * undefined when nothing was requested and each document's own `props.theme`
54
+ * decides. Reported by the CLI so the summary names what actually rendered.
55
+ */
56
+ themeLabel?: string;
57
+ }
58
+ interface FormatAdapter {
59
+ name: FormatName;
60
+ extension: string;
61
+ label: string;
62
+ defaultPort: number;
63
+ generateBuffer(json: unknown, options: GeneratorOptions): Promise<Buffer>;
64
+ createGenerator(plugins: any[], options: GeneratorOptions): Promise<GeneratorResult>;
65
+ parseJson(input: string | object): unknown;
66
+ validateDocument(doc: unknown): {
67
+ valid: boolean;
68
+ errors?: any[];
69
+ };
70
+ generateSchema(options?: any): any;
71
+ getBuiltinThemes(): Record<string, any>;
72
+ resolveTheme(options: GeneratorOptions): Promise<any>;
73
+ loadCustomThemes(options: GeneratorOptions): Promise<Record<string, any> | undefined>;
74
+ /**
75
+ * Renderer ids this format registers, defaults first.
76
+ *
77
+ * Async because the core that owns the registry is imported on demand — the
78
+ * list is read from it rather than repeated here, so the two cannot drift.
79
+ */
80
+ rendererIds(): Promise<readonly string[]>;
81
+ /** Cumulative visual pre-pass dedupe counters (DOCX only) (#156). */
82
+ getVisualPrepassStats?(): Promise<any>;
83
+ /** Reset per-format cache observability counters (DOCX only). */
84
+ resetCacheStats?(): Promise<void>;
85
+ }
86
+ declare class DocxFormatAdapter implements FormatAdapter {
87
+ name: FormatName;
88
+ extension: string;
89
+ label: string;
90
+ defaultPort: number;
91
+ rendererIds(): Promise<readonly string[]>;
92
+ generateBuffer(json: unknown, options: GeneratorOptions): Promise<Buffer>;
93
+ createGenerator(plugins: any[], options: GeneratorOptions): Promise<GeneratorResult>;
94
+ parseJson(input: string | object): unknown;
95
+ validateDocument(doc: unknown): {
96
+ valid: boolean;
97
+ errors?: any[];
98
+ };
99
+ generateSchema(_options?: any): any;
100
+ getBuiltinThemes(): Record<string, any>;
101
+ resolveTheme(options: GeneratorOptions): Promise<any>;
102
+ /**
103
+ * Resolve `theme`/`themePath` once for a whole run: `themePath` is read a
104
+ * single time and feeds both the requested theme and the custom-theme
105
+ * registry, so a bad path warns once instead of once per consumer.
106
+ */
107
+ private resolveThemes;
108
+ loadCustomThemes(options: GeneratorOptions): Promise<Record<string, any> | undefined>;
109
+ getVisualPrepassStats(): Promise<any>;
110
+ resetCacheStats(): Promise<void>;
111
+ }
112
+ declare class PptxFormatAdapter implements FormatAdapter {
113
+ name: FormatName;
114
+ extension: string;
115
+ label: string;
116
+ defaultPort: number;
117
+ rendererIds(): Promise<readonly string[]>;
118
+ generateBuffer(json: unknown, options: GeneratorOptions): Promise<Buffer>;
119
+ createGenerator(plugins: any[], options: GeneratorOptions): Promise<GeneratorResult>;
120
+ parseJson(input: string | object): unknown;
121
+ validateDocument(doc: unknown): {
122
+ valid: boolean;
123
+ errors?: any[];
124
+ };
125
+ generateSchema(_options?: any): any;
126
+ getBuiltinThemes(): Record<string, any>;
127
+ resolveTheme(options: GeneratorOptions): Promise<any>;
128
+ /**
129
+ * Resolve `theme`/`themePath` once for a whole run: `themePath` is read a
130
+ * single time and feeds both the requested theme and the custom-theme
131
+ * registry, so a bad path warns once instead of once per consumer.
132
+ */
133
+ private resolveThemes;
134
+ loadCustomThemes(options: GeneratorOptions): Promise<Record<string, any> | undefined>;
135
+ }
136
+ declare function createAdapter(format: FormatName): FormatAdapter;
137
+
138
+ /**
139
+ * PPTX rasterizer — the concrete service backing docx `visual` components.
140
+ *
141
+ * Pipeline: presentation JSON → (core-pptx) .pptx → (LibreOffice) PDF →
142
+ * (poppler/pdftoppm) PNG. Returns a base64 data URI plus the natural pixel
143
+ * dimensions. Results are content-addressed and cached on disk so repeated
144
+ * builds of an unchanged visual skip the (multi-second) LibreOffice run.
145
+ *
146
+ * Single and batch rasterization share one engine (#153). A batch keeps one
147
+ * .pptx per slide and converts them all in a single `soffice` launch — the
148
+ * launch is the dominant cost, and per-file conversion keeps slides fully
149
+ * independent: each has its own PDF and PNG (no page↔slide index mapping),
150
+ * its own dpi, and a cache key identical to the single-slide path, so both
151
+ * paths share the same disk cache.
152
+ *
153
+ * A request may carry `fonts`: base64 font faces that are staged for the
154
+ * soffice launches (via the shared FontStager pipeline) so the slide renders
155
+ * with the document's real families instead of whatever the host happens to
156
+ * have installed. Those fonts are part of the disk-cache key — the same
157
+ * slide is genuinely different pixels with and without them, and the cache
158
+ * is shared process-wide across callers.
159
+ *
160
+ * Every engine run works against a wall-clock deadline (one batch-scaled
161
+ * soffice window plus one pdftoppm window) so a wedged conversion fails the
162
+ * remaining slides quickly instead of holding the caller — and its
163
+ * concurrency slot — for minutes.
164
+ *
165
+ * This is injected via `services.pptx.render` / `services.pptx.renderBatch`;
166
+ * the published engine packages never depend on these binaries.
167
+ */
168
+
169
+ /**
170
+ * Process-wide rasterizer disk-cache counters (#156).
171
+ */
172
+ interface RasterizerCacheStats {
173
+ /** Slides served from the content-addressed PNG disk cache. */
174
+ diskHits: number;
175
+ /** Unique slides that missed the disk cache and needed the engine. */
176
+ diskMisses: number;
177
+ /** diskHits / (diskHits + diskMisses), 0 when no lookups. */
178
+ hitRate: number;
179
+ /** Requests resolved by batch-internal dedupe (duplicate slides). */
180
+ dedupedRequests: number;
181
+ /** Slides successfully rendered by the engine (LibreOffice + pdftoppm). */
182
+ rendered: number;
183
+ /** Slides that failed at any engine stage. */
184
+ failed: number;
185
+ /** PNG files currently in the disk cache directories. */
186
+ entries: number;
187
+ /** Total bytes of those PNG files. */
188
+ bytes: number;
189
+ }
190
+ /**
191
+ * Get rasterizer cache statistics: process-lifetime counters plus a live
192
+ * scan of the disk cache directories (default dir included, so entries from
193
+ * previous processes are visible too).
194
+ */
195
+ declare function getRasterizerCacheStats(): Promise<RasterizerCacheStats>;
196
+ /**
197
+ * Delete every cached PNG in the known cache directories (default dir
198
+ * included) and reset the counters. Backs "Clear all caches" (#156) — the
199
+ * disk cache used to survive it.
200
+ */
201
+ declare function clearRasterizerCache(): Promise<void>;
202
+ /**
203
+ * Build a LibreOffice-backed pptx rasterizer.
204
+ *
205
+ * @param options.cacheDir - directory for the content-addressed PNG cache
206
+ * (default: <tmp>/jto-visual-cache). Pass `null` to disable caching.
207
+ */
208
+ declare function createLibreOfficePptxRasterizer(options?: {
209
+ cacheDir?: string | null;
210
+ }): PptxRasterizer;
211
+ /**
212
+ * Build a LibreOffice-backed BATCH pptx rasterizer (#153): many independent
213
+ * single-slide presentations, one soffice launch, per-slide results. Shares
214
+ * the content-addressed disk cache with the single-slide rasterizer — the
215
+ * per-slide cache key is identical on both paths.
216
+ */
217
+ declare function createLibreOfficePptxBatchRasterizer(options?: {
218
+ cacheDir?: string | null;
219
+ }): PptxBatchRasterizer;
220
+
221
+ /**
222
+ * Make resolved fonts visible to the LibreOffice child process for the
223
+ * duration of one PDF conversion, then clean up.
224
+ *
225
+ * Linux/macOS: fontconfig + FONTCONFIG_FILE env var.
226
+ * Windows: GDI session registration via koffi (AddFontResourceW).
227
+ *
228
+ * The caller calls `stage(fonts, tempDir)` before spawning soffice, merges
229
+ * `envOverrides` into the child process env, waits for conversion, then
230
+ * awaits `cleanup()` regardless of success or failure.
231
+ *
232
+ * Two consumers today: the playground's LibreOffice PDF-preview converter
233
+ * (`@json-to-office/jto`) and the pptx rasterizer that backs docx `visual`
234
+ * components (`../pptx-rasterizer.ts`).
235
+ */
236
+
237
+ interface FontStageHandle {
238
+ /** Merged into the child process env. Empty object if nothing to stage. */
239
+ envOverrides: Record<string, string>;
240
+ /** Always call in a finally block. Safe to call multiple times (idempotent). */
241
+ cleanup(): Promise<void>;
242
+ }
243
+ interface FontStageOptions {
244
+ /**
245
+ * UserInstallation profile directories the soffice launch(es) will use.
246
+ * The macOS stager seeds its OnStartApp Python macro into EACH of them —
247
+ * a launch with an unseeded profile registers no fonts and silently falls
248
+ * back to system faces. Absent → `<tempDir>/user-profile`, the
249
+ * LibreOfficeConverterService convention.
250
+ */
251
+ profileDirs?: string[];
252
+ }
253
+ interface FontStager {
254
+ stage(fonts: ResolvedFont[], tempDir: string, options?: FontStageOptions): Promise<FontStageHandle>;
255
+ }
256
+
257
+ declare class NoopFontStager implements FontStager {
258
+ stage(_fonts?: unknown, _tempDir?: string, _options?: FontStageOptions): Promise<FontStageHandle>;
259
+ }
260
+
261
+ /**
262
+ * Linux + macOS: use fontconfig to expose staged TTFs to LibreOffice.
263
+ *
264
+ * Writes each resolved font to `<tempDir>/fonts/` and a minimal
265
+ * fontconfig.xml that includes that dir plus the system font config.
266
+ * LibreOffice honors the per-invocation FONTCONFIG_FILE env var.
267
+ *
268
+ * The caller removes the whole tempDir in its own finally block; `cleanup()`
269
+ * only has to undo the read-only freeze stage() puts on the fonts dir so
270
+ * that recursive rm can actually unlink.
271
+ */
272
+
273
+ declare class FontconfigStager implements FontStager {
274
+ stage(fonts: ResolvedFont[], tempDir: string, _options?: FontStageOptions): Promise<FontStageHandle>;
275
+ private pickSystemIncludes;
276
+ }
277
+
278
+ /**
279
+ * Windows: register staged TTFs with GDI via AddFontResourceW so the soffice
280
+ * child process finds them at startup. Forces LibreOffice onto the GDI
281
+ * backend via SAL_DISABLE_SKIA=1 — Skia/DirectWrite doesn't reliably pick
282
+ * up GDI-registered fonts on recent LO builds.
283
+ *
284
+ * Scope, precisely: `AddFontResourceW` adds to the **session** font table, not
285
+ * a private per-process one. That is deliberate and unavoidable here — the
286
+ * private variant (`AddFontResourceExW` with `FR_PRIVATE`) is visible only to
287
+ * the registering process, and the process that has to see these fonts is the
288
+ * `soffice` CHILD. Node stays alive for the full conversion so the fonts
289
+ * persist until cleanup, and GDI releases them on process exit if Node
290
+ * crashes, so nothing leaks past the process.
291
+ *
292
+ * KNOWN LIMITATION — concurrent conversions on one Windows host share that
293
+ * session table. Two conversions staging different bytes under the same
294
+ * synthesized family (say two documents that each embed their own "Inter")
295
+ * register two faces claiming one name, and which one GDI hands to soffice is
296
+ * then order-dependent. Staged FILES never collide — each carries a
297
+ * pid-plus-counter suffix — so this is a resolution ambiguity, not corruption.
298
+ *
299
+ * Not fixed here because the only correct fix is a host-wide lease held from
300
+ * stage() through cleanup(), which serializes every Windows conversion; that
301
+ * is a real throughput cost for a risk the deployed images do not carry (both
302
+ * production containers are Linux/fontconfig, where staging is per-process via
303
+ * FONTCONFIG_FILE and cannot collide). It bites a Windows host running
304
+ * concurrent conversions — worth a lease if that becomes a supported topology.
305
+ */
306
+
307
+ declare class WindowsFontStager implements FontStager {
308
+ stage(fonts: ResolvedFont[], tempDir: string, _options?: FontStageOptions): Promise<FontStageHandle>;
309
+ }
310
+
311
+ /**
312
+ * macOS: make staged fonts visible to the soffice child process by
313
+ * registering them *inside* soffice via a Python macro bound to the
314
+ * `OnStartApp` event.
315
+ *
316
+ * Why this works on macOS 26. Apple tightened `CTFontManagerScopeSession`
317
+ * and `kCTFontManagerScopePersistent` to require signed+notarized callers
318
+ * (unsigned Node processes get `paramErr -50`), and `Process` scope only
319
+ * registers fonts for the calling process — so the Node server can't
320
+ * register fonts the soffice child sees. But Process scope DOES work from
321
+ * inside soffice. LibreOffice for macOS bundles Python 3.12 with `ctypes`,
322
+ * and UNO lets us bind a Python macro to application-start events. We seed
323
+ * a per-invocation UserInstallation profile with:
324
+ *
325
+ * - `user/Scripts/python/JtoFontRegister.py` — a ~20-line macro that
326
+ * reads `JTO_FONT_PATHS` from the process env and calls
327
+ * `CTFontManagerRegisterFontsForURL(url, kScopeProcess, NULL)` via
328
+ * ctypes for each path (Process = 1 in Core Text's scope enum).
329
+ * - `user/registrymodifications.xcu` — binds the macro to `OnStartApp`
330
+ * and sets `MacroSecurityLevel=0` for this ephemeral profile only.
331
+ *
332
+ * SECURITY INVARIANT — macro execution scope. The seeded profile disables
333
+ * soffice's macro-security prompt (`MacroSecurityLevel=0`) so our
334
+ * OnStartApp macro runs without a dialog. This profile MUST ONLY be used
335
+ * to open files this server just generated (i.e., well-formed outputs
336
+ * from `@json-to-office/core-*`). Piping user-supplied .docx/.pptx/.odt
337
+ * through a soffice invocation that uses this stager would execute any
338
+ * embedded VBA/Basic macros silently — an RCE primitive. If a future
339
+ * code path ever converts user-supplied documents (e.g. "PDF-ify my
340
+ * upload"), it must build a separate converter that does NOT share this
341
+ * profile, or call soffice with `--safe-mode` / a default profile.
342
+ *
343
+ * The pptx rasterizer (`./pptx-rasterizer.ts`) is the second consumer and
344
+ * upholds the same invariant: it only ever opens .pptx files this process
345
+ * just built from `@json-to-office/core-pptx` out of the request's own
346
+ * presentation JSON — never a user-supplied binary document.
347
+ *
348
+ * PROFILE DIRS. The macro is only reachable from the UserInstallation
349
+ * profile it was seeded into, so `options.profileDirs` must list EVERY
350
+ * profile the caller will launch soffice with. The converter uses exactly
351
+ * one (`<tempDir>/user-profile`, the default here); the rasterizer uses a
352
+ * batch profile plus one per isolated retry, and a launch against an
353
+ * unseeded profile registers nothing and silently falls back.
354
+ *
355
+ * Flow: the converter spawns
356
+ * `soffice --headless -env:UserInstallation=file://<tempDir>/user-profile ...`
357
+ * with env `{ JTO_FONT_PATHS: "<ttf1>:<ttf2>:..." }`. LO boots, reads our
358
+ * seeded profile, fires `OnStartApp`, runs our macro, registers each
359
+ * staged TTF at Process scope in its own process. Font enumeration then
360
+ * resolves the synthetic family names (`Inter Bold`, `Source Code Pro
361
+ * Medium`, …) against those Process-scope registrations. The PDF export
362
+ * ships the correct glyphs.
363
+ *
364
+ * Elegant side-effects:
365
+ * - Nothing outside the converter's `tempDir` is touched. The user's
366
+ * real `~/Library/Fonts` and `~/Library/Application Support/LibreOffice`
367
+ * stay untouched.
368
+ * - Cleanup is the converter's `fs.rm(tempDir, …)` — no orphan sweep
369
+ * needed. If Node crashes mid-conversion, the per-invocation tempDir
370
+ * is reaped by macOS tmpreaper (or the OS's next boot cleanup).
371
+ * - No DYLD injection, no notarized helper, no filesystem-scan races.
372
+ *
373
+ * Failure mode: if LibreOffice can't run the macro for any reason
374
+ * (macro-security policy applied at bootstrap before our XCU loads,
375
+ * Python not present in a minimal LO build, …) the soffice process still
376
+ * runs and produces a PDF — just with system-fallback fonts, exactly the
377
+ * pre-fix behavior. Non-catastrophic. Diagnose via stderr: the macro
378
+ * writes failures to stderr via `print(..., file=sys.stderr)`.
379
+ */
380
+
381
+ declare class MacOSCoreTextStager implements FontStager {
382
+ stage(fonts: ResolvedFont[], tempDir: string, options?: FontStageOptions): Promise<FontStageHandle>;
383
+ }
384
+
385
+ /**
386
+ * Factory entry point for the font staging pipeline shared by every
387
+ * LibreOffice launch in the toolchain: the playground's PDF preview
388
+ * converter (`@json-to-office/jto`) and the pptx rasterizer that backs docx
389
+ * `visual` components (`../pptx-rasterizer.ts`).
390
+ */
391
+
392
+ declare function getFontStager(platform?: NodeJS.Platform): FontStager;
393
+
394
+ type DiagnosticTone = 'default' | 'info' | 'success' | 'warning' | 'error' | 'muted';
395
+ type DiagnosticSink = (text: string, tone?: DiagnosticTone) => void;
396
+ /**
397
+ * Scope a sink to one operation. The CLI installs an Ink-backed one per
398
+ * task; the MCP server installs one that collects into its response.
399
+ */
400
+ declare function runWithDiagnosticSink<T>(sink: DiagnosticSink, callback: () => T): T;
401
+ /**
402
+ * This package writes to no stream of its own — a host may own stdout as a
403
+ * protocol channel — so with no sink installed the message is dropped.
404
+ */
405
+ declare function emitDiagnostic(text: string, tone?: DiagnosticTone): void;
406
+ /**
407
+ * Plain-text sink for hosts with no UI of their own. stderr, not stdout,
408
+ * so it stays safe to install alongside a stdout protocol stream.
409
+ */
410
+ declare const stderrDiagnosticSink: DiagnosticSink;
411
+
412
+ export { type DiagnosticSink, type DiagnosticTone, DocxFormatAdapter, type FontStageHandle, type FontStageOptions, type FontStager, FontconfigStager, type FormatAdapter, type FormatName, type GeneratorOptions, type GeneratorResult, MacOSCoreTextStager, NoopFontStager, PptxFormatAdapter, type RasterizerCacheStats, WindowsFontStager, clearRasterizerCache, createAdapter, createLibreOfficePptxBatchRasterizer, createLibreOfficePptxRasterizer, emitDiagnostic, getFontStager, getRasterizerCacheStats, runWithDiagnosticSink, stderrDiagnosticSink };