documents.js 2.3.3 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/README.md +27 -14
  2. package/dist/codecs/registry.d.cts +8 -3
  3. package/dist/codecs/registry.d.ts +8 -3
  4. package/dist/convert/canonicalise.cjs +20 -0
  5. package/dist/convert/canonicalise.d.cts +5 -0
  6. package/dist/convert/canonicalise.d.ts +5 -0
  7. package/dist/convert/canonicalise.js +18 -0
  8. package/dist/convert/composition.cjs +29 -28
  9. package/dist/convert/composition.d.cts +1 -1
  10. package/dist/convert/composition.d.ts +1 -1
  11. package/dist/convert/composition.js +29 -28
  12. package/dist/convert/convert.cjs +14 -26
  13. package/dist/convert/convert.d.cts +1 -1
  14. package/dist/convert/convert.d.ts +1 -1
  15. package/dist/convert/convert.js +14 -26
  16. package/dist/convert/decompose.cjs +134 -0
  17. package/dist/convert/decompose.d.cts +13 -0
  18. package/dist/convert/decompose.d.ts +13 -0
  19. package/dist/convert/decompose.js +126 -0
  20. package/dist/convert/factor-styles.cjs +409 -0
  21. package/dist/convert/factor-styles.d.cts +6 -0
  22. package/dist/convert/factor-styles.d.ts +6 -0
  23. package/dist/convert/factor-styles.js +407 -0
  24. package/dist/convert/flatten.cjs +138 -0
  25. package/dist/convert/flatten.d.cts +5 -0
  26. package/dist/convert/flatten.d.ts +5 -0
  27. package/dist/convert/flatten.js +137 -0
  28. package/dist/convert/from-package.cjs +4 -3
  29. package/dist/convert/from-package.d.cts +2 -1
  30. package/dist/convert/from-package.d.ts +2 -1
  31. package/dist/convert/from-package.js +5 -4
  32. package/dist/convert/local.cjs +1 -1
  33. package/dist/convert/local.js +1 -1
  34. package/dist/convert/variant-bridges.cjs +0 -4
  35. package/dist/convert/variant-bridges.js +1 -5
  36. package/dist/csv/read.cjs +0 -1
  37. package/dist/csv/read.js +1 -2
  38. package/dist/edit/docx/numbering.d.cts +1 -1
  39. package/dist/edit/docx/numbering.d.ts +1 -1
  40. package/dist/edit/docx/paragraph.cjs +1 -0
  41. package/dist/edit/docx/paragraph.js +1 -0
  42. package/dist/edit/pdf/editor.cjs +1 -2
  43. package/dist/edit/pdf/editor.d.cts +2 -2
  44. package/dist/edit/pdf/editor.d.ts +2 -2
  45. package/dist/edit/pdf/editor.js +1 -2
  46. package/dist/edit/pdf/item.d.cts +1 -1
  47. package/dist/edit/pdf/item.d.ts +1 -1
  48. package/dist/edit/pdf/page.d.cts +2 -2
  49. package/dist/edit/pdf/page.d.ts +2 -2
  50. package/dist/edit/pdf/util.d.cts +1 -1
  51. package/dist/edit/pdf/util.d.ts +1 -1
  52. package/dist/fonts/registry.d.cts +1 -1
  53. package/dist/fonts/registry.d.ts +1 -1
  54. package/dist/index.cjs +10 -15
  55. package/dist/index.d.cts +7 -4
  56. package/dist/index.d.ts +7 -4
  57. package/dist/index.js +6 -3
  58. package/dist/{item-Dj6f1VTE.d.cts → item-D9vQKT0T.d.cts} +2 -1
  59. package/dist/{item-Dj6f1VTE.d.ts → item-D9vQKT0T.d.ts} +2 -1
  60. package/dist/latex/lint.cjs +3 -2
  61. package/dist/latex/lint.js +3 -2
  62. package/dist/layout/drawing.d.cts +2 -1
  63. package/dist/layout/drawing.d.ts +2 -1
  64. package/dist/layout/engine.cjs +8 -5
  65. package/dist/layout/engine.d.cts +2 -1
  66. package/dist/layout/engine.d.ts +2 -1
  67. package/dist/layout/engine.js +8 -5
  68. package/dist/layout/lattice.d.cts +1 -1
  69. package/dist/layout/lattice.d.ts +1 -1
  70. package/dist/layout/reconstruct.cjs +0 -5
  71. package/dist/layout/reconstruct.d.cts +2 -1
  72. package/dist/layout/reconstruct.d.ts +2 -1
  73. package/dist/layout/reconstruct.js +0 -5
  74. package/dist/layout/shared.cjs +2 -1
  75. package/dist/layout/shared.d.cts +2 -1
  76. package/dist/layout/shared.d.ts +2 -1
  77. package/dist/layout/shared.js +2 -1
  78. package/dist/layout/sheets.d.cts +2 -1
  79. package/dist/layout/sheets.d.ts +2 -1
  80. package/dist/layout/slides.d.cts +2 -1
  81. package/dist/layout/slides.d.ts +2 -1
  82. package/dist/markdown/render.cjs +0 -1
  83. package/dist/markdown/render.js +1 -2
  84. package/dist/model/embedded-drawing.cjs +5 -8
  85. package/dist/model/embedded-drawing.js +5 -8
  86. package/dist/model/formula.cjs +0 -2
  87. package/dist/model/formula.js +0 -2
  88. package/dist/odb/report/render.cjs +0 -1
  89. package/dist/odb/report/render.js +1 -2
  90. package/dist/odb/spreadsheet.cjs +0 -1
  91. package/dist/odb/spreadsheet.js +1 -2
  92. package/dist/odf/odg/read.cjs +0 -2
  93. package/dist/odf/odg/read.js +0 -2
  94. package/dist/odf/odp/read.cjs +0 -2
  95. package/dist/odf/odp/read.js +0 -2
  96. package/dist/odf/ods/read.cjs +0 -2
  97. package/dist/odf/ods/read.js +0 -2
  98. package/dist/odf/odt/read.cjs +0 -2
  99. package/dist/odf/odt/read.js +0 -2
  100. package/dist/ooxml/docx/read.cjs +0 -2
  101. package/dist/ooxml/docx/read.js +0 -2
  102. package/dist/ooxml/pptx/read.cjs +0 -2
  103. package/dist/ooxml/pptx/read.js +0 -2
  104. package/dist/svg/read.cjs +8 -11
  105. package/dist/svg/read.js +8 -11
  106. package/package.json +5 -5
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![GitHub](https://img.shields.io/badge/GitHub-181717?logo=github&logoColor=white)](https://github.com/ExaDev/documents.js) [![npm](https://img.shields.io/badge/npm-CB3837?logo=npm&logoColor=white)](https://www.npmjs.com/package/documents.js) [![Release](https://img.shields.io/github/v/release/ExaDev/documents.js)](https://github.com/ExaDev/documents.js/releases/latest) [![CI](https://img.shields.io/github/actions/workflow/status/ExaDev/documents.js/ci.yml?branch=main)](https://github.com/ExaDev/documents.js/actions)
4
4
 
5
- > Converts between any two compatible document formats through a shared content/layout pivot. docx, pptx, odt, odp, ods, odg, xlsx, csv (TSV is the same format with a tab delimiter), svg, and markdown all read into and build from the same `ContentDocument`/`LayoutDocument` model, with PDF as the one format every variant can reach. A composition engine (`convertDocument`) routes 111 (source, target) pairs across the ten content formats and PDF, including twenty PDF-pivot round trips (the eight layout-engine formats, plus xlsx and csv composing through ods), twenty-four cross-format bridge functions (same-variant direct copies, cross-variant semantic transforms, and PDF-composed), plus special-case conversions for `.odm` master documents, `.odb` database front-ends (HSQLDB and Firebird, four storage tiers), standalone `.odf` formula documents, and a bounded SQL/rpt-formula engine for `.odb` reports. Also includes: read-and-write live-view editors for all six editable formats, docx comment/footnote/header-footer exposure via `readDocxExtras`, real font resolution (source-embedded faces ahead of caller-supplied, vendored substitutes, and the standard 14), a hand-written MathML typesetting engine with embedded-font PDF rendering and a matching MathML ⇄ OMML translator, LaTeX lowering into the schema's two-layer semantic math core (pinned temml parser, symbol tables from prose, a coherence lint), and a fully hand-written PDF codec. Built on [ooxml.js](https://github.com/ExaDev/ooxml.js), [odf.js](https://github.com/ExaDev/odf.js), [pdf-codec](https://github.com/ExaDev/pdf-codec), [markdown-codec](https://github.com/ExaDev/markdown-codec), and [document-schema.js](https://github.com/ExaDev/document-schema.js).
5
+ > Converts between any two compatible document formats through a shared content/layout pivot. docx, pptx, odt, odp, ods, odg, xlsx, csv (TSV is the same format with a tab delimiter), svg, and markdown all read into and build from the same shared `ContentDocument` model (reported to callers as the tree-form `DocumentPackage`), with PDF — reached through pdf-codec's own `LayoutDocument` view — as the one format every variant can reach. A composition engine (`convertDocument`) routes 111 (source, target) pairs across the ten content formats and PDF, including twenty PDF-pivot round trips (the eight layout-engine formats, plus xlsx and csv composing through ods), twenty-four cross-format bridge functions (same-variant direct copies, cross-variant semantic transforms, and PDF-composed), plus special-case conversions for `.odm` master documents, `.odb` database front-ends (HSQLDB and Firebird, four storage tiers), standalone `.odf` formula documents, and a bounded SQL/rpt-formula engine for `.odb` reports. Also includes: read-and-write live-view editors for all six editable formats, docx comment/footnote/header-footer exposure via `readDocxExtras`, real font resolution (source-embedded faces ahead of caller-supplied, vendored substitutes, and the standard 14), a hand-written MathML typesetting engine with embedded-font PDF rendering and a matching MathML ⇄ OMML translator, LaTeX lowering into the schema's two-layer semantic math core (pinned temml parser, symbol tables from prose, a coherence lint), and a fully hand-written PDF codec. Built on [ooxml.js](https://github.com/ExaDev/ooxml.js), [odf.js](https://github.com/ExaDev/odf.js), [pdf-codec](https://github.com/ExaDev/pdf-codec), [markdown-codec](https://github.com/ExaDev/markdown-codec), and [document-schema.js](https://github.com/ExaDev/document-schema.js).
6
6
 
7
7
  `documents.js` extends `ooxml.js` in two directions `ooxml.js` deliberately does not cover: full PDF support (parsing and generating, via `pdf-codec`), and a read-**and-write** manipulation API for docx/pptx content — `ooxml.js`'s own typed readers are one-way. The PDF codec is hand-written against ISO 32000-1, with no external PDF library as a dependency — see [Fidelity](#fidelity) and pdf-codec's own README for the honest trade-off (not as robust against adversarial PDFs as a 15+-year-hardened library; fully auditable and dependency-free instead). `src/mathml/` (the MathML typesetting engine) stays in this package and is hand-written too, for the same supply-chain reason. The one deliberate exception on the math side is the LaTeX parser: `src/latex/` lowers LaTeX into the schema's semantic core over a pinned exact-version [temml](https://temml.org) dependency — see [LaTeX lowering into the semantic core](#latex-lowering-into-the-semantic-core) for why a LaTeX grammar is the one component not worth hand-writing and what the pin guarantees.
8
8
 
@@ -168,22 +168,35 @@ DocumentFormatSchema.parse(userSuppliedFormat); // throws a ZodError for anythin
168
168
 
169
169
  ### Intermediate `DocumentPackage`, JSON, and bytes
170
170
 
171
- 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.
171
+ Every conversion function accepts an `onDocument` callback receiving the intermediate `DocumentPackage` — since document-schema.js 4, the single hierarchical tree: `children` carry the decomposed group tree (one group per container — a section, slide, sheet, or draw page — with heading and list paragraphs anchoring nested groups inside their container's flow), and the content nodes embedded in that tree carry `frames`, the rendered page positions the layout pass stamped onto them, in PDF user-space. `pages` (each rendered page's size, indexed to match every `frames[].pageIndex`) and the minted `styles` table ride the root. 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.
172
172
 
173
173
  ```ts
174
- import { docxToPdf } from 'documents.js';
174
+ import { docxToPdf, flattenPackage } from 'documents.js';
175
175
 
176
176
  const pdfBytes = docxToPdf(docxBytes, {
177
177
  onDocument: (pkg) => {
178
- console.log(pkg.content.kind); // 'wordprocessing'
178
+ console.log(pkg.kind); // 'wordprocessing' -- the document kind rides the tree's root
179
179
  console.log(pkg.pages?.length); // populated for every X-to-PDF/PDF-to-X conversion
180
- const block = pkg.content.kind === 'wordprocessing' ? pkg.content.sections[0]?.blocks[0] : undefined;
180
+ const content = flattenPackage(pkg); // the flat ContentDocument, fully materialised
181
+ const block = content.kind === 'wordprocessing' ? content.sections[0]?.blocks[0] : undefined;
181
182
  console.log(block?.kind === 'paragraph' ? block.runs[0]?.frames : 'no paragraph'); // that run's rendered placements
182
183
  },
183
184
  });
184
185
  ```
185
186
 
186
- `documentPackageWithSchema`/`documentFromJson` turn a `DocumentPackage` into self-describing JSON and back (re-exported from `document-schema.js`):
187
+ The tree and the flat `ContentDocument` are one format in two encodings, related by three laws (stated on [document-schema.js#20](https://github.com/ExaDev/document-schema.js/issues/20), proven over this package's real corpus by the bijection suite in `src/convert/bijection.test.ts`): (i) `flattenPackage(assemblePackage(c))` reproduces `c` exactly, up to one declared normalisation (a present-but-empty sheet `embeddedObjects` array normalises to the field absent); (ii) effective-property equality holds universally — a factored and an unfactored serialisation of one document resolve to the same properties; (iii) minting is idempotent — factoring a second time produces the identical styles table.
188
+
189
+ `assemblePackage` is the one constructor behind every construction site — decompose, then `factorStyles`, the minting pass that hoists property tuples occurring two or more times onto a group-wrapper ref plus a `styles` table entry (deterministic order; `frames`/`sourcePath`/`styleId` are per-node facts and never factor). `decompose`, `flattenPackage`, and `factorStyles` are all exported for a caller composing its own boundary; the readers and builders keep producing and consuming the flat form, so the tree exists only at the package boundary:
190
+
191
+ ```ts
192
+ import { assemblePackage, decompose, factorStyles, flattenPackage } from 'documents.js';
193
+
194
+ const tree = assemblePackage(content, pages); // decompose + mint: the tree a conversion reports
195
+ const flat = flattenPackage(tree); // the exact flat ContentDocument back, refs materialised
196
+ const again = factorStyles(tree); // re-mint: identical table and tree (law iii)
197
+ ```
198
+
199
+ `documentPackageWithSchema`/`documentFromJson` turn a `DocumentPackage` into self-describing JSON and back (re-exported from `document-schema.js`); the version-pinned `$schema` URI the dumper stamps is the package's version — the hand-kept `formatVersion` integer is gone:
187
200
 
188
201
  ```ts
189
202
  import { documentFromJson, documentPackageWithSchema } from 'documents.js';
@@ -192,10 +205,10 @@ const tagged = documentPackageWithSchema(pkg);
192
205
  writeFileSync('converted.doc.json', JSON.stringify(tagged, null, 2));
193
206
 
194
207
  const { kind, value } = documentFromJson(JSON.parse(readFileSync('converted.doc.json', 'utf8')));
195
- // kind: 'DocumentPackage' (here) | 'ContentDocument' | 'LayoutDocument'
208
+ // kind: 'DocumentPackage' (here) | 'ContentDocument'
196
209
  ```
197
210
 
198
- `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):
211
+ `buildDocumentBytes` rebuilds any `DocumentFormat`'s bytes from a tree-form `DocumentPackage` — it flattens once at the boundary and hands the flat form to the builders, whose signatures never changed. `'pdf'` rebuilds the pdf-codec view from the package's own frames+pages (`layoutDocumentFromPackage`, a mechanical inverse walking the flattened content 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 flattened `ContentDocument`. `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):
199
212
 
200
213
  ```ts
201
214
  import { buildDocumentBytes, docxToPdf } from 'documents.js';
@@ -522,7 +535,7 @@ const { family, bold, italic } = describeFontFace(fontBytes, 'BrandSans-Regular.
522
535
 
523
536
  The package is layered from generic primitives outward to the two conversion directions:
524
537
 
525
- - **`src/model/`** — thin additions on top of `document-schema.js`, which owns the two pivot models (`LayoutDocument`, `ContentDocument`) imported, not defined here. Local: `bytes.ts` (magic-byte schemas), `units.ts` (EMU/twip/point conversions), `geometry.ts`/`color.ts`/`style.ts` (thin re-exports plus PDF-specific `flipY`), `paint-order.ts` (merges drawing page `shapes`/`vectors` by `paintOrder`), `formula.ts` (helpers around `ContentFormula`), `embedded-drawing.ts` (packages recovered vectors as a `ContentEmbeddedObjectBlock`).
538
+ - **`src/model/`** — thin additions on top of `document-schema.js`, which owns the content model (`ContentDocument`, and since 4.0.0 the tree-form `DocumentPackage` vocabulary) imported, not defined here; the `LayoutDocument` item family is pdf-codec's own since the schema-4 demotion. Local: `bytes.ts` (magic-byte schemas), `units.ts` (EMU/twip/point conversions), `geometry.ts`/`color.ts`/`style.ts` (thin re-exports plus PDF-specific `flipY`), `paint-order.ts` (merges drawing page `shapes`/`vectors` by `paintOrder`), `formula.ts` (helpers around `ContentFormula`), `embedded-drawing.ts` (packages recovered vectors as a `ContentEmbeddedObjectBlock`).
526
539
  - **`pdf-codec`** (external) — the hand-written PDF codec, plus generic byte/image primitives (now in `byte-codec`). See that package's own README.
527
540
  - **`src/ports/`** — injectable ports: `throwIfAborted` (signal check at long-loop boundaries) and `ClockPort`/`systemClock`/`fixedClock` (injectable "now" for deterministic output — exported but not yet consumed by any conversion path).
528
541
  - **`src/xml/`** and **`src/opc/`** — parent-aware XML query/mutation and OPC package mechanics over `ooxml.js`'s `Package`/`XmlNode`. `src/xml/odf-text.ts` holds `encodeOdfText`/`decodeOdfText` — see the ODF text gotcha below.
@@ -541,7 +554,7 @@ The package is layered from generic primitives outward to the two conversion dir
541
554
  - **`src/hsqldb/`** — `.odb` decoders, four tiers: `script.ts` (TEXT-script DDL/DML parser), `rowformat.ts`/`cache.ts` (CACHED binary row-store), `binary-script.ts` (BINARY/COMPRESSED whole-script). All import only `document-schema.js` — no odf.js knowledge.
542
555
  - **`src/firebird/`** — Tier 3: gbak logical-backup reader. `reader.ts` (attribute framing + RLE decompression + XDR decoding), `schema.ts`/`data.ts` (table/row walking). No ratified spec — built against Firebird's own engine source.
543
556
  - **`src/odb/`** — decoder-selection and pivot-mapping: `read.ts` routes to the right tier, `spreadsheet.ts`/`csv.ts` map to output formats. `odb/sql/` is the bounded SQL engine, `odb/formula/` is the rpt formula engine, `odb/report/` is the renderer, `odb/values.ts` is shared comparison/aggregation semantics.
544
- - **`src/convert/`** — the composition layer: `convert.ts` (all named functions + `convertDocument` + `resolveCompositionPlan`), `composition.ts` (the pathfinder and primitive registry), `codec.ts` (`z.codec()` pairs), `port.ts`/`local.ts` (the `DocumentConverter` port), `variant-bridges.ts` (cross-variant semantic transforms), `from-package.ts` (`buildDocumentBytes`).
557
+ - **`src/convert/`** — the composition layer: `convert.ts` (all named functions + `convertDocument` + `resolveCompositionPlan`), `composition.ts` (the pathfinder and primitive registry), `codec.ts` (`z.codec()` pairs), `port.ts`/`local.ts` (the `DocumentConverter` port), `variant-bridges.ts` (cross-variant semantic transforms), and the package boundary itself — `decompose.ts`/`flatten.ts` (the lossless tree ⇄ flat pair), `factor-styles.ts` (`assemblePackage` and the styles minting pass), `canonicalise.ts` (the shared canonical-key recipe) — plus `from-package.ts` (`buildDocumentBytes`, which flattens once at the boundary).
545
558
  - **`src/codecs/`** — `DOCUMENT_FORMAT_CODECS`: every format's read/build capability as data, so `readDocumentMetadata`/`setDocumentMetadata`/`buildDocumentBytes` dispatch through one registry.
546
559
  - **`src/metadata/`** — cross-format metadata read/write via `DOCUMENT_FORMAT_CODECS`.
547
560
  - **`src/package-codec.ts`** — `decodeDocumentPackage`/`encodeDocumentPackage`/`decodeOdbPackage`.
@@ -578,8 +591,8 @@ To run a single test file: `pnpm vitest run src/path/to/file.test.ts`.
578
591
  - **`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.
579
592
  - **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).
580
593
  - **docx⇄PDF and pptx⇄PDF are explicitly not round-trip-lossless** — see [Fidelity](#fidelity). The cross-format bridge pairs are a genuinely different case.
581
- - **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.
582
- - **`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.
594
+ - **A `DocumentPackage` from `onDocument`/`ConversionResult.package` is a snapshot, not a live view** — mutating the tree's content nodes after the layout pass leaves their `frames` stale; nothing detects or rejects that, and the schema keeps the tree's populated `frames` and `pages` in sync with nothing.
595
+ - **`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. The tree an `onDocument` callback receives embeds those same framed node objects (decompose wraps, it never copies — only a styles-minted paragraph or run is a copy), so the positions are identical in both encodings by construction.
583
596
  - **ODF text getters must call `decodeOdfText`.** See the dedicated gotcha above.
584
597
  - **`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`.
585
598
  - **`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.
@@ -674,7 +687,7 @@ Read as **row → column**. `✓` lossless, `~` bounded, `✗` lossy, `✗✗` s
674
687
  | **csv** | ✗ | ✗ | ✓ | ✗ | ✗ | ✓ | ✗ | ✗ | – | ✗✗ | — | ~ |
675
688
  | **pdf** | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | – | ✗✗ | ✗ | — |
676
689
 
677
- 111 of 132 directional pairs are routable. The `ContentDocument`/`LayoutDocument` pivots are the hub, not PDF — twenty bridges bypass PDF entirely.
690
+ 111 of 132 directional pairs are routable. The shared `ContentDocument` model is the hub, not PDF — twenty bridges bypass PDF entirely.
678
691
 
679
692
  **X → PDF** is a genuine layout render: positioned text, images, tables, lists, vector primitives, styled through the full cascade. It is a faithful visual approximation, not pixel-identical — closeness depends on font availability.
680
693
 
@@ -713,7 +726,7 @@ Conventional Commits (`feat:`, `fix:`, `test:`, `chore:`, …), enforced by comm
713
726
  ## References
714
727
 
715
728
  - [ooxml.js](https://github.com/ExaDev/ooxml.js) — docx/pptx/xlsx ⇄ JSON handling and typed reading, including `readXlsxContent`/`buildXlsxPackage` (consumed by the `odsToXlsx`/`xlsxToOds` bridge and internal codecs, and re-exported directly from this package's own surface — see [Reading and building xlsx content directly](#reading-and-building-xlsx-content-directly)).
716
- - [document-schema.js](https://github.com/ExaDev/document-schema.js) — owns `ContentDocument`/`LayoutDocument` and the port contracts; shared by all sibling packages.
729
+ - [document-schema.js](https://github.com/ExaDev/document-schema.js) — owns `ContentDocument`, the tree-form `DocumentPackage` and its styles-table facility, and the port contracts; shared by all sibling packages.
717
730
  - [markdown-codec](https://github.com/ExaDev/markdown-codec) — CommonMark+GFM ⇄ `ContentDocument` handling. The third format (after docx/odt) sharing the wordprocessing pivot.
718
731
  - [pdf-codec](https://github.com/ExaDev/pdf-codec) — the hand-written PDF codec (`readPdf`/`writePdf`/`pdfCodec`), the embedded STIX Two Math font, and text-measurement/font-resolution primitives.
719
732
  - [byte-codec](https://github.com/ExaDev/byte-codec) — generic byte/image utilities (ByteWriter, CRC-32, deflate/inflate, PNG/JPEG), extracted from pdf-codec.
@@ -1,5 +1,6 @@
1
1
  import { DocumentFormat } from "../convert/port.cjs";
2
- import { ContentCodec, LayoutCodec } from "document-schema.js";
2
+ import { ContentCodec } from "document-schema.js";
3
+ import { LayoutDocument } from "pdf-codec";
3
4
  import { MarkdownImageResolver } from "markdown-codec";
4
5
  //#region src/codecs/registry.d.ts
5
6
  declare function isArrayBufferBacked(bytes: Uint8Array): bytes is Uint8Array<ArrayBuffer>;
@@ -8,10 +9,14 @@ interface DocumentCodecOptions {
8
9
  readonly signal?: AbortSignal;
9
10
  readonly images?: MarkdownImageResolver;
10
11
  }
12
+ interface LayoutEntryCodec {
13
+ read(bytes: Uint8Array, options?: DocumentCodecOptions): LayoutDocument;
14
+ write(layout: LayoutDocument, options?: DocumentCodecOptions): Uint8Array;
15
+ }
11
16
  interface DocumentFormatCodecs {
12
17
  readonly content?: ContentCodec<DocumentCodecOptions>;
13
- readonly layout?: LayoutCodec<DocumentCodecOptions>;
18
+ readonly layout?: LayoutEntryCodec;
14
19
  }
15
20
  declare const DOCUMENT_FORMAT_CODECS: Readonly<Record<DocumentFormat, DocumentFormatCodecs>>;
16
21
  //#endregion
17
- export { DOCUMENT_FORMAT_CODECS, DocumentCodecOptions, DocumentFormatCodecs, isArrayBufferBacked, requireArrayBufferBytes };
22
+ export { DOCUMENT_FORMAT_CODECS, DocumentCodecOptions, DocumentFormatCodecs, LayoutEntryCodec, isArrayBufferBacked, requireArrayBufferBytes };
@@ -1,5 +1,6 @@
1
1
  import { DocumentFormat } from "../convert/port.js";
2
- import { ContentCodec, LayoutCodec } from "document-schema.js";
2
+ import { ContentCodec } from "document-schema.js";
3
+ import { LayoutDocument } from "pdf-codec";
3
4
  import { MarkdownImageResolver } from "markdown-codec";
4
5
  //#region src/codecs/registry.d.ts
5
6
  declare function isArrayBufferBacked(bytes: Uint8Array): bytes is Uint8Array<ArrayBuffer>;
@@ -8,10 +9,14 @@ interface DocumentCodecOptions {
8
9
  readonly signal?: AbortSignal;
9
10
  readonly images?: MarkdownImageResolver;
10
11
  }
12
+ interface LayoutEntryCodec {
13
+ read(bytes: Uint8Array, options?: DocumentCodecOptions): LayoutDocument;
14
+ write(layout: LayoutDocument, options?: DocumentCodecOptions): Uint8Array;
15
+ }
11
16
  interface DocumentFormatCodecs {
12
17
  readonly content?: ContentCodec<DocumentCodecOptions>;
13
- readonly layout?: LayoutCodec<DocumentCodecOptions>;
18
+ readonly layout?: LayoutEntryCodec;
14
19
  }
15
20
  declare const DOCUMENT_FORMAT_CODECS: Readonly<Record<DocumentFormat, DocumentFormatCodecs>>;
16
21
  //#endregion
17
- export { DOCUMENT_FORMAT_CODECS, DocumentCodecOptions, DocumentFormatCodecs, isArrayBufferBacked, requireArrayBufferBytes };
22
+ export { DOCUMENT_FORMAT_CODECS, DocumentCodecOptions, DocumentFormatCodecs, LayoutEntryCodec, isArrayBufferBacked, requireArrayBufferBytes };
@@ -0,0 +1,20 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/convert/canonicalise.ts
3
+ function canonicalise(value) {
4
+ if (Array.isArray(value)) return value.map(canonicalise);
5
+ if (isRecord(value)) {
6
+ const sorted = {};
7
+ for (const key of Object.keys(value).sort()) sorted[key] = canonicalise(value[key]);
8
+ return sorted;
9
+ }
10
+ return value;
11
+ }
12
+ function isRecord(value) {
13
+ return typeof value === "object" && value !== null && !Array.isArray(value);
14
+ }
15
+ function canonicalKey(value) {
16
+ return JSON.stringify(canonicalise(value));
17
+ }
18
+ //#endregion
19
+ exports.canonicalKey = canonicalKey;
20
+ exports.canonicalise = canonicalise;
@@ -0,0 +1,5 @@
1
+ //#region src/convert/canonicalise.d.ts
2
+ declare function canonicalise(value: unknown): unknown;
3
+ declare function canonicalKey(value: unknown): string;
4
+ //#endregion
5
+ export { canonicalKey, canonicalise };
@@ -0,0 +1,5 @@
1
+ //#region src/convert/canonicalise.d.ts
2
+ declare function canonicalise(value: unknown): unknown;
3
+ declare function canonicalKey(value: unknown): string;
4
+ //#endregion
5
+ export { canonicalKey, canonicalise };
@@ -0,0 +1,18 @@
1
+ //#region src/convert/canonicalise.ts
2
+ function canonicalise(value) {
3
+ if (Array.isArray(value)) return value.map(canonicalise);
4
+ if (isRecord(value)) {
5
+ const sorted = {};
6
+ for (const key of Object.keys(value).sort()) sorted[key] = canonicalise(value[key]);
7
+ return sorted;
8
+ }
9
+ return value;
10
+ }
11
+ function isRecord(value) {
12
+ return typeof value === "object" && value !== null && !Array.isArray(value);
13
+ }
14
+ function canonicalKey(value) {
15
+ return JSON.stringify(canonicalise(value));
16
+ }
17
+ //#endregion
18
+ export { canonicalKey, canonicalise };
@@ -28,13 +28,14 @@ const require_ports_abort = require("../ports/abort.cjs");
28
28
  const require_layout_sheets = require("../layout/sheets.cjs");
29
29
  const require_layout_drawing = require("../layout/drawing.cjs");
30
30
  const require_layout_reconstruct = require("../layout/reconstruct.cjs");
31
+ const require_convert_factor_styles = require("./factor-styles.cjs");
31
32
  const require_convert_variant_bridges = require("./variant-bridges.cjs");
32
33
  const require_convert_capability = require("./capability.cjs");
33
34
  let ooxml_js = require("ooxml.js");
34
- let document_schema_js = require("document-schema.js");
35
+ require("document-schema.js");
36
+ let pdf_codec = require("pdf-codec");
35
37
  let odf_js = require("odf.js");
36
38
  require("markdown-codec");
37
- let pdf_codec = require("pdf-codec");
38
39
  //#region src/convert/composition.ts
39
40
  const mathMetricsAt = (sizePt) => (0, pdf_codec.loadMathFont)().metricsAt(sizePt);
40
41
  const CONTENT_FORMATS = [
@@ -217,16 +218,16 @@ function executeBridge(source, target, bytes, options) {
217
218
  buildContent = transform(content);
218
219
  }
219
220
  require_ports_abort.throwIfAborted(options?.signal);
220
- options?.onDocument?.({
221
- formatVersion: document_schema_js.DOCUMENT_PACKAGE_FORMAT_VERSION,
222
- content: buildContent
223
- });
221
+ let out;
224
222
  if (isTextFormatNode(targetNode)) {
225
223
  const text = targetNode.build(buildContent, options);
226
- return targetNode.encode(text);
224
+ out = targetNode.encode(text);
225
+ } else {
226
+ const pkg = targetNode.build(buildContent, options);
227
+ out = targetNode.encode(pkg);
227
228
  }
228
- const pkg = targetNode.build(buildContent, options);
229
- return targetNode.encode(pkg);
229
+ options?.onDocument?.(require_convert_factor_styles.assemblePackage(buildContent));
230
+ return out;
230
231
  }
231
232
  function executeToPdf(format, bytes, options) {
232
233
  if (!LAYOUT_CAPABLE.has(format)) throw new Error(`executeToPdf: '${format}' has no layout engine of its own`);
@@ -311,22 +312,23 @@ function executeToPdf(format, bytes, options) {
311
312
  }
312
313
  default: throw new Error(`executeToPdf: cannot lay out a '${content.kind}' document`);
313
314
  }
314
- options?.onDocument?.({
315
- formatVersion: document_schema_js.DOCUMENT_PACKAGE_FORMAT_VERSION,
316
- content,
317
- pages: [...pages]
318
- });
319
- if (formulas === void 0) return (0, pdf_codec.writePdf)(layout, {
320
- signal: options?.signal,
321
- onSubstitution: options?.onSubstitution,
322
- fonts
323
- });
324
- return (0, pdf_codec.writePdf)(layout, {
315
+ if (formulas === void 0) {
316
+ const out = (0, pdf_codec.writePdf)(layout, {
317
+ signal: options?.signal,
318
+ onSubstitution: options?.onSubstitution,
319
+ fonts
320
+ });
321
+ options?.onDocument?.(require_convert_factor_styles.assemblePackage(content, pages));
322
+ return out;
323
+ }
324
+ const out = (0, pdf_codec.writePdf)(layout, {
325
325
  signal: options?.signal,
326
326
  onSubstitution: options?.onSubstitution,
327
327
  formulas,
328
328
  fonts
329
329
  });
330
+ options?.onDocument?.(require_convert_factor_styles.assemblePackage(content, pages));
331
+ return out;
330
332
  }
331
333
  function executeFromPdf(target, bytes, options) {
332
334
  const node = FORMAT_NODES[target];
@@ -342,17 +344,16 @@ function executeFromPdf(target, bytes, options) {
342
344
  widthPt: page.widthPt,
343
345
  heightPt: page.heightPt
344
346
  }));
345
- options?.onDocument?.({
346
- formatVersion: document_schema_js.DOCUMENT_PACKAGE_FORMAT_VERSION,
347
- content,
348
- pages
349
- });
347
+ let out;
350
348
  if (isTextFormatNode(node)) {
351
349
  const text = node.build(content, options);
352
- return node.encode(text);
350
+ out = node.encode(text);
351
+ } else {
352
+ const pkg = node.build(content);
353
+ out = node.encode(pkg);
353
354
  }
354
- const pkg = node.build(content);
355
- return node.encode(pkg);
355
+ options?.onDocument?.(require_convert_factor_styles.assemblePackage(content, pages));
356
+ return out;
356
357
  }
357
358
  function buildCompositionGraph() {
358
359
  const adj = /* @__PURE__ */ new Map();
@@ -5,9 +5,9 @@ import { SvgDiagnosticSink } from "../svg/diagnostics.cjs";
5
5
  import { CellTypeInferenceSink } from "../layout/cell-typing.cjs";
6
6
  import { t as OmmlDiagnostic } from "../shared-DLZ3IQUl.cjs";
7
7
  import { ContentDocument, DocumentPackage, FontSubstitution, ProvidedFont } from "document-schema.js";
8
+ import { PdfDiagnosticSink, WinAnsiSubstitution } from "pdf-codec";
8
9
  import { MarkdownImageResolver } from "markdown-codec";
9
10
  import { Package } from "ooxml.js";
10
- import { PdfDiagnosticSink, WinAnsiSubstitution } from "pdf-codec";
11
11
  //#region src/convert/composition.d.ts
12
12
  type SourcePackage = Package;
13
13
  interface UnifiedConversionOptions {
@@ -6,8 +6,8 @@ import { CellTypeInferenceSink } from "../layout/cell-typing.js";
6
6
  import { t as OmmlDiagnostic } from "../shared-DUOzjwcL.js";
7
7
  import { Package } from "ooxml.js";
8
8
  import { ContentDocument, DocumentPackage, FontSubstitution, ProvidedFont } from "document-schema.js";
9
- import { MarkdownImageResolver } from "markdown-codec";
10
9
  import { PdfDiagnosticSink, WinAnsiSubstitution } from "pdf-codec";
10
+ import { MarkdownImageResolver } from "markdown-codec";
11
11
  //#region src/convert/composition.d.ts
12
12
  type SourcePackage = Package;
13
13
  interface UnifiedConversionOptions {
@@ -27,13 +27,14 @@ import { throwIfAborted } from "../ports/abort.js";
27
27
  import { convertSpreadsheetToLayout } from "../layout/sheets.js";
28
28
  import { convertDrawingToLayout } from "../layout/drawing.js";
29
29
  import { reconstructDrawing, reconstructPresentation, reconstructSpreadsheet, reconstructWordprocessing } from "../layout/reconstruct.js";
30
+ import { assemblePackage } from "./factor-styles.js";
30
31
  import { drawingToPresentation, presentationToDrawing, presentationToWordprocessing, wordprocessingToPresentation } from "./variant-bridges.js";
31
32
  import { UnsupportedConversionError } from "./capability.js";
32
33
  import { buildXlsxPackage, decodePackage, encodePackage, readXlsxContent } from "ooxml.js";
33
- import { DOCUMENT_PACKAGE_FORMAT_VERSION } from "document-schema.js";
34
+ import "document-schema.js";
35
+ import { createFontMeasurer, createFontRegistry, loadMathFont, readPdf, writePdf } from "pdf-codec";
34
36
  import { decodePackage as decodePackage$1, encodePackage as encodePackage$1 } from "odf.js";
35
37
  import "markdown-codec";
36
- import { createFontMeasurer, createFontRegistry, loadMathFont, readPdf, writePdf } from "pdf-codec";
37
38
  //#region src/convert/composition.ts
38
39
  const mathMetricsAt = (sizePt) => loadMathFont().metricsAt(sizePt);
39
40
  const CONTENT_FORMATS = [
@@ -216,16 +217,16 @@ function executeBridge(source, target, bytes, options) {
216
217
  buildContent = transform(content);
217
218
  }
218
219
  throwIfAborted(options?.signal);
219
- options?.onDocument?.({
220
- formatVersion: DOCUMENT_PACKAGE_FORMAT_VERSION,
221
- content: buildContent
222
- });
220
+ let out;
223
221
  if (isTextFormatNode(targetNode)) {
224
222
  const text = targetNode.build(buildContent, options);
225
- return targetNode.encode(text);
223
+ out = targetNode.encode(text);
224
+ } else {
225
+ const pkg = targetNode.build(buildContent, options);
226
+ out = targetNode.encode(pkg);
226
227
  }
227
- const pkg = targetNode.build(buildContent, options);
228
- return targetNode.encode(pkg);
228
+ options?.onDocument?.(assemblePackage(buildContent));
229
+ return out;
229
230
  }
230
231
  function executeToPdf(format, bytes, options) {
231
232
  if (!LAYOUT_CAPABLE.has(format)) throw new Error(`executeToPdf: '${format}' has no layout engine of its own`);
@@ -310,22 +311,23 @@ function executeToPdf(format, bytes, options) {
310
311
  }
311
312
  default: throw new Error(`executeToPdf: cannot lay out a '${content.kind}' document`);
312
313
  }
313
- options?.onDocument?.({
314
- formatVersion: DOCUMENT_PACKAGE_FORMAT_VERSION,
315
- content,
316
- pages: [...pages]
317
- });
318
- if (formulas === void 0) return writePdf(layout, {
319
- signal: options?.signal,
320
- onSubstitution: options?.onSubstitution,
321
- fonts
322
- });
323
- return writePdf(layout, {
314
+ if (formulas === void 0) {
315
+ const out = writePdf(layout, {
316
+ signal: options?.signal,
317
+ onSubstitution: options?.onSubstitution,
318
+ fonts
319
+ });
320
+ options?.onDocument?.(assemblePackage(content, pages));
321
+ return out;
322
+ }
323
+ const out = writePdf(layout, {
324
324
  signal: options?.signal,
325
325
  onSubstitution: options?.onSubstitution,
326
326
  formulas,
327
327
  fonts
328
328
  });
329
+ options?.onDocument?.(assemblePackage(content, pages));
330
+ return out;
329
331
  }
330
332
  function executeFromPdf(target, bytes, options) {
331
333
  const node = FORMAT_NODES[target];
@@ -341,17 +343,16 @@ function executeFromPdf(target, bytes, options) {
341
343
  widthPt: page.widthPt,
342
344
  heightPt: page.heightPt
343
345
  }));
344
- options?.onDocument?.({
345
- formatVersion: DOCUMENT_PACKAGE_FORMAT_VERSION,
346
- content,
347
- pages
348
- });
346
+ let out;
349
347
  if (isTextFormatNode(node)) {
350
348
  const text = node.build(content, options);
351
- return node.encode(text);
349
+ out = node.encode(text);
350
+ } else {
351
+ const pkg = node.build(content);
352
+ out = node.encode(pkg);
352
353
  }
353
- const pkg = node.build(content);
354
- return node.encode(pkg);
354
+ options?.onDocument?.(assemblePackage(content, pages));
355
+ return out;
355
356
  }
356
357
  function buildCompositionGraph() {
357
358
  const adj = /* @__PURE__ */ new Map();
@@ -12,11 +12,12 @@ const require_ports_abort = require("../ports/abort.cjs");
12
12
  const require_odb_csv = require("../odb/csv.cjs");
13
13
  const require_odb_read = require("../odb/read.cjs");
14
14
  const require_odb_spreadsheet = require("../odb/spreadsheet.cjs");
15
+ const require_convert_factor_styles = require("./factor-styles.cjs");
15
16
  const require_convert_composition = require("./composition.cjs");
16
17
  let ooxml_js = require("ooxml.js");
17
18
  let document_schema_js = require("document-schema.js");
18
- let odf_js = require("odf.js");
19
19
  let pdf_codec = require("pdf-codec");
20
+ let odf_js = require("odf.js");
20
21
  //#region src/convert/convert.ts
21
22
  const mathMetricsAt = (sizePt) => (0, pdf_codec.loadMathFont)().metricsAt(sizePt);
22
23
  function docxToPdf(bytes, options) {
@@ -70,7 +71,7 @@ function odfToPdf(bytes, options) {
70
71
  heightPt: box.heightPt
71
72
  }, document_schema_js.PAGE_SIZE_A4.heightPt);
72
73
  const layout = {
73
- formatVersion: document_schema_js.LAYOUT_FORMAT_VERSION,
74
+ formatVersion: pdf_codec.LAYOUT_FORMAT_VERSION,
74
75
  metadata: content.metadata,
75
76
  pages: [{
76
77
  widthPt: document_schema_js.PAGE_SIZE_A4.widthPt,
@@ -79,16 +80,8 @@ function odfToPdf(bytes, options) {
79
80
  }],
80
81
  images: {}
81
82
  };
82
- options?.onDocument?.({
83
- formatVersion: document_schema_js.DOCUMENT_PACKAGE_FORMAT_VERSION,
84
- content,
85
- pages: [{
86
- widthPt: document_schema_js.PAGE_SIZE_A4.widthPt,
87
- heightPt: document_schema_js.PAGE_SIZE_A4.heightPt
88
- }]
89
- });
90
83
  require_ports_abort.throwIfAborted(options?.signal);
91
- return (0, pdf_codec.writePdf)(layout, {
84
+ const out = (0, pdf_codec.writePdf)(layout, {
92
85
  signal: options?.signal,
93
86
  formulas: [{
94
87
  pageIndex: 0,
@@ -97,6 +90,11 @@ function odfToPdf(bytes, options) {
97
90
  box
98
91
  }]
99
92
  });
93
+ options?.onDocument?.(require_convert_factor_styles.assemblePackage(content, [{
94
+ widthPt: document_schema_js.PAGE_SIZE_A4.widthPt,
95
+ heightPt: document_schema_js.PAGE_SIZE_A4.heightPt
96
+ }]));
97
+ return out;
100
98
  }
101
99
  function pdfToDocx(bytes, options) {
102
100
  return require_convert_composition.convertDocument("pdf", "docx", bytes, options);
@@ -280,7 +278,6 @@ function odmToPdf(bytes, options) {
280
278
  require_ports_abort.throwIfAborted(options?.signal);
281
279
  const content = {
282
280
  kind: "wordprocessing",
283
- formatVersion: document_schema_js.CONTENT_FORMAT_VERSION,
284
281
  metadata: require_model_metadata.resolveMetadataTimestamps((0, odf_js.readOdfMetadata)(pkg), options?.clock),
285
282
  sections: combinedSections
286
283
  };
@@ -307,10 +304,7 @@ function odbToXlsx(bytes, options) {
307
304
  require_ports_abort.throwIfAborted(options?.signal);
308
305
  const content = require_odb_spreadsheet.odbTablesToSpreadsheetDocument(tables);
309
306
  const out = (0, ooxml_js.encodePackage)((0, ooxml_js.buildXlsxPackage)(content));
310
- options?.onDocument?.({
311
- formatVersion: document_schema_js.DOCUMENT_PACKAGE_FORMAT_VERSION,
312
- content
313
- });
307
+ options?.onDocument?.(require_convert_factor_styles.assemblePackage(content));
314
308
  return out;
315
309
  }
316
310
  function odbToCsv(bytes, options) {
@@ -319,10 +313,7 @@ function odbToCsv(bytes, options) {
319
313
  const tables = require_odb_read.readOdbTables(pkg, { timeZone: options?.timeZone });
320
314
  require_ports_abort.throwIfAborted(options?.signal);
321
315
  const csv = require_odb_csv.buildOdbTableCsv(tables, options?.table);
322
- if (options?.onDocument !== void 0) options.onDocument({
323
- formatVersion: document_schema_js.DOCUMENT_PACKAGE_FORMAT_VERSION,
324
- content: require_odb_spreadsheet.odbTablesToSpreadsheetDocument(tables)
325
- });
316
+ if (options?.onDocument !== void 0) options.onDocument(require_convert_factor_styles.assemblePackage(require_odb_spreadsheet.odbTablesToSpreadsheetDocument(tables)));
326
317
  return csv;
327
318
  }
328
319
  function odbReportToDocx(content, options) {
@@ -344,17 +335,14 @@ function odbReportToPdf(content, options) {
344
335
  measurer: (0, pdf_codec.createFontMeasurer)(fonts),
345
336
  mathMetricsAt
346
337
  });
347
- options?.onDocument?.({
348
- formatVersion: document_schema_js.DOCUMENT_PACKAGE_FORMAT_VERSION,
349
- content,
350
- pages: [...pages]
351
- });
352
- return (0, pdf_codec.writePdf)(layout, {
338
+ const out = (0, pdf_codec.writePdf)(layout, {
353
339
  signal: options?.signal,
354
340
  onSubstitution: options?.onSubstitution,
355
341
  formulas,
356
342
  fonts
357
343
  });
344
+ options?.onDocument?.(require_convert_factor_styles.assemblePackage(content, pages));
345
+ return out;
358
346
  }
359
347
  //#endregion
360
348
  exports.OdmUnresolvedSectionError = OdmUnresolvedSectionError;
@@ -5,8 +5,8 @@ import { t as OmmlDiagnostic } from "../shared-DLZ3IQUl.cjs";
5
5
  import { HsqldbDecodeOptions } from "../hsqldb/rowformat.cjs";
6
6
  import { DocumentFontRegistryOptions } from "../fonts/registry.cjs";
7
7
  import { ContentDocument, ContentSection, DocumentPackage } from "document-schema.js";
8
- import { MarkdownImageResolver } from "markdown-codec";
9
8
  import { PdfDiagnosticSink, WinAnsiSubstitution } from "pdf-codec";
9
+ import { MarkdownImageResolver } from "markdown-codec";
10
10
  import { OdmSection, Package } from "odf.js";
11
11
  //#region src/convert/convert.d.ts
12
12
  interface DocumentToPdfOptions extends DocumentFontRegistryOptions {
@@ -5,9 +5,9 @@ import { t as OmmlDiagnostic } from "../shared-DUOzjwcL.js";
5
5
  import { HsqldbDecodeOptions } from "../hsqldb/rowformat.js";
6
6
  import { DocumentFontRegistryOptions } from "../fonts/registry.js";
7
7
  import { ContentDocument, ContentSection, DocumentPackage } from "document-schema.js";
8
+ import { PdfDiagnosticSink, WinAnsiSubstitution } from "pdf-codec";
8
9
  import { OdmSection, Package } from "odf.js";
9
10
  import { MarkdownImageResolver } from "markdown-codec";
10
- import { PdfDiagnosticSink, WinAnsiSubstitution } from "pdf-codec";
11
11
  //#region src/convert/convert.d.ts
12
12
  interface DocumentToPdfOptions extends DocumentFontRegistryOptions {
13
13
  readonly signal?: AbortSignal;