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.
Files changed (40) hide show
  1. package/README.md +8 -5
  2. package/dist/convert/composition.cjs +15 -4
  3. package/dist/convert/composition.js +15 -4
  4. package/dist/convert/convert.cjs +6 -3
  5. package/dist/convert/convert.js +6 -3
  6. package/dist/convert/from-package.cjs +179 -2
  7. package/dist/convert/from-package.d.cts +3 -2
  8. package/dist/convert/from-package.d.ts +3 -2
  9. package/dist/convert/from-package.js +179 -3
  10. package/dist/index.cjs +1 -0
  11. package/dist/index.d.cts +3 -3
  12. package/dist/index.d.ts +3 -3
  13. package/dist/index.js +2 -2
  14. package/dist/layout/drawing.cjs +71 -9
  15. package/dist/layout/drawing.d.cts +8 -3
  16. package/dist/layout/drawing.d.ts +8 -3
  17. package/dist/layout/drawing.js +71 -10
  18. package/dist/layout/engine.cjs +55 -43
  19. package/dist/layout/engine.d.cts +2 -1
  20. package/dist/layout/engine.d.ts +2 -1
  21. package/dist/layout/engine.js +57 -45
  22. package/dist/layout/reconstruct.cjs +259 -104
  23. package/dist/layout/reconstruct.js +259 -104
  24. package/dist/layout/shared.cjs +47 -2
  25. package/dist/layout/shared.d.cts +15 -4
  26. package/dist/layout/shared.d.ts +15 -4
  27. package/dist/layout/shared.js +44 -4
  28. package/dist/layout/sheets.cjs +18 -13
  29. package/dist/layout/sheets.d.cts +4 -2
  30. package/dist/layout/sheets.d.ts +4 -2
  31. package/dist/layout/sheets.js +20 -16
  32. package/dist/layout/slides.cjs +35 -31
  33. package/dist/layout/slides.d.cts +3 -2
  34. package/dist/layout/slides.d.ts +3 -2
  35. package/dist/layout/slides.js +37 -33
  36. package/dist/layout/text-layout.cjs +2 -1
  37. package/dist/layout/text-layout.d.cts +14 -3
  38. package/dist/layout/text-layout.d.ts +14 -3
  39. package/dist/layout/text-layout.js +2 -1
  40. 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` (content + layout). The port surfaces the same value as `package` on `ConversionResult`. For PDF-bypassing bridges, `pkg.layout` is always `undefined`.
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.layout?.pages.length); // populated for every X-to-PDF/PDF-to-X conversion
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'` writes the `LayoutDocument` half directly (throwing if the package carries none), `'odf'` has no builder and throws, everything else rebuilds from the `ContentDocument` half:
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` afterwards leaves `layout` stale; nothing detects or rejects that.
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
- layout = LAYOUT_ENGINES.drawing(content, { measurer });
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
- layout
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
- layout
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
- layout = LAYOUT_ENGINES.drawing(content, { measurer });
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
- layout
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
- layout
305
+ pages
295
306
  });
296
307
  if (node.family === "markdown") {
297
308
  const text = node.build(content);
@@ -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
- layout
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
- layout
304
+ pages: [...pages]
302
305
  });
303
306
  return (0, pdf_codec.writePdf)(layout, {
304
307
  signal: options?.signal,
@@ -75,7 +75,10 @@ function odfToPdf(bytes, options) {
75
75
  options?.onDocument?.({
76
76
  formatVersion: DOCUMENT_PACKAGE_FORMAT_VERSION,
77
77
  content,
78
- layout
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
- layout
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.layout === void 0) throw new Error("this DocumentPackage has no layout -- only a package dumped from a <format>-to-pdf or pdf-to-<format> conversion carries one; a bridge conversion's own dump (e.g. odt-to-docx) never does, so 'pdf' is not a reachable target from it");
8
- return (0, pdf_codec.writePdf)(pkg.layout);
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.layout === void 0) throw new Error("this DocumentPackage has no layout -- only a package dumped from a <format>-to-pdf or pdf-to-<format> conversion carries one; a bridge conversion's own dump (e.g. odt-to-docx) never does, so 'pdf' is not a reachable target from it");
7
- return writePdf(pkg.layout);
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() {