documents.js 1.102.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -5
- package/dist/convert/composition.cjs +15 -4
- package/dist/convert/composition.js +15 -4
- package/dist/convert/convert.cjs +6 -3
- package/dist/convert/convert.js +6 -3
- package/dist/convert/from-package.cjs +179 -2
- package/dist/convert/from-package.d.cts +3 -2
- package/dist/convert/from-package.d.ts +3 -2
- package/dist/convert/from-package.js +179 -3
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/layout/drawing.cjs +71 -9
- package/dist/layout/drawing.d.cts +8 -3
- package/dist/layout/drawing.d.ts +8 -3
- package/dist/layout/drawing.js +71 -10
- package/dist/layout/engine.cjs +55 -43
- package/dist/layout/engine.d.cts +2 -1
- package/dist/layout/engine.d.ts +2 -1
- package/dist/layout/engine.js +57 -45
- package/dist/layout/reconstruct.cjs +259 -104
- package/dist/layout/reconstruct.js +259 -104
- package/dist/layout/shared.cjs +47 -2
- package/dist/layout/shared.d.cts +15 -4
- package/dist/layout/shared.d.ts +15 -4
- package/dist/layout/shared.js +44 -4
- package/dist/layout/sheets.cjs +18 -13
- package/dist/layout/sheets.d.cts +4 -2
- package/dist/layout/sheets.d.ts +4 -2
- package/dist/layout/sheets.js +20 -16
- package/dist/layout/slides.cjs +35 -31
- package/dist/layout/slides.d.cts +3 -2
- package/dist/layout/slides.d.ts +3 -2
- package/dist/layout/slides.js +37 -33
- package/dist/layout/text-layout.cjs +2 -1
- package/dist/layout/text-layout.d.cts +14 -3
- package/dist/layout/text-layout.d.ts +14 -3
- package/dist/layout/text-layout.js +2 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -162,7 +162,7 @@ DocumentFormatSchema.parse(userSuppliedFormat); // throws a ZodError for anythin
|
|
|
162
162
|
|
|
163
163
|
### Intermediate `DocumentPackage`, JSON, and bytes
|
|
164
164
|
|
|
165
|
-
Every conversion function accepts an `onDocument` callback receiving the intermediate `DocumentPackage`
|
|
165
|
+
Every conversion function accepts an `onDocument` callback receiving the intermediate `DocumentPackage` — the fused unified tree of document-schema.js 3: `content` (whose own nodes carry `frames`, the rendered page positions the layout pass stamped onto them, in PDF user-space) plus `pages` (each rendered page's size, indexed to match every `frames[].pageIndex`). The port surfaces the same value as `package` on `ConversionResult`. For PDF-bypassing bridges, `pkg.pages` is always `undefined` and no node carries frames — no layout pass ran.
|
|
166
166
|
|
|
167
167
|
```ts
|
|
168
168
|
import { docxToPdf } from 'documents.js';
|
|
@@ -170,7 +170,9 @@ import { docxToPdf } from 'documents.js';
|
|
|
170
170
|
const pdfBytes = docxToPdf(docxBytes, {
|
|
171
171
|
onDocument: (pkg) => {
|
|
172
172
|
console.log(pkg.content.kind); // 'wordprocessing'
|
|
173
|
-
console.log(pkg.
|
|
173
|
+
console.log(pkg.pages?.length); // populated for every X-to-PDF/PDF-to-X conversion
|
|
174
|
+
const block = pkg.content.kind === 'wordprocessing' ? pkg.content.sections[0]?.blocks[0] : undefined;
|
|
175
|
+
console.log(block?.kind === 'paragraph' ? block.runs[0]?.frames : 'no paragraph'); // that run's rendered placements
|
|
174
176
|
},
|
|
175
177
|
});
|
|
176
178
|
```
|
|
@@ -187,7 +189,7 @@ const { kind, value } = documentFromJson(JSON.parse(readFileSync('converted.doc.
|
|
|
187
189
|
// kind: 'DocumentPackage' (here) | 'ContentDocument' | 'LayoutDocument'
|
|
188
190
|
```
|
|
189
191
|
|
|
190
|
-
`buildDocumentBytes` rebuilds any `DocumentFormat`'s bytes from a `DocumentPackage` — `'pdf'`
|
|
192
|
+
`buildDocumentBytes` rebuilds any `DocumentFormat`'s bytes from a `DocumentPackage` — `'pdf'` rebuilds the pdf-codec view from the package's own frames+pages (`layoutDocumentFromPackage`, a mechanical inverse walking the content tree and emitting `LayoutItem`s from each node's recorded placements; throwing if the package carries no `pages`), `'odf'` has no builder and throws, everything else rebuilds from the `ContentDocument` half. `layoutDocumentFromPackage` is exported too, for a caller wanting the rebuilt `LayoutDocument` without writing bytes. Two honest limits on the pdf rebuild, both structural properties of what a package records: a run's frames carry positions, not the wrap decisions that distributed its text across them, so a wrapped run re-renders once, whole, at its first recorded placement; and no font registry or positioned formula survives a bare package (a formula block's frame records where it sat while its glyphs render as nothing):
|
|
191
193
|
|
|
192
194
|
```ts
|
|
193
195
|
import { buildDocumentBytes, docxToPdf } from 'documents.js';
|
|
@@ -543,7 +545,8 @@ To run a single test file: `pnpm vitest run src/path/to/file.test.ts`.
|
|
|
543
545
|
- **`ooxml.js`'s typed readers are the basis for conversion** — `readDocxContent`/`readPptxContent` are thin wrappers, not independent walks. They are deliberately not re-exported (exposing both would invite using the wrong one). `readDocx`'s `comments`/`footnotes`/`headers`/`footers`/`numbering` are exposed via `readDocxExtras`. `readPptx` has no extras reader yet. xlsx is the one exception: `ooxml.js`'s `readXlsxContent`/`buildXlsxPackage` already read/write a spreadsheet `ContentDocument` directly (unlike `readDocx`/`readPptx`, which `readDocxContent`/`readPptxContent` wrap), so they're re-exported as-is rather than given a documents.js-local wrapper of their own — `readXlsx`, the separate lossy cell-values-only view, stays unexported for the same reason `readDocx`/`readPptx` do.
|
|
544
546
|
- **ODF text content is not a plain string.** ODF represents runs of spaces as `<text:s>`, tabs as `<text:tab/>`, line breaks as `<text:line-break/>` — all elements, not text nodes. Every ODF text getter MUST call `decodeOdfText`, never `textContent()` — which silently drops them (no error, just shorter text).
|
|
545
547
|
- **docx⇄PDF and pptx⇄PDF are explicitly not round-trip-lossless** — see [Fidelity](#fidelity). The cross-format bridge pairs are a genuinely different case.
|
|
546
|
-
- **A `DocumentPackage` from `onDocument`/`ConversionResult.package` is a snapshot, not a live view** — mutating `content`
|
|
548
|
+
- **A `DocumentPackage` from `onDocument`/`ConversionResult.package` is a snapshot, not a live view** — mutating `content` after the layout pass leaves its nodes' `frames` stale; nothing detects or rejects that, and the schema keeps `content`'s populated `frames` and `pages` in sync with nothing.
|
|
549
|
+
- **`frames` are stamped in place onto the caller's own content tree** — `convertXToLayout` mutates its `ContentDocument` argument (each node's placements are appended to its own `frames` array, one frame per rendered placement: per wrapped fragment on a run, the cell box on a cell, the emitted item's box on an image/vector/shape) and returns `pages` alongside the internal `LayoutDocument`. A run wrapped across three lines carries three frames; a repeat-row spreadsheet cell carries one per page it re-renders on. Reconstructors attach frames from the exact items each reconstructed node was clustered from, so every PDF-to-X conversion's content carries genuine positions too.
|
|
547
550
|
- **ODF text getters must call `decodeOdfText`.** See the dedicated gotcha above.
|
|
548
551
|
- **`readPdf` recovers rect/ellipse/line as their own `LayoutRect`/`LayoutEllipse`/`LayoutLine` kinds** via pdf-codec's shape-pattern detection — an axis-aligned closed four-corner subpath is a rect, four kappa-ratio cubics at cardinal points is an ellipse, an open single straight stroke is a line. A false positive changes kind, never geometry. Off-axis rotations, freeform curves, and multi-subpath figures narrow to `LayoutPath`.
|
|
549
552
|
- **`pdfToOds` re-types cells heuristically — this is probabilistic, not a fidelity guarantee.** A rendered PDF never carries a cell's typed value, only the printed string. Re-typing fires only where the string has exactly one defensible reading: the decimal must be exactly representable as a JS number; separators must be unambiguous (`"1,234"` is declined — competing European reading is 1.234); leading zeros decline (`"007"`); dates must self-state their component roles (ISO or named month accepted; `"01/02/2024"` declined). `TRUE`/`FALSE` re-type as booleans; `Yes`/`No` are declined. `displayText` always carries the rendered string verbatim. `onCellTypeInference` reports every decision. A formula is never claimed.
|
|
@@ -604,7 +607,7 @@ To run a single test file: `pnpm vitest run src/path/to/file.test.ts`.
|
|
|
604
607
|
- **A formula that cannot typeset degrades to its plain-text stand-in, never to nothing.** `buildDocxPackage` writes real OMML; `buildOdtPackage` writes real embedded formula sub-documents. The markdown writer is the only stand-in-only path. `odmToPdf` carries formulas through as ordinary blocks.
|
|
605
608
|
- **OMML read/write are deliberately asymmetric** — the reader covers more (`m:d`, `m:nary`, `m:acc`, `m:bar`, `m:func`, `m:sPre`) because it must read what Word wrote. `docx → odt → docx` round trips keep the mathematics but may change the OMML construct.
|
|
606
609
|
- **The OMML translator covers exactly what `src/mathml/layout.ts` typesets.** A stretchy fence diverges: PDF stretches it, docx writes it at base size. `munderover` becomes nested `m:limUpp`/`m:limLow` (no operand scope in MathML).
|
|
607
|
-
- **`sourcePath` traces a `LayoutItem` to its `ContentDocument` origin, but only within one read+layout pass** — not an edit-tracking mechanism.
|
|
610
|
+
- **`sourcePath` traces a `LayoutItem` to its `ContentDocument` origin, but only within one read+layout pass** — not an edit-tracking mechanism. Since the frames fusion it survives as traceability only: the authoritative node↔position association is each content node's own `frames`, stamped at the moment of layout (or of reconstruction) rather than re-matched by string afterwards.
|
|
608
611
|
- **`readMarkdownContent` passes `readMarkdown`'s result straight through** — `markdown-codec` already produces a full `ContentDocument`.
|
|
609
612
|
- **Every markdown construct-mapping gap is a documented `MarkdownDiagnosticCodes` entry** (`md/invented-page-geometry`, `md/nested-emphasis-flattened`, `md/link-title-dropped`, `md/code-block-info-string-dropped`, `md/blockquote-nested-depth`, `md/list-item-block-unlisted`, `md/list-item-multi-block-flattened`, `md/image-unresolved`, `md/raw-html-preserved-as-text`/`md/raw-html-dropped`, `md/front-matter-key-unmapped`, `md/heading-level-clamped`, `md/adjacent-links-merged`, `md/code-span-as-monospace-run`, `md/paragraph-indent-dropped`, `md/list-numid-fallback`, `md/table-cell-formatting-dropped`, `md/table-cell-multi-paragraph-joined`) — never a silent approximation.
|
|
610
613
|
- **`buildMarkdownText` throws for non-`'wordprocessing'` `ContentDocument`.**
|
|
@@ -230,6 +230,7 @@ function executeToPdf(format, bytes, options) {
|
|
|
230
230
|
}
|
|
231
231
|
const measurer = (0, pdf_codec.createFontMeasurer)(fonts);
|
|
232
232
|
let layout;
|
|
233
|
+
let pages;
|
|
233
234
|
let formulas;
|
|
234
235
|
switch (content.kind) {
|
|
235
236
|
case "wordprocessing": {
|
|
@@ -238,6 +239,7 @@ function executeToPdf(format, bytes, options) {
|
|
|
238
239
|
mathMetricsAt
|
|
239
240
|
});
|
|
240
241
|
layout = result.document;
|
|
242
|
+
pages = result.pages;
|
|
241
243
|
formulas = result.formulas;
|
|
242
244
|
break;
|
|
243
245
|
}
|
|
@@ -247,6 +249,7 @@ function executeToPdf(format, bytes, options) {
|
|
|
247
249
|
mathMetricsAt
|
|
248
250
|
});
|
|
249
251
|
layout = result.document;
|
|
252
|
+
pages = result.pages;
|
|
250
253
|
formulas = result.formulas;
|
|
251
254
|
break;
|
|
252
255
|
}
|
|
@@ -257,18 +260,22 @@ function executeToPdf(format, bytes, options) {
|
|
|
257
260
|
signal: options?.signal
|
|
258
261
|
});
|
|
259
262
|
layout = result.document;
|
|
263
|
+
pages = result.pages;
|
|
260
264
|
formulas = result.formulas;
|
|
261
265
|
break;
|
|
262
266
|
}
|
|
263
|
-
case "drawing":
|
|
264
|
-
|
|
267
|
+
case "drawing": {
|
|
268
|
+
const result = LAYOUT_ENGINES.drawing(content, { measurer });
|
|
269
|
+
layout = result.document;
|
|
270
|
+
pages = result.pages;
|
|
265
271
|
break;
|
|
272
|
+
}
|
|
266
273
|
default: throw new Error(`executeToPdf: cannot lay out a '${content.kind}' document`);
|
|
267
274
|
}
|
|
268
275
|
options?.onDocument?.({
|
|
269
276
|
formatVersion: document_schema_js.DOCUMENT_PACKAGE_FORMAT_VERSION,
|
|
270
277
|
content,
|
|
271
|
-
|
|
278
|
+
pages: [...pages]
|
|
272
279
|
});
|
|
273
280
|
if (formulas === void 0) return (0, pdf_codec.writePdf)(layout, {
|
|
274
281
|
signal: options?.signal,
|
|
@@ -289,10 +296,14 @@ function executeFromPdf(target, bytes, options) {
|
|
|
289
296
|
sink: options?.sink
|
|
290
297
|
});
|
|
291
298
|
const content = RECONSTRUCTORS[node.variant](layout, { signal: options?.signal });
|
|
299
|
+
const pages = layout.pages.map((page) => ({
|
|
300
|
+
widthPt: page.widthPt,
|
|
301
|
+
heightPt: page.heightPt
|
|
302
|
+
}));
|
|
292
303
|
options?.onDocument?.({
|
|
293
304
|
formatVersion: document_schema_js.DOCUMENT_PACKAGE_FORMAT_VERSION,
|
|
294
305
|
content,
|
|
295
|
-
|
|
306
|
+
pages
|
|
296
307
|
});
|
|
297
308
|
if (node.family === "markdown") {
|
|
298
309
|
const text = node.build(content);
|
|
@@ -229,6 +229,7 @@ function executeToPdf(format, bytes, options) {
|
|
|
229
229
|
}
|
|
230
230
|
const measurer = createFontMeasurer(fonts);
|
|
231
231
|
let layout;
|
|
232
|
+
let pages;
|
|
232
233
|
let formulas;
|
|
233
234
|
switch (content.kind) {
|
|
234
235
|
case "wordprocessing": {
|
|
@@ -237,6 +238,7 @@ function executeToPdf(format, bytes, options) {
|
|
|
237
238
|
mathMetricsAt
|
|
238
239
|
});
|
|
239
240
|
layout = result.document;
|
|
241
|
+
pages = result.pages;
|
|
240
242
|
formulas = result.formulas;
|
|
241
243
|
break;
|
|
242
244
|
}
|
|
@@ -246,6 +248,7 @@ function executeToPdf(format, bytes, options) {
|
|
|
246
248
|
mathMetricsAt
|
|
247
249
|
});
|
|
248
250
|
layout = result.document;
|
|
251
|
+
pages = result.pages;
|
|
249
252
|
formulas = result.formulas;
|
|
250
253
|
break;
|
|
251
254
|
}
|
|
@@ -256,18 +259,22 @@ function executeToPdf(format, bytes, options) {
|
|
|
256
259
|
signal: options?.signal
|
|
257
260
|
});
|
|
258
261
|
layout = result.document;
|
|
262
|
+
pages = result.pages;
|
|
259
263
|
formulas = result.formulas;
|
|
260
264
|
break;
|
|
261
265
|
}
|
|
262
|
-
case "drawing":
|
|
263
|
-
|
|
266
|
+
case "drawing": {
|
|
267
|
+
const result = LAYOUT_ENGINES.drawing(content, { measurer });
|
|
268
|
+
layout = result.document;
|
|
269
|
+
pages = result.pages;
|
|
264
270
|
break;
|
|
271
|
+
}
|
|
265
272
|
default: throw new Error(`executeToPdf: cannot lay out a '${content.kind}' document`);
|
|
266
273
|
}
|
|
267
274
|
options?.onDocument?.({
|
|
268
275
|
formatVersion: DOCUMENT_PACKAGE_FORMAT_VERSION,
|
|
269
276
|
content,
|
|
270
|
-
|
|
277
|
+
pages: [...pages]
|
|
271
278
|
});
|
|
272
279
|
if (formulas === void 0) return writePdf(layout, {
|
|
273
280
|
signal: options?.signal,
|
|
@@ -288,10 +295,14 @@ function executeFromPdf(target, bytes, options) {
|
|
|
288
295
|
sink: options?.sink
|
|
289
296
|
});
|
|
290
297
|
const content = RECONSTRUCTORS[node.variant](layout, { signal: options?.signal });
|
|
298
|
+
const pages = layout.pages.map((page) => ({
|
|
299
|
+
widthPt: page.widthPt,
|
|
300
|
+
heightPt: page.heightPt
|
|
301
|
+
}));
|
|
291
302
|
options?.onDocument?.({
|
|
292
303
|
formatVersion: DOCUMENT_PACKAGE_FORMAT_VERSION,
|
|
293
304
|
content,
|
|
294
|
-
|
|
305
|
+
pages
|
|
295
306
|
});
|
|
296
307
|
if (node.family === "markdown") {
|
|
297
308
|
const text = node.build(content);
|
package/dist/convert/convert.cjs
CHANGED
|
@@ -76,7 +76,10 @@ function odfToPdf(bytes, options) {
|
|
|
76
76
|
options?.onDocument?.({
|
|
77
77
|
formatVersion: document_schema_js.DOCUMENT_PACKAGE_FORMAT_VERSION,
|
|
78
78
|
content,
|
|
79
|
-
|
|
79
|
+
pages: [{
|
|
80
|
+
widthPt: document_schema_js.PAGE_SIZE_A4.widthPt,
|
|
81
|
+
heightPt: document_schema_js.PAGE_SIZE_A4.heightPt
|
|
82
|
+
}]
|
|
80
83
|
});
|
|
81
84
|
require_ports_abort.throwIfAborted(options?.signal);
|
|
82
85
|
return (0, pdf_codec.writePdf)(layout, {
|
|
@@ -291,14 +294,14 @@ function odbReportToPdf(content, options) {
|
|
|
291
294
|
fonts: options?.fonts,
|
|
292
295
|
onSubstitution: options?.onFontSubstitution
|
|
293
296
|
});
|
|
294
|
-
const { document: layout, formulas } = require_layout_engine.convertWordprocessingToLayout(content, {
|
|
297
|
+
const { document: layout, formulas, pages } = require_layout_engine.convertWordprocessingToLayout(content, {
|
|
295
298
|
measurer: (0, pdf_codec.createFontMeasurer)(fonts),
|
|
296
299
|
mathMetricsAt
|
|
297
300
|
});
|
|
298
301
|
options?.onDocument?.({
|
|
299
302
|
formatVersion: document_schema_js.DOCUMENT_PACKAGE_FORMAT_VERSION,
|
|
300
303
|
content,
|
|
301
|
-
|
|
304
|
+
pages: [...pages]
|
|
302
305
|
});
|
|
303
306
|
return (0, pdf_codec.writePdf)(layout, {
|
|
304
307
|
signal: options?.signal,
|
package/dist/convert/convert.js
CHANGED
|
@@ -75,7 +75,10 @@ function odfToPdf(bytes, options) {
|
|
|
75
75
|
options?.onDocument?.({
|
|
76
76
|
formatVersion: DOCUMENT_PACKAGE_FORMAT_VERSION,
|
|
77
77
|
content,
|
|
78
|
-
|
|
78
|
+
pages: [{
|
|
79
|
+
widthPt: PAGE_SIZE_A4.widthPt,
|
|
80
|
+
heightPt: PAGE_SIZE_A4.heightPt
|
|
81
|
+
}]
|
|
79
82
|
});
|
|
80
83
|
throwIfAborted(options?.signal);
|
|
81
84
|
return writePdf(layout, {
|
|
@@ -290,14 +293,14 @@ function odbReportToPdf(content, options) {
|
|
|
290
293
|
fonts: options?.fonts,
|
|
291
294
|
onSubstitution: options?.onFontSubstitution
|
|
292
295
|
});
|
|
293
|
-
const { document: layout, formulas } = convertWordprocessingToLayout(content, {
|
|
296
|
+
const { document: layout, formulas, pages } = convertWordprocessingToLayout(content, {
|
|
294
297
|
measurer: createFontMeasurer(fonts),
|
|
295
298
|
mathMetricsAt
|
|
296
299
|
});
|
|
297
300
|
options?.onDocument?.({
|
|
298
301
|
formatVersion: DOCUMENT_PACKAGE_FORMAT_VERSION,
|
|
299
302
|
content,
|
|
300
|
-
|
|
303
|
+
pages: [...pages]
|
|
301
304
|
});
|
|
302
305
|
return writePdf(layout, {
|
|
303
306
|
signal: options?.signal,
|
|
@@ -1,16 +1,193 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_model_geometry = require("../model/geometry.cjs");
|
|
3
|
+
const require_layout_shared = require("../layout/shared.cjs");
|
|
4
|
+
require("../layout/sheets.cjs");
|
|
5
|
+
const require_layout_drawing = require("../layout/drawing.cjs");
|
|
2
6
|
const require_codecs_registry = require("../codecs/registry.cjs");
|
|
7
|
+
let document_schema_js = require("document-schema.js");
|
|
3
8
|
let pdf_codec = require("pdf-codec");
|
|
4
9
|
//#region src/convert/from-package.ts
|
|
5
10
|
function buildDocumentBytes(pkg, target) {
|
|
6
11
|
if (target === "pdf") {
|
|
7
|
-
if (pkg.
|
|
8
|
-
return (0, pdf_codec.writePdf)(pkg
|
|
12
|
+
if (pkg.pages === void 0) throw new Error("this DocumentPackage has no pages -- only a package dumped from a <format>-to-pdf or pdf-to-<format> conversion carries them; a bridge conversion's own dump (e.g. odt-to-docx) never does, so 'pdf' is not a reachable target from it");
|
|
13
|
+
return (0, pdf_codec.writePdf)(layoutDocumentFromPackage(pkg));
|
|
9
14
|
}
|
|
10
15
|
if (target === "odf") throw new Error("'odf' (a standalone formula document) cannot be built from a DocumentPackage -- there is no ContentDocument-to-odf builder");
|
|
11
16
|
const content = require_codecs_registry.DOCUMENT_FORMAT_CODECS[target].content;
|
|
12
17
|
if (!content?.write) throw new Error(`DocumentFormat '${target}' has no content.write codec in DOCUMENT_FORMAT_CODECS, and is not 'pdf'/'odf' -- this is an internal invariant violation, not a caller error`);
|
|
13
18
|
return require_codecs_registry.requireArrayBufferBytes(content.write(pkg.content));
|
|
14
19
|
}
|
|
20
|
+
function pageOfFrame(state, frame) {
|
|
21
|
+
return state.pages[frame.pageIndex];
|
|
22
|
+
}
|
|
23
|
+
function emitRun(state, run) {
|
|
24
|
+
const frame = run.frames?.[0];
|
|
25
|
+
if (frame === void 0) return;
|
|
26
|
+
const page = pageOfFrame(state, frame);
|
|
27
|
+
if (page === void 0) return;
|
|
28
|
+
const font = require_layout_shared.runFont(run);
|
|
29
|
+
const sizePt = run.sizePt ?? 18;
|
|
30
|
+
const textItem = {
|
|
31
|
+
kind: "text",
|
|
32
|
+
text: run.text,
|
|
33
|
+
xPt: frame.xPt,
|
|
34
|
+
yPt: frame.yPt,
|
|
35
|
+
font,
|
|
36
|
+
sizePt,
|
|
37
|
+
color: run.color ?? document_schema_js.COLOR_BLACK,
|
|
38
|
+
underline: run.underline
|
|
39
|
+
};
|
|
40
|
+
page.items.push(textItem);
|
|
41
|
+
if (run.hyperlink !== void 0) {
|
|
42
|
+
const link = {
|
|
43
|
+
kind: "link",
|
|
44
|
+
uri: run.hyperlink,
|
|
45
|
+
xPt: frame.xPt,
|
|
46
|
+
yPt: frame.yPt,
|
|
47
|
+
widthPt: frame.widthPt,
|
|
48
|
+
heightPt: frame.heightPt
|
|
49
|
+
};
|
|
50
|
+
page.items.push(link);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function emitParagraph(state, paragraph) {
|
|
54
|
+
for (const run of paragraph.runs) emitRun(state, run);
|
|
55
|
+
}
|
|
56
|
+
function emitImageBlock(state, block, frames) {
|
|
57
|
+
for (const frame of frames ?? []) {
|
|
58
|
+
const page = pageOfFrame(state, frame);
|
|
59
|
+
if (page === void 0) continue;
|
|
60
|
+
const image = {
|
|
61
|
+
kind: "image",
|
|
62
|
+
imageId: require_layout_shared.registerImage(block, state.images),
|
|
63
|
+
xPt: frame.xPt,
|
|
64
|
+
yPt: frame.yPt,
|
|
65
|
+
widthPt: frame.widthPt,
|
|
66
|
+
heightPt: frame.heightPt
|
|
67
|
+
};
|
|
68
|
+
page.items.push(image);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function emitTableCell(state, cell) {
|
|
72
|
+
for (const frame of cell.frames ?? []) {
|
|
73
|
+
const page = pageOfFrame(state, frame);
|
|
74
|
+
if (page === void 0) continue;
|
|
75
|
+
if (cell.background !== void 0) page.items.push({
|
|
76
|
+
kind: "rect",
|
|
77
|
+
xPt: frame.xPt,
|
|
78
|
+
yPt: frame.yPt,
|
|
79
|
+
widthPt: frame.widthPt,
|
|
80
|
+
heightPt: frame.heightPt,
|
|
81
|
+
fill: cell.background
|
|
82
|
+
});
|
|
83
|
+
if (cell.borders !== void 0) {
|
|
84
|
+
const frameYDown = require_model_geometry.flipY({
|
|
85
|
+
xPt: frame.xPt,
|
|
86
|
+
yPt: frame.yPt,
|
|
87
|
+
widthPt: frame.widthPt,
|
|
88
|
+
heightPt: frame.heightPt
|
|
89
|
+
}, page.heightPt);
|
|
90
|
+
require_layout_shared.pushCellBorderLines(cell.borders, frameYDown, page.heightPt, cell.sourcePath, page.items);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
for (const block of cell.blocks) if (block.kind === "paragraph") emitParagraph(state, block);
|
|
94
|
+
else if (block.kind === "image") emitImageBlock(state, block, block.frames);
|
|
95
|
+
else if (block.kind === "table") emitTable(state, block);
|
|
96
|
+
}
|
|
97
|
+
function emitTable(state, table) {
|
|
98
|
+
for (const row of table.rows) for (const cell of row.cells) emitTableCell(state, cell);
|
|
99
|
+
}
|
|
100
|
+
function emitVector(state, vector) {
|
|
101
|
+
for (const frame of vector.frames ?? []) {
|
|
102
|
+
const page = pageOfFrame(state, frame);
|
|
103
|
+
if (page === void 0) continue;
|
|
104
|
+
const items = page.items;
|
|
105
|
+
require_layout_drawing.convertVector(vector, page.heightPt, items);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function emitSheetCell(state, cell) {
|
|
109
|
+
if ((cell.runs ?? []).some((run) => (run.frames?.length ?? 0) > 0)) for (const run of cell.runs ?? []) emitRun(state, run);
|
|
110
|
+
else for (const frame of cell.frames ?? []) {
|
|
111
|
+
const page = pageOfFrame(state, frame);
|
|
112
|
+
if (page === void 0) continue;
|
|
113
|
+
page.items.push({
|
|
114
|
+
kind: "text",
|
|
115
|
+
text: cell.displayText,
|
|
116
|
+
xPt: frame.xPt,
|
|
117
|
+
yPt: frame.yPt,
|
|
118
|
+
font: document_schema_js.DEFAULT_LAYOUT_FONT,
|
|
119
|
+
sizePt: 10,
|
|
120
|
+
color: document_schema_js.COLOR_BLACK
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
for (const frame of cell.frames ?? []) {
|
|
124
|
+
const page = pageOfFrame(state, frame);
|
|
125
|
+
if (page === void 0) continue;
|
|
126
|
+
if (cell.background !== void 0) page.items.push({
|
|
127
|
+
kind: "rect",
|
|
128
|
+
xPt: frame.xPt,
|
|
129
|
+
yPt: frame.yPt,
|
|
130
|
+
widthPt: frame.widthPt,
|
|
131
|
+
heightPt: frame.heightPt,
|
|
132
|
+
fill: cell.background
|
|
133
|
+
});
|
|
134
|
+
if (cell.borders !== void 0) {
|
|
135
|
+
const frameYDown = require_model_geometry.flipY({
|
|
136
|
+
xPt: frame.xPt,
|
|
137
|
+
yPt: frame.yPt,
|
|
138
|
+
widthPt: frame.widthPt,
|
|
139
|
+
heightPt: frame.heightPt
|
|
140
|
+
}, page.heightPt);
|
|
141
|
+
require_layout_shared.pushCellBorderLines(cell.borders, frameYDown, page.heightPt, cell.sourcePath, page.items);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function emitSheet(state, sheet) {
|
|
146
|
+
for (const cell of sheet.cells) emitSheetCell(state, cell);
|
|
147
|
+
for (const image of sheet.images) for (const frame of image.frames ?? []) {
|
|
148
|
+
const page = pageOfFrame(state, frame);
|
|
149
|
+
if (page === void 0) continue;
|
|
150
|
+
const layoutImage = {
|
|
151
|
+
kind: "image",
|
|
152
|
+
imageId: require_layout_shared.registerImage(image, state.images),
|
|
153
|
+
xPt: frame.xPt,
|
|
154
|
+
yPt: frame.yPt,
|
|
155
|
+
widthPt: frame.widthPt,
|
|
156
|
+
heightPt: frame.heightPt
|
|
157
|
+
};
|
|
158
|
+
page.items.push(layoutImage);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function emitBlocks(state, blocks) {
|
|
162
|
+
for (const block of blocks) if (block.kind === "paragraph") emitParagraph(state, block);
|
|
163
|
+
else if (block.kind === "image") emitImageBlock(state, block, block.frames);
|
|
164
|
+
else if (block.kind === "table") emitTable(state, block);
|
|
165
|
+
}
|
|
166
|
+
function layoutDocumentFromPackage(pkg) {
|
|
167
|
+
const pages = (pkg.pages ?? []).map((page) => ({
|
|
168
|
+
widthPt: page.widthPt,
|
|
169
|
+
heightPt: page.heightPt,
|
|
170
|
+
items: []
|
|
171
|
+
}));
|
|
172
|
+
const state = {
|
|
173
|
+
pages,
|
|
174
|
+
images: {}
|
|
175
|
+
};
|
|
176
|
+
const content = pkg.content;
|
|
177
|
+
if (content.kind === "wordprocessing") for (const section of content.sections) emitBlocks(state, section.blocks);
|
|
178
|
+
else if (content.kind === "presentation") for (const slide of content.slides) for (const shape of slide.shapes) emitBlocks(state, shape.blocks);
|
|
179
|
+
else if (content.kind === "spreadsheet") for (const sheet of content.sheets) emitSheet(state, sheet);
|
|
180
|
+
else if (content.kind === "drawing") for (const drawPage of content.pages) {
|
|
181
|
+
for (const vector of drawPage.vectors) emitVector(state, vector);
|
|
182
|
+
for (const shape of drawPage.shapes) emitBlocks(state, shape.blocks);
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
formatVersion: document_schema_js.LAYOUT_FORMAT_VERSION,
|
|
186
|
+
metadata: content.metadata,
|
|
187
|
+
pages,
|
|
188
|
+
images: state.images
|
|
189
|
+
};
|
|
190
|
+
}
|
|
15
191
|
//#endregion
|
|
16
192
|
exports.buildDocumentBytes = buildDocumentBytes;
|
|
193
|
+
exports.layoutDocumentFromPackage = layoutDocumentFromPackage;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DocumentFormat } from "./port.cjs";
|
|
2
|
-
import { DocumentPackage } from "document-schema.js";
|
|
2
|
+
import { DocumentPackage, LayoutDocument } from "document-schema.js";
|
|
3
3
|
//#region src/convert/from-package.d.ts
|
|
4
4
|
declare function buildDocumentBytes(pkg: DocumentPackage, target: DocumentFormat): Uint8Array<ArrayBuffer>;
|
|
5
|
+
declare function layoutDocumentFromPackage(pkg: DocumentPackage): LayoutDocument;
|
|
5
6
|
//#endregion
|
|
6
|
-
export { buildDocumentBytes };
|
|
7
|
+
export { buildDocumentBytes, layoutDocumentFromPackage };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DocumentFormat } from "./port.js";
|
|
2
|
-
import { DocumentPackage } from "document-schema.js";
|
|
2
|
+
import { DocumentPackage, LayoutDocument } from "document-schema.js";
|
|
3
3
|
//#region src/convert/from-package.d.ts
|
|
4
4
|
declare function buildDocumentBytes(pkg: DocumentPackage, target: DocumentFormat): Uint8Array<ArrayBuffer>;
|
|
5
|
+
declare function layoutDocumentFromPackage(pkg: DocumentPackage): LayoutDocument;
|
|
5
6
|
//#endregion
|
|
6
|
-
export { buildDocumentBytes };
|
|
7
|
+
export { buildDocumentBytes, layoutDocumentFromPackage };
|
|
@@ -1,15 +1,191 @@
|
|
|
1
|
+
import { flipY } from "../model/geometry.js";
|
|
2
|
+
import { pushCellBorderLines, registerImage, runFont } from "../layout/shared.js";
|
|
3
|
+
import "../layout/sheets.js";
|
|
4
|
+
import { convertVector } from "../layout/drawing.js";
|
|
1
5
|
import { DOCUMENT_FORMAT_CODECS, requireArrayBufferBytes } from "../codecs/registry.js";
|
|
6
|
+
import { COLOR_BLACK, DEFAULT_LAYOUT_FONT, LAYOUT_FORMAT_VERSION } from "document-schema.js";
|
|
2
7
|
import { writePdf } from "pdf-codec";
|
|
3
8
|
//#region src/convert/from-package.ts
|
|
4
9
|
function buildDocumentBytes(pkg, target) {
|
|
5
10
|
if (target === "pdf") {
|
|
6
|
-
if (pkg.
|
|
7
|
-
return writePdf(pkg
|
|
11
|
+
if (pkg.pages === void 0) throw new Error("this DocumentPackage has no pages -- only a package dumped from a <format>-to-pdf or pdf-to-<format> conversion carries them; a bridge conversion's own dump (e.g. odt-to-docx) never does, so 'pdf' is not a reachable target from it");
|
|
12
|
+
return writePdf(layoutDocumentFromPackage(pkg));
|
|
8
13
|
}
|
|
9
14
|
if (target === "odf") throw new Error("'odf' (a standalone formula document) cannot be built from a DocumentPackage -- there is no ContentDocument-to-odf builder");
|
|
10
15
|
const content = DOCUMENT_FORMAT_CODECS[target].content;
|
|
11
16
|
if (!content?.write) throw new Error(`DocumentFormat '${target}' has no content.write codec in DOCUMENT_FORMAT_CODECS, and is not 'pdf'/'odf' -- this is an internal invariant violation, not a caller error`);
|
|
12
17
|
return requireArrayBufferBytes(content.write(pkg.content));
|
|
13
18
|
}
|
|
19
|
+
function pageOfFrame(state, frame) {
|
|
20
|
+
return state.pages[frame.pageIndex];
|
|
21
|
+
}
|
|
22
|
+
function emitRun(state, run) {
|
|
23
|
+
const frame = run.frames?.[0];
|
|
24
|
+
if (frame === void 0) return;
|
|
25
|
+
const page = pageOfFrame(state, frame);
|
|
26
|
+
if (page === void 0) return;
|
|
27
|
+
const font = runFont(run);
|
|
28
|
+
const sizePt = run.sizePt ?? 18;
|
|
29
|
+
const textItem = {
|
|
30
|
+
kind: "text",
|
|
31
|
+
text: run.text,
|
|
32
|
+
xPt: frame.xPt,
|
|
33
|
+
yPt: frame.yPt,
|
|
34
|
+
font,
|
|
35
|
+
sizePt,
|
|
36
|
+
color: run.color ?? COLOR_BLACK,
|
|
37
|
+
underline: run.underline
|
|
38
|
+
};
|
|
39
|
+
page.items.push(textItem);
|
|
40
|
+
if (run.hyperlink !== void 0) {
|
|
41
|
+
const link = {
|
|
42
|
+
kind: "link",
|
|
43
|
+
uri: run.hyperlink,
|
|
44
|
+
xPt: frame.xPt,
|
|
45
|
+
yPt: frame.yPt,
|
|
46
|
+
widthPt: frame.widthPt,
|
|
47
|
+
heightPt: frame.heightPt
|
|
48
|
+
};
|
|
49
|
+
page.items.push(link);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function emitParagraph(state, paragraph) {
|
|
53
|
+
for (const run of paragraph.runs) emitRun(state, run);
|
|
54
|
+
}
|
|
55
|
+
function emitImageBlock(state, block, frames) {
|
|
56
|
+
for (const frame of frames ?? []) {
|
|
57
|
+
const page = pageOfFrame(state, frame);
|
|
58
|
+
if (page === void 0) continue;
|
|
59
|
+
const image = {
|
|
60
|
+
kind: "image",
|
|
61
|
+
imageId: registerImage(block, state.images),
|
|
62
|
+
xPt: frame.xPt,
|
|
63
|
+
yPt: frame.yPt,
|
|
64
|
+
widthPt: frame.widthPt,
|
|
65
|
+
heightPt: frame.heightPt
|
|
66
|
+
};
|
|
67
|
+
page.items.push(image);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function emitTableCell(state, cell) {
|
|
71
|
+
for (const frame of cell.frames ?? []) {
|
|
72
|
+
const page = pageOfFrame(state, frame);
|
|
73
|
+
if (page === void 0) continue;
|
|
74
|
+
if (cell.background !== void 0) page.items.push({
|
|
75
|
+
kind: "rect",
|
|
76
|
+
xPt: frame.xPt,
|
|
77
|
+
yPt: frame.yPt,
|
|
78
|
+
widthPt: frame.widthPt,
|
|
79
|
+
heightPt: frame.heightPt,
|
|
80
|
+
fill: cell.background
|
|
81
|
+
});
|
|
82
|
+
if (cell.borders !== void 0) {
|
|
83
|
+
const frameYDown = flipY({
|
|
84
|
+
xPt: frame.xPt,
|
|
85
|
+
yPt: frame.yPt,
|
|
86
|
+
widthPt: frame.widthPt,
|
|
87
|
+
heightPt: frame.heightPt
|
|
88
|
+
}, page.heightPt);
|
|
89
|
+
pushCellBorderLines(cell.borders, frameYDown, page.heightPt, cell.sourcePath, page.items);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
for (const block of cell.blocks) if (block.kind === "paragraph") emitParagraph(state, block);
|
|
93
|
+
else if (block.kind === "image") emitImageBlock(state, block, block.frames);
|
|
94
|
+
else if (block.kind === "table") emitTable(state, block);
|
|
95
|
+
}
|
|
96
|
+
function emitTable(state, table) {
|
|
97
|
+
for (const row of table.rows) for (const cell of row.cells) emitTableCell(state, cell);
|
|
98
|
+
}
|
|
99
|
+
function emitVector(state, vector) {
|
|
100
|
+
for (const frame of vector.frames ?? []) {
|
|
101
|
+
const page = pageOfFrame(state, frame);
|
|
102
|
+
if (page === void 0) continue;
|
|
103
|
+
const items = page.items;
|
|
104
|
+
convertVector(vector, page.heightPt, items);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function emitSheetCell(state, cell) {
|
|
108
|
+
if ((cell.runs ?? []).some((run) => (run.frames?.length ?? 0) > 0)) for (const run of cell.runs ?? []) emitRun(state, run);
|
|
109
|
+
else for (const frame of cell.frames ?? []) {
|
|
110
|
+
const page = pageOfFrame(state, frame);
|
|
111
|
+
if (page === void 0) continue;
|
|
112
|
+
page.items.push({
|
|
113
|
+
kind: "text",
|
|
114
|
+
text: cell.displayText,
|
|
115
|
+
xPt: frame.xPt,
|
|
116
|
+
yPt: frame.yPt,
|
|
117
|
+
font: DEFAULT_LAYOUT_FONT,
|
|
118
|
+
sizePt: 10,
|
|
119
|
+
color: COLOR_BLACK
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
for (const frame of cell.frames ?? []) {
|
|
123
|
+
const page = pageOfFrame(state, frame);
|
|
124
|
+
if (page === void 0) continue;
|
|
125
|
+
if (cell.background !== void 0) page.items.push({
|
|
126
|
+
kind: "rect",
|
|
127
|
+
xPt: frame.xPt,
|
|
128
|
+
yPt: frame.yPt,
|
|
129
|
+
widthPt: frame.widthPt,
|
|
130
|
+
heightPt: frame.heightPt,
|
|
131
|
+
fill: cell.background
|
|
132
|
+
});
|
|
133
|
+
if (cell.borders !== void 0) {
|
|
134
|
+
const frameYDown = flipY({
|
|
135
|
+
xPt: frame.xPt,
|
|
136
|
+
yPt: frame.yPt,
|
|
137
|
+
widthPt: frame.widthPt,
|
|
138
|
+
heightPt: frame.heightPt
|
|
139
|
+
}, page.heightPt);
|
|
140
|
+
pushCellBorderLines(cell.borders, frameYDown, page.heightPt, cell.sourcePath, page.items);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function emitSheet(state, sheet) {
|
|
145
|
+
for (const cell of sheet.cells) emitSheetCell(state, cell);
|
|
146
|
+
for (const image of sheet.images) for (const frame of image.frames ?? []) {
|
|
147
|
+
const page = pageOfFrame(state, frame);
|
|
148
|
+
if (page === void 0) continue;
|
|
149
|
+
const layoutImage = {
|
|
150
|
+
kind: "image",
|
|
151
|
+
imageId: registerImage(image, state.images),
|
|
152
|
+
xPt: frame.xPt,
|
|
153
|
+
yPt: frame.yPt,
|
|
154
|
+
widthPt: frame.widthPt,
|
|
155
|
+
heightPt: frame.heightPt
|
|
156
|
+
};
|
|
157
|
+
page.items.push(layoutImage);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function emitBlocks(state, blocks) {
|
|
161
|
+
for (const block of blocks) if (block.kind === "paragraph") emitParagraph(state, block);
|
|
162
|
+
else if (block.kind === "image") emitImageBlock(state, block, block.frames);
|
|
163
|
+
else if (block.kind === "table") emitTable(state, block);
|
|
164
|
+
}
|
|
165
|
+
function layoutDocumentFromPackage(pkg) {
|
|
166
|
+
const pages = (pkg.pages ?? []).map((page) => ({
|
|
167
|
+
widthPt: page.widthPt,
|
|
168
|
+
heightPt: page.heightPt,
|
|
169
|
+
items: []
|
|
170
|
+
}));
|
|
171
|
+
const state = {
|
|
172
|
+
pages,
|
|
173
|
+
images: {}
|
|
174
|
+
};
|
|
175
|
+
const content = pkg.content;
|
|
176
|
+
if (content.kind === "wordprocessing") for (const section of content.sections) emitBlocks(state, section.blocks);
|
|
177
|
+
else if (content.kind === "presentation") for (const slide of content.slides) for (const shape of slide.shapes) emitBlocks(state, shape.blocks);
|
|
178
|
+
else if (content.kind === "spreadsheet") for (const sheet of content.sheets) emitSheet(state, sheet);
|
|
179
|
+
else if (content.kind === "drawing") for (const drawPage of content.pages) {
|
|
180
|
+
for (const vector of drawPage.vectors) emitVector(state, vector);
|
|
181
|
+
for (const shape of drawPage.shapes) emitBlocks(state, shape.blocks);
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
formatVersion: LAYOUT_FORMAT_VERSION,
|
|
185
|
+
metadata: content.metadata,
|
|
186
|
+
pages,
|
|
187
|
+
images: state.images
|
|
188
|
+
};
|
|
189
|
+
}
|
|
14
190
|
//#endregion
|
|
15
|
-
export { buildDocumentBytes };
|
|
191
|
+
export { buildDocumentBytes, layoutDocumentFromPackage };
|
package/dist/index.cjs
CHANGED
|
@@ -800,6 +800,7 @@ Object.defineProperty(exports, "isXmlNode", {
|
|
|
800
800
|
return ooxml_js.isXmlNode;
|
|
801
801
|
}
|
|
802
802
|
});
|
|
803
|
+
exports.layoutDocumentFromPackage = require_convert_from_package.layoutDocumentFromPackage;
|
|
803
804
|
Object.defineProperty(exports, "layoutDocumentWithSchema", {
|
|
804
805
|
enumerable: true,
|
|
805
806
|
get: function() {
|