@bendyline/squisq-formats 1.4.2 → 2.0.1
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/README.md +48 -2
- package/dist/chunk-2KPARF2P.js +99 -0
- package/dist/chunk-2KPARF2P.js.map +1 -0
- package/dist/{chunk-5LSSWZPU.js → chunk-4VUWTSGM.js} +72 -25
- package/dist/chunk-4VUWTSGM.js.map +1 -0
- package/dist/{chunk-YESS7VY3.js → chunk-ABVI556T.js} +154 -39
- package/dist/chunk-ABVI556T.js.map +1 -0
- package/dist/{chunk-WWBIKRNP.js → chunk-CRAVSMPZ.js} +13 -11
- package/dist/chunk-CRAVSMPZ.js.map +1 -0
- package/dist/chunk-FE6OJV6O.js +560 -0
- package/dist/chunk-FE6OJV6O.js.map +1 -0
- package/dist/{chunk-D7IFPWNZ.js → chunk-GVS2XXV6.js} +27 -26
- package/dist/chunk-GVS2XXV6.js.map +1 -0
- package/dist/{chunk-RTS5XBZ7.js → chunk-HTW2M27H.js} +341 -100
- package/dist/chunk-HTW2M27H.js.map +1 -0
- package/dist/{chunk-46FMDOWZ.js → chunk-LXYLOOST.js} +49 -17
- package/dist/chunk-LXYLOOST.js.map +1 -0
- package/dist/{chunk-MJGRI6XR.js → chunk-MLX2BOJC.js} +12 -7
- package/dist/chunk-MLX2BOJC.js.map +1 -0
- package/dist/chunk-NKAJPJ4G.js +106 -0
- package/dist/chunk-NKAJPJ4G.js.map +1 -0
- package/dist/{chunk-DQAZR57U.js → chunk-O3GVVND4.js} +1 -1
- package/dist/chunk-ODL3SSPT.js +18 -0
- package/dist/chunk-ODL3SSPT.js.map +1 -0
- package/dist/chunk-QRVN6A6E.js +127 -0
- package/dist/chunk-QRVN6A6E.js.map +1 -0
- package/dist/chunk-U32AG3G3.js +195 -0
- package/dist/chunk-U32AG3G3.js.map +1 -0
- package/dist/{chunk-EHLGMSTE.js → chunk-VJJM2SSH.js} +10 -8
- package/dist/chunk-VJJM2SSH.js.map +1 -0
- package/dist/chunk-VSYHZECT.js +778 -0
- package/dist/chunk-VSYHZECT.js.map +1 -0
- package/dist/{chunk-SHLPSADL.js → chunk-WC7WULGV.js} +81 -189
- package/dist/chunk-WC7WULGV.js.map +1 -0
- package/dist/chunk-XKUMNGBW.js +131 -0
- package/dist/chunk-XKUMNGBW.js.map +1 -0
- package/dist/chunk-YRT7GQ5Y.js +28 -0
- package/dist/chunk-YRT7GQ5Y.js.map +1 -0
- package/dist/container/index.d.ts +13 -3
- package/dist/container/index.js +23 -20
- package/dist/container/index.js.map +1 -1
- package/dist/docx/index.d.ts +6 -2
- package/dist/docx/index.js +5 -3
- package/dist/epub/index.d.ts +3 -1
- package/dist/epub/index.js +3 -3
- package/dist/export-D2NkylDT.d.ts +69 -0
- package/dist/extract-H6RXJMHP.js +15 -0
- package/dist/extract-H6RXJMHP.js.map +1 -0
- package/dist/html/index.d.ts +15 -85
- package/dist/html/index.js +3 -3
- package/dist/images-7FBWPKE3.js +7 -0
- package/dist/images-7FBWPKE3.js.map +1 -0
- package/dist/import-DTkDxHmZ.d.ts +84 -0
- package/dist/import-K8mfc0fz.d.ts +136 -0
- package/dist/index.d.ts +14 -4
- package/dist/index.js +33 -12
- package/dist/infer/index.d.ts +138 -0
- package/dist/infer/index.js +28 -0
- package/dist/infer/index.js.map +1 -0
- package/dist/layouts-BHrgZ5FS.d.ts +150 -0
- package/dist/layouts-QVPK3ZCU.js +12 -0
- package/dist/layouts-QVPK3ZCU.js.map +1 -0
- package/dist/mapTheme-IR27S6IV.js +11 -0
- package/dist/mapTheme-IR27S6IV.js.map +1 -0
- package/dist/ooxml/index.d.ts +21 -127
- package/dist/ooxml/index.js +31 -9
- package/dist/pdf/index.d.ts +3 -1
- package/dist/pdf/index.js +1 -1
- package/dist/pptx/index.d.ts +8 -108
- package/dist/pptx/index.js +13 -4
- package/dist/reader-B9L8Ucbj.d.ts +140 -0
- package/dist/registry/index.d.ts +60 -8
- package/dist/registry/index.js +9 -4
- package/dist/themeReader-DJKErl_j.d.ts +56 -0
- package/dist/xlsx/index.d.ts +6 -66
- package/dist/xlsx/index.js +4 -2
- package/dist/zipLimits-BOKCB7qk.d.ts +31 -0
- package/package.json +8 -2
- package/src/__tests__/container.test.ts +41 -0
- package/src/__tests__/convert.test.ts +310 -1
- package/src/__tests__/epub.test.ts +102 -1
- package/src/__tests__/html.test.ts +62 -0
- package/src/__tests__/inferTheme.test.ts +135 -0
- package/src/__tests__/lossyWarnings.test.ts +41 -0
- package/src/__tests__/ooxml.test.ts +76 -2
- package/src/__tests__/ooxmlCancellation.test.ts +113 -0
- package/src/__tests__/ooxmlThemeReader.test.ts +92 -0
- package/src/__tests__/plainHtml.test.ts +15 -1
- package/src/__tests__/plainHtmlBundle.test.ts +18 -0
- package/src/__tests__/pptxImport.test.ts +30 -0
- package/src/__tests__/pptxInferFixtures.ts +314 -0
- package/src/__tests__/pptxLayoutInfer.test.ts +395 -0
- package/src/__tests__/zipSafety.test.ts +317 -0
- package/src/container/index.ts +37 -32
- package/src/docx/export.ts +4 -2
- package/src/docx/import.ts +4 -3
- package/src/epub/export.ts +94 -25
- package/src/html/docsHtmlBundle.ts +6 -2
- package/src/html/htmlTemplate.ts +31 -6
- package/src/html/imageUtils.ts +14 -5
- package/src/html/index.ts +12 -1
- package/src/html/plainHtml.ts +7 -3
- package/src/html/plainHtmlBundle.ts +11 -9
- package/src/index.ts +23 -0
- package/src/infer/extract.ts +127 -0
- package/src/infer/index.ts +199 -0
- package/src/infer/mapTheme.ts +176 -0
- package/src/infer/types.ts +27 -0
- package/src/ooxml/index.ts +14 -0
- package/src/ooxml/readUtils.ts +44 -0
- package/src/ooxml/reader.ts +129 -29
- package/src/ooxml/themeReader.ts +197 -0
- package/src/ooxml/types.ts +8 -9
- package/src/ooxml/writer.ts +28 -10
- package/src/pdf/export.ts +4 -2
- package/src/pdf/import.ts +40 -34
- package/src/pptx/export.ts +4 -2
- package/src/pptx/import.ts +214 -32
- package/src/pptx/index.ts +14 -0
- package/src/pptx/layouts.ts +1222 -0
- package/src/registry/convert.ts +168 -53
- package/src/registry/defaultFormats.ts +135 -45
- package/src/registry/index.ts +6 -1
- package/src/registry/types.ts +69 -6
- package/src/shared/boundedZipArchive.ts +383 -0
- package/src/shared/fidelity.ts +130 -0
- package/src/shared/zipEntryCount.ts +151 -0
- package/src/shared/zipLimits.ts +296 -0
- package/src/shared/zipSafety.ts +19 -0
- package/src/xlsx/import.ts +3 -2
- package/dist/chunk-46FMDOWZ.js.map +0 -1
- package/dist/chunk-5LSSWZPU.js.map +0 -1
- package/dist/chunk-D7IFPWNZ.js.map +0 -1
- package/dist/chunk-EHLGMSTE.js.map +0 -1
- package/dist/chunk-MJGRI6XR.js.map +0 -1
- package/dist/chunk-RTS5XBZ7.js.map +0 -1
- package/dist/chunk-SHLPSADL.js.map +0 -1
- package/dist/chunk-WWBIKRNP.js.map +0 -1
- package/dist/chunk-YESS7VY3.js.map +0 -1
- /package/dist/{chunk-DQAZR57U.js.map → chunk-O3GVVND4.js.map} +0 -0
package/src/registry/convert.ts
CHANGED
|
@@ -9,9 +9,12 @@
|
|
|
9
9
|
|
|
10
10
|
import type { MarkdownDocument } from '@bendyline/squisq/markdown';
|
|
11
11
|
import type { ContentContainer } from '@bendyline/squisq/storage';
|
|
12
|
+
import type { TransformStyleInput, TransformStyleRegistry } from '@bendyline/squisq/transform';
|
|
12
13
|
import { ConversionError } from './errors.js';
|
|
13
14
|
import { defaultRegistry } from './registry.js';
|
|
15
|
+
import { openBoundedZipArchive, ZipSafetyError } from '../shared/zipSafety.js';
|
|
14
16
|
import type {
|
|
17
|
+
BuiltinFormatOptions,
|
|
15
18
|
ConversionResult,
|
|
16
19
|
ConvertOptions,
|
|
17
20
|
ConvertSource,
|
|
@@ -19,8 +22,22 @@ import type {
|
|
|
19
22
|
FormatId,
|
|
20
23
|
FormatRegistry,
|
|
21
24
|
NormalizedInput,
|
|
25
|
+
PreparedConversion,
|
|
26
|
+
PreparedExportOptions,
|
|
22
27
|
} from './types.js';
|
|
23
28
|
|
|
29
|
+
function markdownFormatOptions(options: ConvertOptions): BuiltinFormatOptions['md'] {
|
|
30
|
+
return (options.formatOptions?.md ?? {}) as BuiltinFormatOptions['md'];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function throwIfAborted(signal?: AbortSignal): void {
|
|
34
|
+
if (!signal?.aborted) return;
|
|
35
|
+
if (signal.reason !== undefined) throw signal.reason;
|
|
36
|
+
const error = new Error('Document conversion was cancelled.');
|
|
37
|
+
error.name = 'AbortError';
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
|
|
24
41
|
// ── Byte sniffing ───────────────────────────────────────────────────
|
|
25
42
|
|
|
26
43
|
function hasPrefix(bytes: Uint8Array, prefix: number[]): boolean {
|
|
@@ -41,13 +58,19 @@ const PDF_MAGIC = [0x25, 0x50, 0x44, 0x46]; // "%PDF"
|
|
|
41
58
|
* marker means `dbk`.
|
|
42
59
|
*/
|
|
43
60
|
async function sniffZip(bytes: Uint8Array): Promise<FormatId> {
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
throw new ConversionError('invalid-input', 'Input is not a readable ZIP archive.');
|
|
61
|
+
const archive = await openBoundedZipArchive(bytes).catch((cause: unknown) => {
|
|
62
|
+
if (cause instanceof ZipSafetyError && cause.code !== 'invalid-archive') throw cause;
|
|
63
|
+
throw new ConversionError('invalid-input', 'Input is not a readable ZIP archive.', { cause });
|
|
47
64
|
});
|
|
48
|
-
const contentTypes =
|
|
65
|
+
const contentTypes = archive.entries.find((entry) => entry.path === '[Content_Types].xml');
|
|
49
66
|
if (!contentTypes) return 'dbk';
|
|
50
|
-
const
|
|
67
|
+
const maxContentTypesBytes = 1024 * 1024;
|
|
68
|
+
if ((contentTypes.declaredSize ?? 0) > maxContentTypesBytes) {
|
|
69
|
+
throw new ConversionError('invalid-input', 'OOXML content-types metadata is too large.');
|
|
70
|
+
}
|
|
71
|
+
const xmlBytes = await archive.read(contentTypes.path, maxContentTypesBytes);
|
|
72
|
+
if (!xmlBytes) return 'dbk';
|
|
73
|
+
const xml = new TextDecoder().decode(xmlBytes);
|
|
51
74
|
if (xml.includes('wordprocessingml')) return 'docx';
|
|
52
75
|
if (xml.includes('presentationml')) return 'pptx';
|
|
53
76
|
if (xml.includes('spreadsheetml')) return 'xlsx';
|
|
@@ -80,7 +103,8 @@ async function detectByteFormat(
|
|
|
80
103
|
|
|
81
104
|
/** Lowercased extension without the leading dot (`report.DocX` → `docx`). */
|
|
82
105
|
function extractExt(filename: string): string {
|
|
83
|
-
const
|
|
106
|
+
const normalized = filename.replace(/\\/g, '/');
|
|
107
|
+
const base = normalized.split('/').pop() ?? normalized;
|
|
84
108
|
const dot = base.lastIndexOf('.');
|
|
85
109
|
if (dot <= 0) return '';
|
|
86
110
|
return base.slice(dot + 1).toLowerCase();
|
|
@@ -88,7 +112,8 @@ function extractExt(filename: string): string {
|
|
|
88
112
|
|
|
89
113
|
/** Basename without its final extension (`a/b/report.docx` → `report`). */
|
|
90
114
|
function baseNameOf(filename: string): string {
|
|
91
|
-
const
|
|
115
|
+
const normalized = filename.replace(/\\/g, '/');
|
|
116
|
+
const base = normalized.split('/').pop() ?? normalized;
|
|
92
117
|
const dot = base.lastIndexOf('.');
|
|
93
118
|
return dot > 0 ? base.slice(0, dot) : base;
|
|
94
119
|
}
|
|
@@ -147,7 +172,9 @@ async function normalizeBytes(
|
|
|
147
172
|
);
|
|
148
173
|
}
|
|
149
174
|
const text = await container.readDocument();
|
|
150
|
-
markdownDoc = text
|
|
175
|
+
markdownDoc = text
|
|
176
|
+
? parseMarkdown(text, markdownFormatOptions(options).parse)
|
|
177
|
+
: { type: 'document', children: [] };
|
|
151
178
|
} else {
|
|
152
179
|
// importDoc is guaranteed present by the guard above.
|
|
153
180
|
markdownDoc = await fromDef.importDoc!(buffer, options);
|
|
@@ -176,7 +203,9 @@ async function normalizeMarkdown(
|
|
|
176
203
|
const { stringifyMarkdown } = await import('@bendyline/squisq/markdown');
|
|
177
204
|
|
|
178
205
|
const markdownDoc =
|
|
179
|
-
typeof source.markdown === 'string'
|
|
206
|
+
typeof source.markdown === 'string'
|
|
207
|
+
? parseMarkdown(source.markdown, markdownFormatOptions(options).parse)
|
|
208
|
+
: source.markdown;
|
|
180
209
|
|
|
181
210
|
let container = source.container;
|
|
182
211
|
if (!container) {
|
|
@@ -228,13 +257,14 @@ async function normalize(
|
|
|
228
257
|
|
|
229
258
|
async function applyTransformStyle(
|
|
230
259
|
input: NormalizedInput,
|
|
231
|
-
transformStyle:
|
|
260
|
+
transformStyle: TransformStyleInput,
|
|
232
261
|
themeId?: string,
|
|
262
|
+
registry?: TransformStyleRegistry,
|
|
233
263
|
): Promise<string[]> {
|
|
234
264
|
const { applyTransform, extractDocImages } = await import('@bendyline/squisq/transform');
|
|
235
265
|
const { docToMarkdown, markdownToDoc } = await import('@bendyline/squisq/doc');
|
|
236
266
|
const images = extractDocImages(input.doc.blocks);
|
|
237
|
-
const result = applyTransform(input.doc, transformStyle, { themeId, images });
|
|
267
|
+
const result = applyTransform(input.doc, transformStyle, { themeId, images, registry });
|
|
238
268
|
input.doc = result.doc;
|
|
239
269
|
const markdownDoc = docToMarkdown(result.doc);
|
|
240
270
|
input.markdownDoc = markdownDoc;
|
|
@@ -254,65 +284,150 @@ async function applyTransformStyle(
|
|
|
254
284
|
return [];
|
|
255
285
|
}
|
|
256
286
|
|
|
257
|
-
// ── convert
|
|
287
|
+
// ── prepare / convert ───────────────────────────────────────────────
|
|
258
288
|
|
|
259
|
-
/**
|
|
260
|
-
|
|
261
|
-
*
|
|
262
|
-
* @param source - A bytes / markdown / doc source.
|
|
263
|
-
* @param to - The target format id (must be registered and support export).
|
|
264
|
-
* @param options - Conversion options (registry, theme, transform, …).
|
|
265
|
-
* @throws {@link ConversionError} on any failure, with a stable `code`.
|
|
266
|
-
*/
|
|
267
|
-
export async function convert(
|
|
289
|
+
/** Normalize and transform a source once, then safely export that snapshot many times. */
|
|
290
|
+
export async function prepareConversion(
|
|
268
291
|
source: ConvertSource,
|
|
269
|
-
to: FormatId,
|
|
270
292
|
options: ConvertOptions = {},
|
|
271
|
-
): Promise<
|
|
293
|
+
): Promise<PreparedConversion> {
|
|
294
|
+
return prepareConversionInternal(source, options);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async function prepareConversionInternal(
|
|
298
|
+
source: ConvertSource,
|
|
299
|
+
options: ConvertOptions,
|
|
300
|
+
failureFormat?: FormatId,
|
|
301
|
+
): Promise<PreparedConversion> {
|
|
302
|
+
throwIfAborted(options.signal);
|
|
272
303
|
const registry = options.registry ?? defaultRegistry();
|
|
273
304
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
305
|
+
let normalized: Normalized;
|
|
306
|
+
try {
|
|
307
|
+
normalized = await normalize(source, options, registry);
|
|
308
|
+
throwIfAborted(options.signal);
|
|
309
|
+
} catch (err: unknown) {
|
|
310
|
+
throwIfAborted(options.signal);
|
|
311
|
+
if (err instanceof ConversionError) throw err;
|
|
312
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
313
|
+
throw new ConversionError('invalid-input', message, {
|
|
314
|
+
format: options.from,
|
|
315
|
+
cause: err,
|
|
316
|
+
});
|
|
284
317
|
}
|
|
285
|
-
|
|
286
|
-
const { input, warnings } = await normalize(source, options, registry);
|
|
318
|
+
const { input, warnings } = normalized;
|
|
287
319
|
|
|
288
320
|
// Respect an existing doc theme; only fill it in from options when absent.
|
|
289
321
|
if (options.themeId && !input.doc.themeId) {
|
|
290
|
-
input.doc.themeId
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
if (options.transformStyle) {
|
|
294
|
-
warnings.push(
|
|
295
|
-
...(await applyTransformStyle(
|
|
296
|
-
input,
|
|
297
|
-
options.transformStyle,
|
|
298
|
-
options.themeId ?? input.doc.themeId,
|
|
299
|
-
)),
|
|
300
|
-
);
|
|
322
|
+
input.doc = { ...input.doc, themeId: options.themeId };
|
|
301
323
|
}
|
|
302
324
|
|
|
303
|
-
let result: ConversionResult;
|
|
304
325
|
try {
|
|
305
|
-
|
|
326
|
+
if (options.transformStyle) {
|
|
327
|
+
warnings.push(
|
|
328
|
+
...(await applyTransformStyle(
|
|
329
|
+
input,
|
|
330
|
+
options.transformStyle,
|
|
331
|
+
options.themeId ?? input.doc.themeId,
|
|
332
|
+
options.transformRegistry,
|
|
333
|
+
)),
|
|
334
|
+
);
|
|
335
|
+
throwIfAborted(options.signal);
|
|
336
|
+
}
|
|
306
337
|
} catch (err: unknown) {
|
|
338
|
+
throwIfAborted(options.signal);
|
|
307
339
|
if (err instanceof ConversionError) throw err;
|
|
308
340
|
const message = err instanceof Error ? err.message : String(err);
|
|
309
|
-
throw new ConversionError('conversion-failed', message, {
|
|
341
|
+
throw new ConversionError('conversion-failed', message, {
|
|
342
|
+
format: failureFormat,
|
|
343
|
+
cause: err,
|
|
344
|
+
});
|
|
310
345
|
}
|
|
311
346
|
|
|
312
|
-
const
|
|
347
|
+
const preparedWarnings = Object.freeze([...warnings]);
|
|
348
|
+
const preparedInput = { ...input };
|
|
349
|
+
return Object.freeze({
|
|
350
|
+
async convert(to: FormatId, targetOptions: PreparedExportOptions = {}) {
|
|
351
|
+
const target = requireExportTarget(registry, to);
|
|
352
|
+
const exportOptions = mergePreparedExportOptions(options, targetOptions);
|
|
353
|
+
throwIfAborted(exportOptions.signal);
|
|
354
|
+
let result: ConversionResult;
|
|
355
|
+
try {
|
|
356
|
+
result = await target.exportDoc!({ ...preparedInput }, exportOptions);
|
|
357
|
+
throwIfAborted(exportOptions.signal);
|
|
358
|
+
} catch (err: unknown) {
|
|
359
|
+
throwIfAborted(exportOptions.signal);
|
|
360
|
+
if (err instanceof ConversionError) throw err;
|
|
361
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
362
|
+
throw new ConversionError('conversion-failed', message, { format: to, cause: err });
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const ext = target.extensions[0]?.replace(/^\.+/, '') ?? to;
|
|
366
|
+
return {
|
|
367
|
+
...result,
|
|
368
|
+
suggestedFilename: `${preparedInput.baseName}.${ext}`,
|
|
369
|
+
warnings: [...preparedWarnings, ...result.warnings],
|
|
370
|
+
};
|
|
371
|
+
},
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function requireExportTarget(registry: FormatRegistry, to: FormatId): FormatDefinition {
|
|
376
|
+
const target = registry.get(to);
|
|
377
|
+
if (!target) {
|
|
378
|
+
throw new ConversionError('unknown-format', `Unknown target format "${to}".`, { format: to });
|
|
379
|
+
}
|
|
380
|
+
if (!target.exportDoc) {
|
|
381
|
+
throw new ConversionError(
|
|
382
|
+
'unsupported-output',
|
|
383
|
+
`Format "${target.label}" does not support export.`,
|
|
384
|
+
{ format: to, hint: 'This format is import-only.' },
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
return target;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function mergePreparedExportOptions(
|
|
391
|
+
prepared: ConvertOptions,
|
|
392
|
+
target: PreparedExportOptions,
|
|
393
|
+
): ConvertOptions {
|
|
313
394
|
return {
|
|
314
|
-
...
|
|
315
|
-
|
|
316
|
-
|
|
395
|
+
...prepared,
|
|
396
|
+
signal: mergeAbortSignals(prepared.signal, target.signal),
|
|
397
|
+
title: target.title ?? prepared.title,
|
|
398
|
+
resolvePlayerScript: target.resolvePlayerScript ?? prepared.resolvePlayerScript,
|
|
399
|
+
formatOptions:
|
|
400
|
+
target.formatOptions === undefined
|
|
401
|
+
? prepared.formatOptions
|
|
402
|
+
: { ...(prepared.formatOptions ?? {}), ...target.formatOptions },
|
|
317
403
|
};
|
|
318
404
|
}
|
|
405
|
+
|
|
406
|
+
function mergeAbortSignals(
|
|
407
|
+
preparedSignal?: AbortSignal,
|
|
408
|
+
targetSignal?: AbortSignal,
|
|
409
|
+
): AbortSignal | undefined {
|
|
410
|
+
if (!preparedSignal) return targetSignal;
|
|
411
|
+
if (!targetSignal || preparedSignal === targetSignal) return preparedSignal;
|
|
412
|
+
return AbortSignal.any([preparedSignal, targetSignal]);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Convert a source document to a target format.
|
|
417
|
+
*
|
|
418
|
+
* @param source - A bytes / markdown / doc source.
|
|
419
|
+
* @param to - The target format id (must be registered and support export).
|
|
420
|
+
* @param options - Conversion options (registry, theme, transform, …).
|
|
421
|
+
* @throws {@link ConversionError} on any failure, with a stable `code`.
|
|
422
|
+
*/
|
|
423
|
+
export async function convert(
|
|
424
|
+
source: ConvertSource,
|
|
425
|
+
to: FormatId,
|
|
426
|
+
options: ConvertOptions = {},
|
|
427
|
+
): Promise<ConversionResult> {
|
|
428
|
+
throwIfAborted(options.signal);
|
|
429
|
+
const registry = options.registry ?? defaultRegistry();
|
|
430
|
+
requireExportTarget(registry, to);
|
|
431
|
+
const prepared = await prepareConversionInternal(source, { ...options, registry }, to);
|
|
432
|
+
return prepared.convert(to);
|
|
433
|
+
}
|
|
@@ -11,8 +11,10 @@
|
|
|
11
11
|
import type { ContentContainer } from '@bendyline/squisq/storage';
|
|
12
12
|
import type { MarkdownDocument } from '@bendyline/squisq/markdown';
|
|
13
13
|
import { ConversionError } from './errors.js';
|
|
14
|
+
import { markdownFidelityWarnings } from '../shared/fidelity.js';
|
|
14
15
|
import type {
|
|
15
16
|
ConversionResult,
|
|
17
|
+
BuiltinFormatOptions,
|
|
16
18
|
ConvertOptions,
|
|
17
19
|
FormatDefinition,
|
|
18
20
|
NormalizedInput,
|
|
@@ -57,6 +59,13 @@ function resolveThemeId(input: NormalizedInput, options: ConvertOptions): string
|
|
|
57
59
|
return options.themeId ?? input.doc.themeId;
|
|
58
60
|
}
|
|
59
61
|
|
|
62
|
+
function optionsFor<K extends keyof BuiltinFormatOptions>(
|
|
63
|
+
options: ConvertOptions,
|
|
64
|
+
id: K,
|
|
65
|
+
): BuiltinFormatOptions[K] {
|
|
66
|
+
return (options.formatOptions?.[id] ?? {}) as BuiltinFormatOptions[K];
|
|
67
|
+
}
|
|
68
|
+
|
|
60
69
|
/** The markdown shape an exporter needs — reuse the source's, else derive it. */
|
|
61
70
|
async function markdownOf(input: NormalizedInput): Promise<MarkdownDocument> {
|
|
62
71
|
if (input.markdownDoc) return input.markdownDoc;
|
|
@@ -85,6 +94,34 @@ async function collectContainerImages(
|
|
|
85
94
|
return images;
|
|
86
95
|
}
|
|
87
96
|
|
|
97
|
+
async function collectContainerDocxImages(
|
|
98
|
+
container: ContentContainer,
|
|
99
|
+
): Promise<Map<string, { data: ArrayBuffer; contentType: string }>> {
|
|
100
|
+
const images = await collectContainerImages(container);
|
|
101
|
+
const { extToMime } = await import('../shared/images.js');
|
|
102
|
+
return new Map(
|
|
103
|
+
[...images].map(([path, data]) => [
|
|
104
|
+
path,
|
|
105
|
+
{ data, contentType: extToMime(path.split('.').pop() ?? '') },
|
|
106
|
+
]),
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Copy a container and make its primary document match the normalized source. */
|
|
111
|
+
async function snapshotContainerWithDocument(input: NormalizedInput): Promise<ContentContainer> {
|
|
112
|
+
const { MemoryContentContainer } = await import('@bendyline/squisq/storage');
|
|
113
|
+
const snapshot = new MemoryContentContainer();
|
|
114
|
+
const entries = await input.container.listFiles();
|
|
115
|
+
for (const entry of entries) {
|
|
116
|
+
const data = await input.container.readFile(entry.path);
|
|
117
|
+
if (data) await snapshot.writeFile(entry.path, new Uint8Array(data), entry.mimeType);
|
|
118
|
+
}
|
|
119
|
+
const { stringifyMarkdown } = await import('@bendyline/squisq/markdown');
|
|
120
|
+
const documentPath = (await input.container.getDocumentPath()) ?? 'index.md';
|
|
121
|
+
await snapshot.writeDocument(stringifyMarkdown(await markdownOf(input)), documentPath);
|
|
122
|
+
return snapshot;
|
|
123
|
+
}
|
|
124
|
+
|
|
88
125
|
/** Resolve the player IIFE bundle or throw a helpful missing-dependency error. */
|
|
89
126
|
async function requirePlayerScript(options: ConvertOptions, format: string): Promise<string> {
|
|
90
127
|
const script = await options.resolvePlayerScript?.();
|
|
@@ -102,6 +139,20 @@ function ok(bytes: Uint8Array, mimeType: string, warnings: string[] = []): Conve
|
|
|
102
139
|
return { bytes, mimeType, suggestedFilename: '', warnings };
|
|
103
140
|
}
|
|
104
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Narrow the untyped per-format escape hatch (`options.formatOptions.pptx`)
|
|
144
|
+
* into the PPTX importer's options. Theme + layout inference default ON;
|
|
145
|
+
* only an explicit `false` disables them.
|
|
146
|
+
*/
|
|
147
|
+
function pptxImportOptionsFrom(options: ConvertOptions): BuiltinFormatOptions['pptx'] {
|
|
148
|
+
const raw = optionsFor(options, 'pptx');
|
|
149
|
+
return {
|
|
150
|
+
...raw,
|
|
151
|
+
inferTheme: raw?.inferTheme !== false,
|
|
152
|
+
inferLayouts: raw?.inferLayouts !== false,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
105
156
|
// ── Definitions ─────────────────────────────────────────────────────
|
|
106
157
|
|
|
107
158
|
export function defaultFormats(): FormatDefinition[] {
|
|
@@ -110,13 +161,13 @@ export function defaultFormats(): FormatDefinition[] {
|
|
|
110
161
|
label: 'Markdown',
|
|
111
162
|
mimeType: MIME.md,
|
|
112
163
|
extensions: ['.md', '.markdown'],
|
|
113
|
-
async importDoc(data): Promise<MarkdownDocument> {
|
|
164
|
+
async importDoc(data, options): Promise<MarkdownDocument> {
|
|
114
165
|
const { parseMarkdown } = await import('@bendyline/squisq/markdown');
|
|
115
|
-
return parseMarkdown(new TextDecoder().decode(data));
|
|
166
|
+
return parseMarkdown(new TextDecoder().decode(data), optionsFor(options, 'md').parse);
|
|
116
167
|
},
|
|
117
|
-
async exportDoc(input): Promise<ConversionResult> {
|
|
168
|
+
async exportDoc(input, options): Promise<ConversionResult> {
|
|
118
169
|
const { stringifyMarkdown } = await import('@bendyline/squisq/markdown');
|
|
119
|
-
const text = stringifyMarkdown(await markdownOf(input));
|
|
170
|
+
const text = stringifyMarkdown(await markdownOf(input), optionsFor(options, 'md').stringify);
|
|
120
171
|
return ok(new TextEncoder().encode(text), MIME.md);
|
|
121
172
|
},
|
|
122
173
|
};
|
|
@@ -126,20 +177,28 @@ export function defaultFormats(): FormatDefinition[] {
|
|
|
126
177
|
label: 'Word (DOCX)',
|
|
127
178
|
mimeType: MIME.docx,
|
|
128
179
|
extensions: ['.docx'],
|
|
129
|
-
async importContainer(data): Promise<ContentContainer> {
|
|
180
|
+
async importContainer(data, options): Promise<ContentContainer> {
|
|
130
181
|
const { docxToContainer } = await import('../docx/index.js');
|
|
131
|
-
return docxToContainer(data);
|
|
182
|
+
return docxToContainer(data, optionsFor(options, 'docx'));
|
|
132
183
|
},
|
|
133
|
-
async importDoc(data): Promise<MarkdownDocument> {
|
|
184
|
+
async importDoc(data, options): Promise<MarkdownDocument> {
|
|
134
185
|
const { docxToMarkdownDoc } = await import('../docx/index.js');
|
|
135
|
-
return docxToMarkdownDoc(data);
|
|
186
|
+
return docxToMarkdownDoc(data, optionsFor(options, 'docx'));
|
|
136
187
|
},
|
|
137
188
|
async exportDoc(input, options): Promise<ConversionResult> {
|
|
138
189
|
const { markdownDocToDocx } = await import('../docx/index.js');
|
|
139
|
-
const
|
|
190
|
+
const raw = optionsFor(options, 'docx');
|
|
191
|
+
const markdownDoc = await markdownOf(input);
|
|
192
|
+
const containerImages = await collectContainerDocxImages(input.container);
|
|
193
|
+
const images = new Map([...containerImages, ...(raw.images ?? new Map())]);
|
|
194
|
+
const buf = await markdownDocToDocx(markdownDoc, {
|
|
195
|
+
...raw,
|
|
196
|
+
...(options.title !== undefined ? { title: options.title } : {}),
|
|
140
197
|
themeId: resolveThemeId(input, options),
|
|
198
|
+
themeRegistry: options.themeRegistry ?? raw.themeRegistry,
|
|
199
|
+
images,
|
|
141
200
|
});
|
|
142
|
-
return ok(await toBytes(buf), MIME.docx);
|
|
201
|
+
return ok(await toBytes(buf), MIME.docx, markdownFidelityWarnings(markdownDoc, 'docx'));
|
|
143
202
|
},
|
|
144
203
|
};
|
|
145
204
|
|
|
@@ -148,20 +207,25 @@ export function defaultFormats(): FormatDefinition[] {
|
|
|
148
207
|
label: 'PDF',
|
|
149
208
|
mimeType: MIME.pdf,
|
|
150
209
|
extensions: ['.pdf'],
|
|
151
|
-
async importContainer(data): Promise<ContentContainer> {
|
|
210
|
+
async importContainer(data, options): Promise<ContentContainer> {
|
|
152
211
|
const { pdfToContainer } = await import('../pdf/index.js');
|
|
153
|
-
return pdfToContainer(data);
|
|
212
|
+
return pdfToContainer(data, optionsFor(options, 'pdf'));
|
|
154
213
|
},
|
|
155
|
-
async importDoc(data): Promise<MarkdownDocument> {
|
|
214
|
+
async importDoc(data, options): Promise<MarkdownDocument> {
|
|
156
215
|
const { pdfToMarkdownDoc } = await import('../pdf/index.js');
|
|
157
|
-
return pdfToMarkdownDoc(data);
|
|
216
|
+
return pdfToMarkdownDoc(data, optionsFor(options, 'pdf'));
|
|
158
217
|
},
|
|
159
218
|
async exportDoc(input, options): Promise<ConversionResult> {
|
|
160
219
|
const { markdownDocToPdf } = await import('../pdf/index.js');
|
|
161
|
-
const
|
|
220
|
+
const raw = optionsFor(options, 'pdf');
|
|
221
|
+
const markdownDoc = await markdownOf(input);
|
|
222
|
+
const buf = await markdownDocToPdf(markdownDoc, {
|
|
223
|
+
...raw,
|
|
224
|
+
...(options.title !== undefined ? { title: options.title } : {}),
|
|
162
225
|
themeId: resolveThemeId(input, options),
|
|
226
|
+
themeRegistry: options.themeRegistry ?? raw.themeRegistry,
|
|
163
227
|
});
|
|
164
|
-
return ok(await toBytes(buf), MIME.pdf);
|
|
228
|
+
return ok(await toBytes(buf), MIME.pdf, markdownFidelityWarnings(markdownDoc, 'pdf'));
|
|
165
229
|
},
|
|
166
230
|
};
|
|
167
231
|
|
|
@@ -170,22 +234,28 @@ export function defaultFormats(): FormatDefinition[] {
|
|
|
170
234
|
label: 'PowerPoint (PPTX)',
|
|
171
235
|
mimeType: MIME.pptx,
|
|
172
236
|
extensions: ['.pptx'],
|
|
173
|
-
async importContainer(data): Promise<ContentContainer> {
|
|
237
|
+
async importContainer(data, options): Promise<ContentContainer> {
|
|
174
238
|
const { pptxToContainer } = await import('../pptx/index.js');
|
|
175
|
-
return pptxToContainer(data);
|
|
239
|
+
return pptxToContainer(data, pptxImportOptionsFrom(options));
|
|
176
240
|
},
|
|
177
|
-
async importDoc(data): Promise<MarkdownDocument> {
|
|
241
|
+
async importDoc(data, options): Promise<MarkdownDocument> {
|
|
178
242
|
const { pptxToMarkdownDoc } = await import('../pptx/index.js');
|
|
179
|
-
return pptxToMarkdownDoc(data);
|
|
243
|
+
return pptxToMarkdownDoc(data, pptxImportOptionsFrom(options));
|
|
180
244
|
},
|
|
181
245
|
async exportDoc(input, options): Promise<ConversionResult> {
|
|
182
246
|
const { markdownDocToPptx } = await import('../pptx/index.js');
|
|
183
|
-
const
|
|
184
|
-
const
|
|
247
|
+
const raw = optionsFor(options, 'pptx');
|
|
248
|
+
const markdownDoc = await markdownOf(input);
|
|
249
|
+
const containerImages = await collectContainerImages(input.container);
|
|
250
|
+
const images = new Map([...containerImages, ...(raw.images ?? new Map())]);
|
|
251
|
+
const buf = await markdownDocToPptx(markdownDoc, {
|
|
252
|
+
...raw,
|
|
253
|
+
...(options.title !== undefined ? { title: options.title } : {}),
|
|
185
254
|
themeId: resolveThemeId(input, options),
|
|
255
|
+
themeRegistry: options.themeRegistry ?? raw.themeRegistry,
|
|
186
256
|
images,
|
|
187
257
|
});
|
|
188
|
-
return ok(await toBytes(buf), MIME.pptx);
|
|
258
|
+
return ok(await toBytes(buf), MIME.pptx, markdownFidelityWarnings(markdownDoc, 'pptx'));
|
|
189
259
|
},
|
|
190
260
|
};
|
|
191
261
|
|
|
@@ -194,11 +264,11 @@ export function defaultFormats(): FormatDefinition[] {
|
|
|
194
264
|
label: 'Excel (XLSX)',
|
|
195
265
|
mimeType: MIME.xlsx,
|
|
196
266
|
extensions: ['.xlsx'],
|
|
197
|
-
async importDoc(data): Promise<MarkdownDocument> {
|
|
267
|
+
async importDoc(data, options): Promise<MarkdownDocument> {
|
|
198
268
|
const { xlsxToMarkdownDoc } = await import('../xlsx/index.js');
|
|
199
|
-
return xlsxToMarkdownDoc(data);
|
|
269
|
+
return xlsxToMarkdownDoc(data, optionsFor(options, 'xlsx'));
|
|
200
270
|
},
|
|
201
|
-
async exportDoc(input): Promise<ConversionResult> {
|
|
271
|
+
async exportDoc(input, options): Promise<ConversionResult> {
|
|
202
272
|
const { markdownDocToXlsx } = await import('../xlsx/index.js');
|
|
203
273
|
const markdownDoc = await markdownOf(input);
|
|
204
274
|
// XLSX fidelity is tables-only: `table` nodes become worksheets and
|
|
@@ -211,7 +281,10 @@ export function defaultFormats(): FormatDefinition[] {
|
|
|
211
281
|
omitted > 0
|
|
212
282
|
? [`XLSX export is tables-only; ${omitted} non-table block(s) were omitted.`]
|
|
213
283
|
: [];
|
|
214
|
-
const blob = await markdownDocToXlsx(markdownDoc
|
|
284
|
+
const blob = await markdownDocToXlsx(markdownDoc, {
|
|
285
|
+
...optionsFor(options, 'xlsx'),
|
|
286
|
+
...(options.title !== undefined ? { title: options.title } : {}),
|
|
287
|
+
});
|
|
215
288
|
return ok(await toBytes(blob), MIME.xlsx, warnings);
|
|
216
289
|
},
|
|
217
290
|
};
|
|
@@ -221,21 +294,22 @@ export function defaultFormats(): FormatDefinition[] {
|
|
|
221
294
|
label: 'CSV',
|
|
222
295
|
mimeType: MIME.csv,
|
|
223
296
|
extensions: ['.csv'],
|
|
224
|
-
async importDoc(data): Promise<MarkdownDocument> {
|
|
297
|
+
async importDoc(data, options): Promise<MarkdownDocument> {
|
|
225
298
|
const { csvToMarkdownDoc } = await import('../csv/index.js');
|
|
226
|
-
return csvToMarkdownDoc(data);
|
|
299
|
+
return csvToMarkdownDoc(data, optionsFor(options, 'csv'));
|
|
227
300
|
},
|
|
228
|
-
async exportDoc(input): Promise<ConversionResult> {
|
|
301
|
+
async exportDoc(input, options): Promise<ConversionResult> {
|
|
229
302
|
const { markdownDocToCsv } = await import('../csv/index.js');
|
|
230
303
|
const markdownDoc = await markdownOf(input);
|
|
304
|
+
const raw = optionsFor(options, 'csv');
|
|
231
305
|
const tableCount = markdownDoc.children.filter((n) => n.type === 'table').length;
|
|
232
306
|
const warnings: string[] = [];
|
|
233
|
-
if (tableCount > 1) {
|
|
307
|
+
if (tableCount > 1 && raw.tableIndex === undefined) {
|
|
234
308
|
warnings.push(
|
|
235
309
|
`Document has ${tableCount} tables; CSV export emitted only the first. Use the csv converter's tableIndex option to select another.`,
|
|
236
310
|
);
|
|
237
311
|
}
|
|
238
|
-
const text = markdownDocToCsv(markdownDoc);
|
|
312
|
+
const text = markdownDocToCsv(markdownDoc, raw);
|
|
239
313
|
return ok(new TextEncoder().encode(text), MIME.csv, warnings);
|
|
240
314
|
},
|
|
241
315
|
};
|
|
@@ -245,22 +319,26 @@ export function defaultFormats(): FormatDefinition[] {
|
|
|
245
319
|
label: 'HTML (single file)',
|
|
246
320
|
mimeType: MIME.html,
|
|
247
321
|
extensions: ['.html', '.htm'],
|
|
248
|
-
async importDoc(data): Promise<MarkdownDocument> {
|
|
322
|
+
async importDoc(data, options): Promise<MarkdownDocument> {
|
|
249
323
|
const { htmlToMarkdownDoc } = await import('../html/index.js');
|
|
250
|
-
return htmlToMarkdownDoc(data);
|
|
324
|
+
return htmlToMarkdownDoc(data, optionsFor(options, 'html'));
|
|
251
325
|
},
|
|
252
326
|
// importContainer omitted: HTML import already inlines images as data URIs,
|
|
253
327
|
// so there is nothing to extract into a container this wave.
|
|
254
328
|
async exportDoc(input, options): Promise<ConversionResult> {
|
|
255
329
|
const playerScript = await requirePlayerScript(options, 'html');
|
|
256
330
|
const { docToHtml } = await import('../html/index.js');
|
|
257
|
-
const
|
|
331
|
+
const raw = optionsFor(options, 'html');
|
|
332
|
+
const containerImages = await collectContainerImages(input.container);
|
|
333
|
+
const images = new Map([...containerImages, ...(raw.images ?? new Map())]);
|
|
258
334
|
const htmlText = docToHtml(input.doc, {
|
|
335
|
+
...raw,
|
|
259
336
|
playerScript,
|
|
260
337
|
images,
|
|
261
338
|
title: options.title ?? input.baseName,
|
|
262
|
-
mode: 'static',
|
|
339
|
+
mode: raw.mode ?? 'static',
|
|
263
340
|
themeId: resolveThemeId(input, options),
|
|
341
|
+
themeRegistry: options.themeRegistry ?? raw.themeRegistry,
|
|
264
342
|
});
|
|
265
343
|
return ok(new TextEncoder().encode(htmlText), MIME.html);
|
|
266
344
|
},
|
|
@@ -274,13 +352,17 @@ export function defaultFormats(): FormatDefinition[] {
|
|
|
274
352
|
async exportDoc(input, options): Promise<ConversionResult> {
|
|
275
353
|
const playerScript = await requirePlayerScript(options, 'htmlzip');
|
|
276
354
|
const { docToHtmlZip } = await import('../html/index.js');
|
|
277
|
-
const
|
|
355
|
+
const raw = optionsFor(options, 'htmlzip');
|
|
356
|
+
const containerImages = await collectContainerImages(input.container);
|
|
357
|
+
const images = new Map([...containerImages, ...(raw.images ?? new Map())]);
|
|
278
358
|
const blob = await docToHtmlZip(input.doc, {
|
|
359
|
+
...raw,
|
|
279
360
|
playerScript,
|
|
280
361
|
images,
|
|
281
362
|
title: options.title ?? input.baseName,
|
|
282
|
-
mode: 'static',
|
|
363
|
+
mode: raw.mode ?? 'static',
|
|
283
364
|
themeId: resolveThemeId(input, options),
|
|
365
|
+
themeRegistry: options.themeRegistry ?? raw.themeRegistry,
|
|
284
366
|
});
|
|
285
367
|
return ok(await toBytes(blob), MIME.zip);
|
|
286
368
|
},
|
|
@@ -293,13 +375,18 @@ export function defaultFormats(): FormatDefinition[] {
|
|
|
293
375
|
extensions: ['.epub'],
|
|
294
376
|
async exportDoc(input, options): Promise<ConversionResult> {
|
|
295
377
|
const { markdownDocToEpub } = await import('../epub/index.js');
|
|
296
|
-
const
|
|
297
|
-
const
|
|
378
|
+
const raw = optionsFor(options, 'epub');
|
|
379
|
+
const markdownDoc = await markdownOf(input);
|
|
380
|
+
const containerImages = await collectContainerImages(input.container);
|
|
381
|
+
const images = new Map([...containerImages, ...(raw.images ?? new Map())]);
|
|
382
|
+
const buf = await markdownDocToEpub(markdownDoc, {
|
|
383
|
+
...raw,
|
|
298
384
|
title: options.title ?? input.baseName,
|
|
299
385
|
themeId: resolveThemeId(input, options),
|
|
386
|
+
themeRegistry: options.themeRegistry ?? raw.themeRegistry,
|
|
300
387
|
images,
|
|
301
388
|
});
|
|
302
|
-
return ok(await toBytes(buf), MIME.epub);
|
|
389
|
+
return ok(await toBytes(buf), MIME.epub, markdownFidelityWarnings(markdownDoc, 'epub'));
|
|
303
390
|
},
|
|
304
391
|
};
|
|
305
392
|
|
|
@@ -308,13 +395,16 @@ export function defaultFormats(): FormatDefinition[] {
|
|
|
308
395
|
label: 'Squisq container (DBK)',
|
|
309
396
|
mimeType: MIME.zip,
|
|
310
397
|
extensions: ['.dbk', '.zip'],
|
|
311
|
-
async importContainer(data): Promise<ContentContainer> {
|
|
398
|
+
async importContainer(data, options): Promise<ContentContainer> {
|
|
312
399
|
const { zipToContainer } = await import('../container/index.js');
|
|
313
|
-
return zipToContainer(data);
|
|
400
|
+
return zipToContainer(data, optionsFor(options, 'dbk'));
|
|
314
401
|
},
|
|
315
|
-
async exportDoc(input): Promise<ConversionResult> {
|
|
402
|
+
async exportDoc(input, options): Promise<ConversionResult> {
|
|
316
403
|
const { containerToZip } = await import('../container/index.js');
|
|
317
|
-
const blob = await containerToZip(
|
|
404
|
+
const blob = await containerToZip(
|
|
405
|
+
await snapshotContainerWithDocument(input),
|
|
406
|
+
optionsFor(options, 'dbk'),
|
|
407
|
+
);
|
|
318
408
|
return ok(await toBytes(blob), MIME.zip);
|
|
319
409
|
},
|
|
320
410
|
};
|
package/src/registry/index.ts
CHANGED
|
@@ -27,6 +27,11 @@ export type {
|
|
|
27
27
|
FormatDefinition,
|
|
28
28
|
FormatRegistry,
|
|
29
29
|
ConvertSource,
|
|
30
|
+
BuiltinFormatOptions,
|
|
31
|
+
MarkdownFormatOptions,
|
|
32
|
+
DbkFormatOptions,
|
|
33
|
+
PreparedConversion,
|
|
34
|
+
PreparedExportOptions,
|
|
30
35
|
} from './types.js';
|
|
31
36
|
export { BUILTIN_FORMAT_IDS } from './types.js';
|
|
32
37
|
|
|
@@ -35,4 +40,4 @@ export type { ConversionErrorCode, ConversionErrorOptions } from './errors.js';
|
|
|
35
40
|
|
|
36
41
|
export { createRegistry, defaultRegistry } from './registry.js';
|
|
37
42
|
export { defaultFormats } from './defaultFormats.js';
|
|
38
|
-
export { convert } from './convert.js';
|
|
43
|
+
export { convert, prepareConversion } from './convert.js';
|