@bendyline/squisq-formats 1.4.0 → 1.4.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 +180 -43
- package/dist/{chunk-CRTC6DEX.js → chunk-46FMDOWZ.js} +2 -2
- package/dist/{chunk-ON232HJR.js → chunk-5LSSWZPU.js} +2 -2
- package/dist/{chunk-APSIAEXL.js → chunk-D7IFPWNZ.js} +87 -43
- package/dist/chunk-D7IFPWNZ.js.map +1 -0
- package/dist/chunk-DQAZR57U.js +33 -0
- package/dist/chunk-DQAZR57U.js.map +1 -0
- package/dist/chunk-EHLGMSTE.js +273 -0
- package/dist/chunk-EHLGMSTE.js.map +1 -0
- package/dist/{chunk-A3FHLTY5.js → chunk-MJGRI6XR.js} +6 -19
- package/dist/chunk-MJGRI6XR.js.map +1 -0
- package/dist/{chunk-JBNVE2GV.js → chunk-RFAPOKHJ.js} +9 -2
- package/dist/chunk-RFAPOKHJ.js.map +1 -0
- package/dist/chunk-RTS5XBZ7.js +526 -0
- package/dist/chunk-RTS5XBZ7.js.map +1 -0
- package/dist/{chunk-FMOIGA5T.js → chunk-SHLPSADL.js} +178 -5
- package/dist/chunk-SHLPSADL.js.map +1 -0
- package/dist/chunk-SSUPBUF5.js +15 -0
- package/dist/chunk-SSUPBUF5.js.map +1 -0
- package/dist/{chunk-BJQIJ4S4.js → chunk-WWBIKRNP.js} +17 -41
- package/dist/chunk-WWBIKRNP.js.map +1 -0
- package/dist/{chunk-I2GFTEOV.js → chunk-YESS7VY3.js} +77 -15
- package/dist/{chunk-I2GFTEOV.js.map → chunk-YESS7VY3.js.map} +1 -1
- package/dist/csv/index.d.ts +14 -1
- package/dist/csv/index.js +1 -1
- package/dist/docx/index.js +4 -3
- package/dist/epub/index.js +3 -2
- package/dist/html/index.d.ts +3 -0
- package/dist/html/index.js +3 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +26 -11
- package/dist/ooxml/index.d.ts +5 -1
- package/dist/ooxml/index.js +16 -12
- package/dist/pdf/index.js +2 -1
- package/dist/pptx/index.d.ts +18 -3
- package/dist/pptx/index.js +7 -4
- package/dist/registry/index.d.ts +167 -0
- package/dist/registry/index.js +17 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/xlsx/index.d.ts +44 -14
- package/dist/xlsx/index.js +4 -2
- package/package.json +7 -2
- package/src/__tests__/convert.test.ts +186 -0
- package/src/__tests__/csvImport.test.ts +34 -1
- package/src/__tests__/formatRegistry.test.ts +174 -0
- package/src/__tests__/lossyWarnings.test.ts +105 -0
- package/src/__tests__/pdfImport.test.ts +95 -1
- package/src/__tests__/pptxImport.test.ts +46 -1
- package/src/__tests__/roundTripAssets.test.ts +50 -0
- package/src/__tests__/roundTripMatrix.test.ts +10 -4
- package/src/__tests__/sharedContainer.test.ts +41 -0
- package/src/__tests__/sharedImages.test.ts +61 -0
- package/src/__tests__/xlsxExport.test.ts +164 -0
- package/src/csv/index.ts +25 -2
- package/src/docx/import.ts +6 -37
- package/src/html/imageUtils.ts +5 -19
- package/src/index.ts +29 -5
- package/src/ooxml/index.ts +3 -0
- package/src/ooxml/namespaces.ts +10 -0
- package/src/pdf/import.ts +145 -50
- package/src/pptx/import.ts +127 -10
- package/src/pptx/index.ts +1 -1
- package/src/registry/convert.ts +318 -0
- package/src/registry/defaultFormats.ts +323 -0
- package/src/registry/errors.ts +46 -0
- package/src/registry/index.ts +38 -0
- package/src/registry/registry.ts +48 -0
- package/src/registry/types.ts +107 -0
- package/src/shared/container.ts +28 -0
- package/src/shared/images.ts +44 -0
- package/src/xlsx/export.ts +253 -0
- package/src/xlsx/index.ts +10 -36
- package/dist/chunk-A3FHLTY5.js.map +0 -1
- package/dist/chunk-APSIAEXL.js.map +0 -1
- package/dist/chunk-BJQIJ4S4.js.map +0 -1
- package/dist/chunk-FMOIGA5T.js.map +0 -1
- package/dist/chunk-JBNVE2GV.js.map +0 -1
- package/dist/chunk-L53YIGYS.js +0 -141
- package/dist/chunk-L53YIGYS.js.map +0 -1
- package/dist/chunk-QSE7EWE7.js +0 -181
- package/dist/chunk-QSE7EWE7.js.map +0 -1
- /package/dist/{chunk-CRTC6DEX.js.map → chunk-46FMDOWZ.js.map} +0 -0
- /package/dist/{chunk-ON232HJR.js.map → chunk-5LSSWZPU.js.map} +0 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @bendyline/squisq-formats
|
|
2
2
|
|
|
3
|
-
Document format converters for Squisq. Import and export between Squisq's Markdown-based document model and common file formats — DOCX, PPTX,
|
|
3
|
+
Document format converters for Squisq. Import and export between Squisq's Markdown-based document model and common file formats — DOCX, PDF, HTML, EPUB, PPTX, XLSX, and CSV. All converters run in the browser or Node with no server or native binaries required.
|
|
4
4
|
|
|
5
5
|
Part of the [Squisq](https://github.com/bendyline/squisq) monorepo.
|
|
6
6
|
|
|
@@ -13,118 +13,255 @@ Part of the [Squisq](https://github.com/bendyline/squisq) monorepo.
|
|
|
13
13
|
npm install @bendyline/squisq-formats @bendyline/squisq
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
+
## How Conversion Works
|
|
17
|
+
|
|
18
|
+
All converters use Squisq's `MarkdownDocument` AST (from `@bendyline/squisq/markdown`) as the pivot format:
|
|
19
|
+
|
|
20
|
+
- **Import** parses a file into a `MarkdownDocument`. The `xxxToContainer` variants (DOCX, PDF, PPTX) also extract embedded images into a `ContentContainer` alongside the markdown.
|
|
21
|
+
- **Export** serializes a `MarkdownDocument` out to the target format. Each format also has a `docToXxx` / `xxxToDoc` wrapper that converts through `MarkdownDocument` internally.
|
|
22
|
+
|
|
23
|
+
Conversions preserve document structure and most of the flavor of the source — headings, text formatting, lists, tables, links, images — but they are **not lossless round-trips**. Per-format fidelity limits are listed below.
|
|
24
|
+
|
|
25
|
+
All exports that accept a `themeId` (DOCX, PDF, EPUB, PPTX, plain HTML) apply Squisq theme colors/typography, and fall back to the doc's frontmatter theme (`squisq-theme` / `themeId` / `theme` keys) when the option is omitted.
|
|
26
|
+
|
|
27
|
+
For a uniform, format-agnostic entry point over every converter, use the **format registry** and `convert()` (`@bendyline/squisq-formats/registry`, also re-exported from the root) — see [Programmatic `convert()`](#programmatic-convert) below.
|
|
28
|
+
|
|
16
29
|
## Supported Formats
|
|
17
30
|
|
|
18
|
-
| Format | Import
|
|
19
|
-
| --------------------- |
|
|
20
|
-
| **DOCX** (Word) | ✅
|
|
21
|
-
| **PDF** | ✅
|
|
22
|
-
| **HTML** |
|
|
23
|
-
| **EPUB** (e-book) | —
|
|
24
|
-
| **PPTX** (PowerPoint) |
|
|
25
|
-
| **XLSX** (Excel) |
|
|
31
|
+
| Format | Import | Export | Theme | Subpath |
|
|
32
|
+
| --------------------- | ------ | ------ | ----- | ------------------------------------- |
|
|
33
|
+
| **DOCX** (Word) | ✅ | ✅ | ✅ | `@bendyline/squisq-formats/docx` |
|
|
34
|
+
| **PDF** | ✅ | ✅ | ✅ | `@bendyline/squisq-formats/pdf` |
|
|
35
|
+
| **HTML** | ✅ | ✅ | ✅ | `@bendyline/squisq-formats/html` |
|
|
36
|
+
| **EPUB** (e-book) | — | ✅ | ✅ | `@bendyline/squisq-formats/epub` |
|
|
37
|
+
| **PPTX** (PowerPoint) | ✅ | ✅ | ✅ | `@bendyline/squisq-formats/pptx` |
|
|
38
|
+
| **XLSX** (Excel) | ✅ | ✅¹ | — | `@bendyline/squisq-formats/xlsx` |
|
|
39
|
+
| **CSV** | ✅ | ✅ | — | `@bendyline/squisq-formats/csv` |
|
|
40
|
+
| **Container ZIP** | ✅ | ✅ | — | `@bendyline/squisq-formats/container` |
|
|
26
41
|
|
|
27
|
-
|
|
42
|
+
¹ XLSX export is **tables-only** (v1.5) — see the [XLSX](#xlsx) section.
|
|
28
43
|
|
|
29
44
|
## Quick Examples
|
|
30
45
|
|
|
31
46
|
### DOCX
|
|
32
47
|
|
|
33
48
|
```ts
|
|
34
|
-
import {
|
|
49
|
+
import {
|
|
50
|
+
markdownDocToDocx,
|
|
51
|
+
docxToMarkdownDoc,
|
|
52
|
+
docxToContainer,
|
|
53
|
+
} from '@bendyline/squisq-formats/docx';
|
|
35
54
|
|
|
36
|
-
// Export: MarkdownDocument → DOCX (
|
|
37
|
-
const docxBytes = await markdownDocToDocx(markdownDoc);
|
|
55
|
+
// Export: MarkdownDocument → DOCX (ArrayBuffer)
|
|
56
|
+
const docxBytes = await markdownDocToDocx(markdownDoc, { title: 'Report', themeId: 'documentary' });
|
|
38
57
|
|
|
39
|
-
// Import: DOCX (ArrayBuffer) → MarkdownDocument
|
|
40
|
-
const
|
|
58
|
+
// Import: DOCX (ArrayBuffer | Blob) → MarkdownDocument
|
|
59
|
+
const imported = await docxToMarkdownDoc(docxBuffer, { extractImages: true });
|
|
60
|
+
|
|
61
|
+
// Import with assets: DOCX → ContentContainer (markdown + images/ files)
|
|
62
|
+
const container = await docxToContainer(docxBuffer);
|
|
41
63
|
```
|
|
42
64
|
|
|
65
|
+
**Fidelity:** import covers headings, paragraphs, inline formatting (bold, italic, strikethrough, inline code), hyperlinks, lists, tables, blockquotes, code blocks, footnotes, and embedded images (opt-in via `extractImages`; always on for `docxToContainer`). Export embeds images only when provided via `options.images`; otherwise they appear as placeholder text.
|
|
66
|
+
|
|
43
67
|
### PDF
|
|
44
68
|
|
|
45
69
|
```ts
|
|
46
70
|
import {
|
|
47
71
|
markdownDocToPdf,
|
|
48
72
|
pdfToMarkdownDoc,
|
|
73
|
+
pdfToContainer,
|
|
49
74
|
configurePdfWorker,
|
|
50
75
|
} from '@bendyline/squisq-formats/pdf';
|
|
51
76
|
|
|
52
|
-
// Configure the PDF.js worker (
|
|
77
|
+
// Configure the PDF.js worker (needed for import in some environments)
|
|
53
78
|
configurePdfWorker('/pdf.worker.min.mjs');
|
|
54
79
|
|
|
55
|
-
// Export: MarkdownDocument → PDF (
|
|
56
|
-
const pdfBytes = await markdownDocToPdf(markdownDoc);
|
|
80
|
+
// Export: MarkdownDocument → PDF (ArrayBuffer)
|
|
81
|
+
const pdfBytes = await markdownDocToPdf(markdownDoc, { pageSize: 'a4' });
|
|
57
82
|
|
|
58
|
-
// Import: PDF (
|
|
59
|
-
const
|
|
83
|
+
// Import: PDF → MarkdownDocument (heuristic structure detection)
|
|
84
|
+
const imported = await pdfToMarkdownDoc(pdfBuffer);
|
|
85
|
+
|
|
86
|
+
// Import with assets: PDF → ContentContainer (markdown + extracted images)
|
|
87
|
+
const container = await pdfToContainer(pdfBuffer);
|
|
60
88
|
```
|
|
61
89
|
|
|
90
|
+
**Fidelity:** export uses pdf-lib's standard 14 fonts (`themeId` affects colors only). PDF has no semantic structure, so import is heuristic and best-effort: headings are detected by font size, and tables / code blocks / blockquotes / links via the `detectTables` / `detectCodeBlocks` / `detectBlockquotes` / `detectLinks` options (all default true). `pdfToMarkdownDoc` is text-only; `pdfToContainer` also extracts embedded images, placed **by page** — each image is inserted after the last content block from its page (image-only pages fall back to the nearest preceding page with content, else the document end). Placement is page-level only. Image extraction needs a browser canvas to encode PNG — **under Node it is skipped** (with a `console.warn`) and no images are emitted.
|
|
91
|
+
|
|
62
92
|
### HTML
|
|
63
93
|
|
|
64
94
|
```ts
|
|
65
|
-
import {
|
|
95
|
+
import { PLAYER_BUNDLE } from '@bendyline/squisq-react/standalone-source';
|
|
96
|
+
import {
|
|
97
|
+
docToHtml,
|
|
98
|
+
docToHtmlZip,
|
|
99
|
+
markdownDocToPlainHtml,
|
|
100
|
+
htmlToMarkdownDoc,
|
|
101
|
+
} from '@bendyline/squisq-formats/html';
|
|
102
|
+
|
|
103
|
+
// Interactive player export: Doc → single self-contained HTML string
|
|
104
|
+
// (player inlined, images as data URIs, timer-based playback — no audio)
|
|
105
|
+
const html = docToHtml(doc, { playerScript: PLAYER_BUNDLE, images });
|
|
106
|
+
|
|
107
|
+
// Interactive player export: Doc → ZIP Blob (external assets + optional audio)
|
|
108
|
+
const zipBlob = await docToHtmlZip(doc, { playerScript: PLAYER_BUNDLE, images, audio });
|
|
66
109
|
|
|
67
|
-
//
|
|
68
|
-
const
|
|
110
|
+
// Static export: MarkdownDocument → semantic HTML page (no player)
|
|
111
|
+
const page = markdownDocToPlainHtml(markdownDoc, { themeId: 'warm-earth' });
|
|
69
112
|
|
|
70
|
-
//
|
|
71
|
-
const
|
|
113
|
+
// Import: HTML → MarkdownDocument (sanitized by default)
|
|
114
|
+
const imported = await htmlToMarkdownDoc(htmlString);
|
|
72
115
|
```
|
|
73
116
|
|
|
117
|
+
The subpath also exports `markdownDocsToPlainHtmlBundle` / `markdownDocsToHtmlBundle` (recursive multi-doc ZIP bundles with `.md` links rewritten to `.html`), `htmlToMarkdownDocSync` / `htmlToMarkdown`, and helpers (`collectImagePaths`, `collectLinkRefs`, `inferMimeType`).
|
|
118
|
+
|
|
74
119
|
### EPUB
|
|
75
120
|
|
|
76
121
|
```ts
|
|
77
122
|
import { markdownDocToEpub } from '@bendyline/squisq-formats/epub';
|
|
78
123
|
|
|
79
|
-
// Export: MarkdownDocument → EPUB (ArrayBuffer)
|
|
124
|
+
// Export: MarkdownDocument → EPUB 3 (ArrayBuffer). No import path.
|
|
80
125
|
const epubBytes = await markdownDocToEpub(markdownDoc, {
|
|
81
126
|
title: 'My Book',
|
|
82
127
|
author: 'Jane Doe',
|
|
128
|
+
images, // Map<string, ArrayBuffer> — embedded when provided
|
|
129
|
+
coverImage, // optional JPEG/PNG
|
|
83
130
|
});
|
|
84
131
|
```
|
|
85
132
|
|
|
133
|
+
Chapters split at H1/H2 boundaries. Providing `audio` + `audioSegments` generates EPUB 3 Media Overlays (SMIL) for synchronized narration.
|
|
134
|
+
|
|
86
135
|
### PPTX
|
|
87
136
|
|
|
88
137
|
```ts
|
|
89
|
-
import {
|
|
138
|
+
import {
|
|
139
|
+
markdownDocToPptx,
|
|
140
|
+
pptxToMarkdownDoc,
|
|
141
|
+
pptxToContainer,
|
|
142
|
+
} from '@bendyline/squisq-formats/pptx';
|
|
90
143
|
|
|
91
144
|
// Export: MarkdownDocument → PPTX (ArrayBuffer)
|
|
92
|
-
//
|
|
93
|
-
const pptxBytes = await markdownDocToPptx(markdownDoc);
|
|
145
|
+
// slideBreak: 'h1' | 'h2' (default — H1 and H2 both break) | 'heading'
|
|
146
|
+
const pptxBytes = await markdownDocToPptx(markdownDoc, { slideBreak: 'h2', images });
|
|
147
|
+
|
|
148
|
+
// Import: PPTX → MarkdownDocument (add { extractImages: true } to reference slide images)
|
|
149
|
+
const imported = await pptxToMarkdownDoc(pptxBuffer);
|
|
150
|
+
|
|
151
|
+
// Import with assets: PPTX → ContentContainer (markdown + extracted slide images)
|
|
152
|
+
const container = await pptxToContainer(pptxBuffer);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**Fidelity:** export preserves inline formatting as DrawingML runs and embeds images when provided via `options.images`. Import reads slide order from `ppt/presentation.xml` and converts each slide's title (→ H2), body text (→ bullet list), and tables. **Slide-image extraction (v1.5):** import can now extract slide-level `<p:pic>` bitmaps into `images/` — `pptxToContainer` returns a container with those files and forces `extractImages: true`, while `pptxToMarkdownDoc` leaves it off by default. Honest limit: only slide-level `<p:pic>` bitmaps are extracted — layout/master images, charts, SmartArt, and picture-fills are **not**.
|
|
156
|
+
|
|
157
|
+
### CSV
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
import { csvToMarkdownDoc, markdownDocToCsv, parseCsv } from '@bendyline/squisq-formats/csv';
|
|
161
|
+
|
|
162
|
+
// Import: CSV (string | ArrayBuffer | Blob) → single-table MarkdownDocument
|
|
163
|
+
const tableDoc = await csvToMarkdownDoc(csvText, { delimiter: ',', hasHeader: true });
|
|
164
|
+
|
|
165
|
+
// Export: serializes one table node to CSV text (the first by default)
|
|
166
|
+
const csv = markdownDocToCsv(markdownDoc);
|
|
167
|
+
// Pick another table in a multi-table document:
|
|
168
|
+
const second = markdownDocToCsv(markdownDoc, { tableIndex: 1 });
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Self-contained RFC-4180 parser/serializer (not OOXML). Cell formatting is flattened to plain text on export. Export emits a single table — the first, or `tableIndex` (zero-based). An out-of-range `tableIndex` throws; a table-less document exports to an empty string.
|
|
172
|
+
|
|
173
|
+
### XLSX
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
import { xlsxToMarkdownDoc, markdownDocToXlsx } from '@bendyline/squisq-formats/xlsx';
|
|
177
|
+
|
|
178
|
+
// Import: XLSX → MarkdownDocument (one table per sheet; or pick one via `sheet`)
|
|
179
|
+
const imported = await xlsxToMarkdownDoc(xlsxBuffer, { sheet: 0 });
|
|
180
|
+
|
|
181
|
+
// Export: MarkdownDocument → XLSX (ArrayBuffer) — one worksheet per markdown table
|
|
182
|
+
const xlsxBytes = await markdownDocToXlsx(markdownDoc);
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**Export is implemented (v1.5) with tables-only fidelity:** every markdown `table` becomes one worksheet, named after the nearest preceding heading (auto-named `Sheet1`, `Sheet2`, … otherwise). All non-table content is dropped. Numeric-looking cells are written as numbers; everything else as inline strings. A document with no tables yields a single empty (but valid) sheet — export never throws. `markdownDocToXlsx` / `docToXlsx` now return `Promise<ArrayBuffer>` (previously threw / returned `Blob`).
|
|
186
|
+
|
|
187
|
+
### Container ZIP
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
import { containerToZip, zipToContainer } from '@bendyline/squisq-formats/container';
|
|
191
|
+
|
|
192
|
+
// ContentContainer → ZIP Blob (paths preserved)
|
|
193
|
+
const zipBlob = await containerToZip(container);
|
|
194
|
+
|
|
195
|
+
// ZIP → MemoryContentContainer (directories skipped, path traversal rejected)
|
|
196
|
+
const container2 = await zipToContainer(zipData);
|
|
94
197
|
```
|
|
95
198
|
|
|
96
199
|
### Doc-level Convenience Functions
|
|
97
200
|
|
|
98
|
-
Each format also exports `Doc`-level wrappers that handle the Markdown↔Doc conversion internally:
|
|
201
|
+
Each format also exports `Doc`-level wrappers that handle the Markdown ↔ Doc conversion internally:
|
|
99
202
|
|
|
100
203
|
```ts
|
|
101
204
|
import { docToDocx, docxToDoc } from '@bendyline/squisq-formats/docx';
|
|
102
205
|
import { docToPdf, pdfToDoc } from '@bendyline/squisq-formats/pdf';
|
|
103
|
-
import { docToPptx } from '@bendyline/squisq-formats/pptx';
|
|
206
|
+
import { docToPptx, pptxToDoc } from '@bendyline/squisq-formats/pptx';
|
|
207
|
+
import { docToEpub } from '@bendyline/squisq-formats/epub';
|
|
208
|
+
import { csvToDoc } from '@bendyline/squisq-formats/csv';
|
|
209
|
+
import { xlsxToDoc, docToXlsx } from '@bendyline/squisq-formats/xlsx';
|
|
104
210
|
```
|
|
105
211
|
|
|
212
|
+
## Programmatic `convert()`
|
|
213
|
+
|
|
214
|
+
`@bendyline/squisq-formats/registry` (also re-exported from the package root) is a uniform, format-agnostic front door over every converter. Each format is a `FormatDefinition` that knows how to import raw bytes into the `MarkdownDocument` pivot and/or export back out; `convert()` normalizes any source into a `Doc`, optionally applies a theme/transform, and hands off to the target exporter. Every result comes back as the same `ConversionResult` (`bytes` + `mimeType` + `suggestedFilename` + `warnings`). Converter modules load lazily, so importing the registry never eagerly bundles a heavy converter.
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
import { convert } from '@bendyline/squisq-formats/registry';
|
|
218
|
+
|
|
219
|
+
const result = await convert(
|
|
220
|
+
{ kind: 'markdown', markdown: '# Hello', baseName: 'greeting' },
|
|
221
|
+
'docx',
|
|
222
|
+
);
|
|
223
|
+
// result.bytes, result.mimeType, result.suggestedFilename === 'greeting.docx'
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Sources are `{ kind: 'bytes' | 'markdown' | 'doc', … }`; byte sources are format-sniffed by magic bytes + extension (pass `from` to skip). Failures throw a structured `ConversionError` with a stable `code` (`unknown-format`, `unsupported-input`, `unsupported-output`, `invalid-input`, `missing-dependency`, `conversion-failed`) plus a `hint`.
|
|
227
|
+
|
|
228
|
+
**Player-embedding HTML export (`html` / `htmlzip`)** needs the standalone player bundle. Pass `resolvePlayerScript`, or you'll get a `missing-dependency` error:
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
const html = await convert({ kind: 'markdown', markdown: src }, 'html', {
|
|
232
|
+
resolvePlayerScript: () =>
|
|
233
|
+
import('@bendyline/squisq-react/standalone-source').then((m) => m.PLAYER_BUNDLE),
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Also exported: `createRegistry` / `defaultRegistry` / `defaultFormats`, `BUILTIN_FORMAT_IDS`, `ConversionError`, and the `ConvertSource` / `ConvertOptions` / `ConversionResult` / `FormatDefinition` / `FormatRegistry` types.
|
|
238
|
+
|
|
106
239
|
## Subpath Exports
|
|
107
240
|
|
|
108
|
-
| Subpath
|
|
109
|
-
|
|
|
110
|
-
| `@bendyline/squisq-formats/docx`
|
|
111
|
-
| `@bendyline/squisq-formats/pdf`
|
|
112
|
-
| `@bendyline/squisq-formats/html`
|
|
113
|
-
| `@bendyline/squisq-formats/epub`
|
|
114
|
-
| `@bendyline/squisq-formats/
|
|
115
|
-
| `@bendyline/squisq-formats/
|
|
116
|
-
| `@bendyline/squisq-formats/
|
|
241
|
+
| Subpath | Contents |
|
|
242
|
+
| ------------------------------------- | ------------------------------------------------------------------------------ |
|
|
243
|
+
| `@bendyline/squisq-formats/docx` | DOCX import/export (+ `docxToContainer`) |
|
|
244
|
+
| `@bendyline/squisq-formats/pdf` | PDF import/export (+ `pdfToContainer`, `configurePdfWorker`) |
|
|
245
|
+
| `@bendyline/squisq-formats/html` | Player HTML export, static plain-HTML export + bundles, HTML import |
|
|
246
|
+
| `@bendyline/squisq-formats/epub` | EPUB 3 e-book export (with optional Media Overlays) |
|
|
247
|
+
| `@bendyline/squisq-formats/pptx` | PPTX export + import (text/lists/tables + slide images) |
|
|
248
|
+
| `@bendyline/squisq-formats/xlsx` | XLSX import + tables-only export |
|
|
249
|
+
| `@bendyline/squisq-formats/csv` | CSV import/export (RFC 4180) |
|
|
250
|
+
| `@bendyline/squisq-formats/ooxml` | Shared OOXML infrastructure (package reader/writer, XML utilities, namespaces) |
|
|
251
|
+
| `@bendyline/squisq-formats/container` | `ContentContainer` ↔ ZIP serialization |
|
|
252
|
+
| `@bendyline/squisq-formats/registry` | Format registry + programmatic `convert()` (also re-exported from the root) |
|
|
117
253
|
|
|
118
|
-
|
|
254
|
+
The package root re-exports the common converters; `./container`, the plain-HTML/bundle functions, `docxToContainer`, `pdfToContainer`, `PdfPageSize`, and the image utilities are subpath-only.
|
|
119
255
|
|
|
120
|
-
|
|
256
|
+
See the full [API Reference](../../docs/API.md#bendylinesquisq-formats) for every signature and options interface.
|
|
121
257
|
|
|
122
258
|
## Related Packages
|
|
123
259
|
|
|
124
260
|
| Package | Description |
|
|
125
261
|
| ---------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
|
|
126
262
|
| [@bendyline/squisq](https://www.npmjs.com/package/@bendyline/squisq) | Headless core — schemas, templates, spatial, markdown, storage |
|
|
127
|
-
| [@bendyline/squisq-react](https://www.npmjs.com/package/@bendyline/squisq-react) | React components for rendering docs
|
|
263
|
+
| [@bendyline/squisq-react](https://www.npmjs.com/package/@bendyline/squisq-react) | React components for rendering docs (+ `PLAYER_BUNDLE`) |
|
|
264
|
+
| [@bendyline/squisq-cli](https://www.npmjs.com/package/@bendyline/squisq-cli) | `squisq` CLI — batch conversion + MP4 rendering |
|
|
128
265
|
| [@bendyline/squisq-editor-react](https://www.npmjs.com/package/@bendyline/squisq-editor-react) | React editor with raw/WYSIWYG/preview modes |
|
|
129
266
|
|
|
130
267
|
## License
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
arrayBufferToBase64DataUrl,
|
|
3
3
|
extractFilename,
|
|
4
4
|
inferMimeType
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-MJGRI6XR.js";
|
|
6
6
|
|
|
7
7
|
// src/html/index.ts
|
|
8
8
|
import JSZip3 from "jszip";
|
|
@@ -1430,4 +1430,4 @@ export {
|
|
|
1430
1430
|
docToHtml,
|
|
1431
1431
|
docToHtmlZip
|
|
1432
1432
|
};
|
|
1433
|
-
//# sourceMappingURL=chunk-
|
|
1433
|
+
//# sourceMappingURL=chunk-46FMDOWZ.js.map
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
import {
|
|
8
8
|
extractFilename,
|
|
9
9
|
inferMimeType
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-MJGRI6XR.js";
|
|
11
11
|
|
|
12
12
|
// src/epub/export.ts
|
|
13
13
|
import JSZip from "jszip";
|
|
@@ -629,4 +629,4 @@ export {
|
|
|
629
629
|
markdownDocToEpub,
|
|
630
630
|
docToEpub
|
|
631
631
|
};
|
|
632
|
-
//# sourceMappingURL=chunk-
|
|
632
|
+
//# sourceMappingURL=chunk-5LSSWZPU.js.map
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildContainer
|
|
3
|
+
} from "./chunk-SSUPBUF5.js";
|
|
4
|
+
|
|
1
5
|
// src/pdf/export.ts
|
|
2
6
|
import { PDFDocument, StandardFonts, rgb } from "pdf-lib";
|
|
3
7
|
import { docToMarkdown, resolveThemeForDoc } from "@bendyline/squisq/doc";
|
|
@@ -713,7 +717,6 @@ function hexToRgb(hex) {
|
|
|
713
717
|
// src/pdf/import.ts
|
|
714
718
|
import { markdownToDoc } from "@bendyline/squisq/doc";
|
|
715
719
|
import { stringifyMarkdown } from "@bendyline/squisq/markdown";
|
|
716
|
-
import { MemoryContentContainer } from "@bendyline/squisq/storage";
|
|
717
720
|
async function pdfToMarkdownDoc(data, options = {}) {
|
|
718
721
|
const bytes = data instanceof Blob ? new Uint8Array(await data.arrayBuffer()) : data instanceof ArrayBuffer ? new Uint8Array(data) : data;
|
|
719
722
|
const textLines = await extractTextLines(bytes);
|
|
@@ -733,18 +736,16 @@ async function pdfToContainer(data, options = {}) {
|
|
|
733
736
|
const textLines = await extractTextLines(bytes);
|
|
734
737
|
const images = await extractImages(bytes);
|
|
735
738
|
const bodySize = options.bodyFontSize ?? detectBodyFontSize(textLines);
|
|
736
|
-
|
|
739
|
+
const blockPages = [];
|
|
740
|
+
let blocks = classifyLines(textLines, bodySize, options, blockPages);
|
|
737
741
|
if (images.length > 0) {
|
|
738
|
-
blocks = insertImageBlocks(blocks, images);
|
|
742
|
+
blocks = insertImageBlocks(blocks, blockPages, images);
|
|
739
743
|
}
|
|
740
744
|
const markdownDoc = { type: "document", children: blocks };
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
await container.writeFile(img.path, new Uint8Array(img.data), "image/png");
|
|
746
|
-
}
|
|
747
|
-
return container;
|
|
745
|
+
return buildContainer(
|
|
746
|
+
stringifyMarkdown(markdownDoc),
|
|
747
|
+
images.map((img) => [img.path, { data: img.data, mimeType: "image/png" }])
|
|
748
|
+
);
|
|
748
749
|
}
|
|
749
750
|
async function extractImages(data) {
|
|
750
751
|
if (typeof document === "undefined") return [];
|
|
@@ -836,20 +837,47 @@ function imageDataToPng(img) {
|
|
|
836
837
|
return null;
|
|
837
838
|
}
|
|
838
839
|
}
|
|
839
|
-
function
|
|
840
|
+
function imageParagraph(img) {
|
|
841
|
+
const imgNode = {
|
|
842
|
+
type: "image",
|
|
843
|
+
url: img.path,
|
|
844
|
+
alt: `Image ${img.path.replace("images/image", "").replace(".png", "")}`
|
|
845
|
+
};
|
|
846
|
+
return { type: "paragraph", children: [imgNode] };
|
|
847
|
+
}
|
|
848
|
+
function insertImageBlocks(blocks, blockPages, images) {
|
|
840
849
|
if (images.length === 0) return blocks;
|
|
841
|
-
|
|
850
|
+
if (blocks.length === 0) {
|
|
851
|
+
return images.map(imageParagraph);
|
|
852
|
+
}
|
|
853
|
+
const lastBlockIndexByPage = /* @__PURE__ */ new Map();
|
|
854
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
855
|
+
lastBlockIndexByPage.set(blockPages[i], i);
|
|
856
|
+
}
|
|
857
|
+
const pagesWithBlocks = [...lastBlockIndexByPage.keys()].sort((a, b) => a - b);
|
|
858
|
+
const lastIndex = blocks.length - 1;
|
|
859
|
+
const anchorFor = (page) => {
|
|
860
|
+
const direct = lastBlockIndexByPage.get(page);
|
|
861
|
+
if (direct !== void 0) return direct;
|
|
862
|
+
let anchor = -1;
|
|
863
|
+
for (const p of pagesWithBlocks) {
|
|
864
|
+
if (p < page) anchor = lastBlockIndexByPage.get(p);
|
|
865
|
+
else break;
|
|
866
|
+
}
|
|
867
|
+
return anchor === -1 ? lastIndex : anchor;
|
|
868
|
+
};
|
|
869
|
+
const insertAfter = /* @__PURE__ */ new Map();
|
|
842
870
|
for (const img of images) {
|
|
843
|
-
const
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
result.push(
|
|
871
|
+
const anchor = anchorFor(img.page);
|
|
872
|
+
const group = insertAfter.get(anchor);
|
|
873
|
+
if (group) group.push(imageParagraph(img));
|
|
874
|
+
else insertAfter.set(anchor, [imageParagraph(img)]);
|
|
875
|
+
}
|
|
876
|
+
const result = [];
|
|
877
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
878
|
+
result.push(blocks[i]);
|
|
879
|
+
const imgs = insertAfter.get(i);
|
|
880
|
+
if (imgs) result.push(...imgs);
|
|
853
881
|
}
|
|
854
882
|
return result;
|
|
855
883
|
}
|
|
@@ -966,8 +994,12 @@ function modeStr(arr) {
|
|
|
966
994
|
}
|
|
967
995
|
return maxVal;
|
|
968
996
|
}
|
|
969
|
-
function classifyLines(lines, bodySize, options) {
|
|
997
|
+
function classifyLines(lines, bodySize, options, blockPages) {
|
|
970
998
|
const blocks = [];
|
|
999
|
+
const pushBlock = (block, page) => {
|
|
1000
|
+
blocks.push(block);
|
|
1001
|
+
if (blockPages) blockPages.push(page);
|
|
1002
|
+
};
|
|
971
1003
|
const detectTables = options.detectTables !== false;
|
|
972
1004
|
const detectCodeBlocks = options.detectCodeBlocks !== false;
|
|
973
1005
|
const detectBlockquotes = options.detectBlockquotes !== false;
|
|
@@ -979,11 +1011,14 @@ function classifyLines(lines, bodySize, options) {
|
|
|
979
1011
|
const line = lines[i];
|
|
980
1012
|
if (line.fontSize >= IMPORT_HEADING_MIN_SIZE && line.fontSize > bodySize + 1) {
|
|
981
1013
|
const depth = sizeToHeadingDepth(line.fontSize);
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
1014
|
+
pushBlock(
|
|
1015
|
+
{
|
|
1016
|
+
type: "heading",
|
|
1017
|
+
depth,
|
|
1018
|
+
children: buildInlineNodes(line, options)
|
|
1019
|
+
},
|
|
1020
|
+
line.page
|
|
1021
|
+
);
|
|
987
1022
|
i++;
|
|
988
1023
|
continue;
|
|
989
1024
|
}
|
|
@@ -993,10 +1028,13 @@ function classifyLines(lines, bodySize, options) {
|
|
|
993
1028
|
codeLines.push(lines[i].text);
|
|
994
1029
|
i++;
|
|
995
1030
|
}
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1031
|
+
pushBlock(
|
|
1032
|
+
{
|
|
1033
|
+
type: "code",
|
|
1034
|
+
value: codeLines.join("\n")
|
|
1035
|
+
},
|
|
1036
|
+
line.page
|
|
1037
|
+
);
|
|
1000
1038
|
continue;
|
|
1001
1039
|
}
|
|
1002
1040
|
if (detectTables && i + 1 < lines.length) {
|
|
@@ -1004,7 +1042,7 @@ function classifyLines(lines, bodySize, options) {
|
|
|
1004
1042
|
if (tableLines > 0) {
|
|
1005
1043
|
const table = buildTable(lines.slice(i, i + tableLines), options);
|
|
1006
1044
|
if (table) {
|
|
1007
|
-
|
|
1045
|
+
pushBlock(table, line.page);
|
|
1008
1046
|
i += tableLines;
|
|
1009
1047
|
continue;
|
|
1010
1048
|
}
|
|
@@ -1014,7 +1052,7 @@ function classifyLines(lines, bodySize, options) {
|
|
|
1014
1052
|
const orderedMatch = line.text.match(IMPORT_ORDERED_PREFIX);
|
|
1015
1053
|
if (bulletMatch || orderedMatch) {
|
|
1016
1054
|
const listResult = consumeList(lines, i, typicalLeftMargin, bodySize, options);
|
|
1017
|
-
|
|
1055
|
+
pushBlock(listResult.list, line.page);
|
|
1018
1056
|
i = listResult.nextIndex;
|
|
1019
1057
|
continue;
|
|
1020
1058
|
}
|
|
@@ -1030,10 +1068,13 @@ function classifyLines(lines, bodySize, options) {
|
|
|
1030
1068
|
children: buildInlineNodes(ql, options)
|
|
1031
1069
|
})
|
|
1032
1070
|
);
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1071
|
+
pushBlock(
|
|
1072
|
+
{
|
|
1073
|
+
type: "blockquote",
|
|
1074
|
+
children: quoteBlocks
|
|
1075
|
+
},
|
|
1076
|
+
line.page
|
|
1077
|
+
);
|
|
1037
1078
|
continue;
|
|
1038
1079
|
}
|
|
1039
1080
|
const paraLines = [line];
|
|
@@ -1061,10 +1102,13 @@ function classifyLines(lines, bodySize, options) {
|
|
|
1061
1102
|
allInlines.push(...buildInlineNodes(paraLines[j], options));
|
|
1062
1103
|
}
|
|
1063
1104
|
if (allInlines.length > 0) {
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1105
|
+
pushBlock(
|
|
1106
|
+
{
|
|
1107
|
+
type: "paragraph",
|
|
1108
|
+
children: mergeAdjacentText(allInlines)
|
|
1109
|
+
},
|
|
1110
|
+
line.page
|
|
1111
|
+
);
|
|
1068
1112
|
}
|
|
1069
1113
|
}
|
|
1070
1114
|
return blocks;
|
|
@@ -1284,4 +1328,4 @@ export {
|
|
|
1284
1328
|
pdfToContainer,
|
|
1285
1329
|
configurePdfWorker
|
|
1286
1330
|
};
|
|
1287
|
-
//# sourceMappingURL=chunk-
|
|
1331
|
+
//# sourceMappingURL=chunk-D7IFPWNZ.js.map
|