js.documents 7.1.6 → 7.1.8
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 +15 -13
- package/dist/convert/composition.cjs +5 -1
- package/dist/convert/composition.js +6 -2
- package/dist/convert/convert.cjs +3 -3
- package/dist/convert/convert.d.cts +2 -2
- package/dist/convert/convert.d.ts +2 -2
- package/dist/convert/convert.js +3 -3
- package/dist/convert/variant-bridges.cjs +67 -0
- package/dist/convert/variant-bridges.d.cts +5 -1
- package/dist/convert/variant-bridges.d.ts +5 -1
- package/dist/convert/variant-bridges.js +67 -1
- package/package.json +12 -12
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/ExaDev/documents.js/tree/main/packages/documents.js) [](https://www.npmjs.com/package/documents.js) [](https://www.npmjs.com/package/documents.js) [](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, markdown, rtf, and the three legacy binary formats doc/xls/ppt ([MS-DOC], BIFF8, [MS-PPT], each wrapped in an [MS-CFB] compound file) all read into and build from the same shared `ContentDocument` model (reported to callers as the tree-form `DocumentTree`), with PDF — reached through pdf-codec's own `LayoutDocument` view — as the one format every variant can reach; wpd (WordPerfect 6.x-X6) reads into the same wordprocessing variant as a read-only source, routable everywhere the others are but never buildable as a target, since wpd-codec ships no writer. A composition engine (`convertDocument`) routes
|
|
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, markdown, rtf, and the three legacy binary formats doc/xls/ppt ([MS-DOC], BIFF8, [MS-PPT], each wrapped in an [MS-CFB] compound file) all read into and build from the same shared `ContentDocument` model (reported to callers as the tree-form `DocumentTree`), with PDF — reached through pdf-codec's own `LayoutDocument` view — as the one format every variant can reach; wpd (WordPerfect 6.x-X6) reads into the same wordprocessing variant as a read-only source, routable everywhere the others are but never buildable as a target, since wpd-codec ships no writer. A composition engine (`convertDocument`) routes 217 (source, target) pairs across the fifteen content formats and PDF, including twenty-eight PDF-pivot round trips (the eight layout-engine formats, plus xlsx/csv/xls composing through ods, and rtf/doc composing through docx/odt/markdown and ppt composing through pptx/odp), twenty-four cross-format bridge functions (same-variant direct copies, cross-variant semantic transforms, and PDF-composed — two of which, `xlsxToMarkdown`/`csvToMarkdown`, became one-directional cross-variant transforms in their own right when [ExaDev/documents.js#1043](https://github.com/ExaDev/documents.js/issues/1043) registered `spreadsheetToWordprocessing`), fifteen one-way wpd-sourced routes, 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](../ooxml.js/README.md), [odf.js](../odf.js/README.md), [pdf-codec](../pdf-codec/README.md), [markdown-codec](../markdown-codec/README.md), [rtf-codec](../rtf-codec/README.md), [wpd-codec](../wpd-codec/README.md), [doc-codec](../doc-codec/README.md), [xls-codec](../xls-codec/README.md), [ppt-codec](../ppt-codec/README.md), [archive-codec](../archive-codec/README.md), and [document-schema.js](../document-schema.js/README.md).
|
|
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
|
|
|
@@ -98,7 +98,7 @@ npm install documents.js document-schema.js
|
|
|
98
98
|
|
|
99
99
|
### The generic entry point: `convertDocument`
|
|
100
100
|
|
|
101
|
-
A single function, `convertDocument`, sits behind every named conversion and reaches every pair the composition engine can route — all
|
|
101
|
+
A single function, `convertDocument`, sits behind every named conversion and reaches every pair the composition engine can route — all 217 supported (source, target) combinations. The named functions below are thin one-line forwarders to it; they remain the ergonomic layer for a caller who wants a fixed pair and autocomplete discovery, while `convertDocument` is the first-class entry point for a caller working from a runtime format pair (CLI, MCP tool, matrix enumeration).
|
|
102
102
|
|
|
103
103
|
```ts
|
|
104
104
|
import { convertDocument } from "documents.js";
|
|
@@ -200,7 +200,7 @@ Each accepts an optional `signal` (`AbortSignal`) and either `onSubstitution` (X
|
|
|
200
200
|
|
|
201
201
|
### Cross-format bridges
|
|
202
202
|
|
|
203
|
-
Twenty-
|
|
203
|
+
Twenty-two bridge functions genuinely bypass the PDF pivot where a direct path exists. Eight same-variant direct-copy pairs (`odtToDocx`/`docxToOdt`, `odpToPptx`/`pptxToOdp`, `odsToXlsx`/`xlsxToOds`, `csvToOds`/`odsToCsv`, `csvToXlsx`/`xlsxToCsv`, `svgToOdg`/`odgToSvg`, `markdownToDocx`/`docxToMarkdown`, `markdownToOdt`/`odtToMarkdown`) compose a direct `readXContent` → `buildYPackage` pivot copy — the csv pairs are one hop to its spreadsheet siblings, so csv never needs PDF to reach ods or xlsx, and `svgToOdg`/`odgToSvg` bridge svg to its drawing sibling odg the same way. Two cross-variant semantic-transform pairs (`docxToPptx`/`pptxToDocx`, `odtToOdp`/`odpToOdt`) go through `src/convert/variant-bridges.ts`. `xlsxToMarkdown` and `csvToMarkdown` join them as two one-directional cross-variant transforms since [ExaDev/documents.js#1043](https://github.com/ExaDev/documents.js/issues/1043) registered `spreadsheetToWordprocessing` (also in `variant-bridges.ts`): their own reverse direction (`markdownToXlsx`/`markdownToCsv`) has no transform to take instead, so those two still route through PDF internally, since a markdown table has no cell types, formulas, or geometry of its own to recover — the lossiest conversions left in the package.
|
|
204
204
|
|
|
205
205
|
```ts
|
|
206
206
|
import {
|
|
@@ -233,7 +233,7 @@ const { document, diagnostics } = await converter.convert(
|
|
|
233
233
|
);
|
|
234
234
|
```
|
|
235
235
|
|
|
236
|
-
`DocumentFormat` includes `docx`/`pptx`/`xlsx`/`odt`/`odp`/`ods`/`odg`/`svg`/`odf`/`csv`/`markdown`/`rtf`/`doc`/`xls`/`ppt`/`wpd`/`pdf` — seventeen members, `wpd` the one read-only member: it appears as a source in `conversions` but never as a target, since wpd-codec ships no writer. The port's `conversions` list is derived from `resolveCompositionPlan` plus the `odf`→`pdf` special case —
|
|
236
|
+
`DocumentFormat` includes `docx`/`pptx`/`xlsx`/`odt`/`odp`/`ods`/`odg`/`svg`/`odf`/`csv`/`markdown`/`rtf`/`doc`/`xls`/`ppt`/`wpd`/`pdf` — seventeen members, `wpd` the one read-only member: it appears as a source in `conversions` but never as a target, since wpd-codec ships no writer. The port's `conversions` list is derived from `resolveCompositionPlan` plus the `odf`→`pdf` special case — 217 pairs total. `DocumentFormat` is inferred from `DocumentFormatSchema` (a real Zod schema); `DOCUMENT_FORMATS` is exported as a plain array derived from the same schema:
|
|
237
237
|
|
|
238
238
|
The port also exposes `contractVersion: number`, bumped only when `DocumentConverter`'s own contract shape changes — a new field on `ConversionResult` a caller might need to branch on, or a new `ConversionOptions` field an implementation is now expected to honour — never when the `conversions` table simply grows with more supported source/target pairs (that's discoverable at runtime via `conversions` itself). It is currently `7`: the bump from `6` reflects `ConversionResult.package` changing type to the tree-form `DocumentTree` described below, which a caller reading that field must now flatten rather than read directly.
|
|
239
239
|
|
|
@@ -542,7 +542,7 @@ const layout = readPdf(pdfBytes); // -> LayoutDocument: pages of positioned text
|
|
|
542
542
|
const bytes = writePdf(layout);
|
|
543
543
|
```
|
|
544
544
|
|
|
545
|
-
The fifteen PDF round trips and sixteen PDF-bypassing bridge directions are also available as schema-validated [`z.codec()`](https://zod.dev) pairs (`pdfCodec`, `docxPdfCodec`, `pptxPdfCodec`, `odtPdfCodec`, `odpPdfCodec`, `odsPdfCodec`, `odgPdfCodec`, `svgPdfCodec`, `xlsxPdfCodec`, `csvPdfCodec`, `markdownPdfCodec`, `rtfPdfCodec`, `docPdfCodec`, `xlsPdfCodec`, `pptPdfCodec`, `odtDocxCodec`, `odpPptxCodec`, `odsXlsxCodec`, `odsCsvCodec`, `xlsxCsvCodec`, `odgSvgCodec`, `markdownDocxCodec`, `markdownOdtCodec`) — the no-options form, adding automatic two-way schema validation. The two
|
|
545
|
+
The fifteen PDF round trips and sixteen PDF-bypassing bridge directions are also available as schema-validated [`z.codec()`](https://zod.dev) pairs (`pdfCodec`, `docxPdfCodec`, `pptxPdfCodec`, `odtPdfCodec`, `odpPdfCodec`, `odsPdfCodec`, `odgPdfCodec`, `svgPdfCodec`, `xlsxPdfCodec`, `csvPdfCodec`, `markdownPdfCodec`, `rtfPdfCodec`, `docPdfCodec`, `xlsPdfCodec`, `pptPdfCodec`, `odtDocxCodec`, `odpPptxCodec`, `odsXlsxCodec`, `odsCsvCodec`, `xlsxCsvCodec`, `odgSvgCodec`, `markdownDocxCodec`, `markdownOdtCodec`) — the no-options form, adding automatic two-way schema validation. The two asymmetric pairs above have codec forms too (`xlsxMarkdownCodec`, `csvMarkdownCodec`) — decode is now `spreadsheetToWordprocessing`'s direct cross-variant transform, encode still composes through PDF:
|
|
546
546
|
|
|
547
547
|
```ts
|
|
548
548
|
import { z } from "zod";
|
|
@@ -930,29 +930,31 @@ To run a single test file: `pnpm vitest run src/path/to/file.test.ts`.
|
|
|
930
930
|
|
|
931
931
|
## Fidelity
|
|
932
932
|
|
|
933
|
-
Read as **row → column**. `✓` lossless, `~` bounded, `✗` lossy, `✗✗` severe, `→` one-way, `–` no conversion. `.odm`/`.odb` sit outside this table. `rtf` is wired into the composition engine and bidirectionally routable to every other format here except `csv`/`xlsx` (one hop past the pathfinder's own 3-hop cap) and `odf` (excluded from routing entirely — see below). `wpd` is wired in as a read-only source (see composition.ts's own `ReadOnlyContentFormat`): routable to every other format here except `odf`, exactly like `rtf`'s reach, but every one of its cells is `→` rather than a fidelity grade, since a read-only format has no reverse direction to compare against and no column of its own — nothing ever routes to `wpd`, wpd-codec having no writer at all. Every cell below is derived from `resolveCompositionPlan`'s actual resolved route for that pair (hop count and hop kind — same-variant bridge, cross-variant transform, or PDF pivot), not hand-reasoned: the pathfinder generates the full cross-product of same-variant and cross-variant-transform edges, so a pair can be routable even with no named convenience function for it (e.g. `odp → docx`, `odt → pptx`). `doc`/`xls`/`ppt` (the three legacy binary formats — see doc-codec/xls-codec/ppt-codec's own READMEs for exactly what each reads/writes) are wired into the composition engine too, and bidirectionally routable to most of the formats here
|
|
933
|
+
Read as **row → column**. `✓` lossless, `~` bounded, `✗` lossy, `✗✗` severe, `→` one-way, `–` no conversion. `.odm`/`.odb` sit outside this table. `rtf` is wired into the composition engine and bidirectionally routable to every other format here except as a source reaching `csv`/`xlsx` (`rtf → csv`/`rtf → xlsx`, one hop past the pathfinder's own 3-hop cap) and `odf` (excluded from routing entirely — see below); the reverse `csv → rtf`/`xlsx → rtf` routes since [ExaDev/documents.js#1043](https://github.com/ExaDev/documents.js/issues/1043) registered a one-way spreadsheet → wordprocessing transform (see below). `wpd` is wired in as a read-only source (see composition.ts's own `ReadOnlyContentFormat`): routable to every other format here except `odf`, exactly like `rtf`'s reach, but every one of its cells is `→` rather than a fidelity grade, since a read-only format has no reverse direction to compare against and no column of its own — nothing ever routes to `wpd`, wpd-codec having no writer at all. Every cell below is derived from `resolveCompositionPlan`'s actual resolved route for that pair (hop count and hop kind — same-variant bridge, cross-variant transform, or PDF pivot), not hand-reasoned: the pathfinder generates the full cross-product of same-variant and cross-variant-transform edges, so a pair can be routable even with no named convenience function for it (e.g. `odp → docx`, `odt → pptx`). `doc`/`xls`/`ppt` (the three legacy binary formats — see doc-codec/xls-codec/ppt-codec's own READMEs for exactly what each reads/writes) are wired into the composition engine too, and bidirectionally routable to most of the formats here — `doc` reaches everything except, as a source, `csv`/`xlsx`/`xls`; `xls` reaches everything except, as a source, `rtf`/`ppt` (the identical one-hop-too-many gap `rtf`'s own `csv`/`xlsx` pair has, for the same reason — none of the three has a toPdf/fromPdf edge of its own); `ppt` reaches everything except, as a source, `csv`/`xlsx`/`xls` — like the `rtf` extension before them (ExaDev/documents.js#853), all three now carry a real per-pair fidelity audit in the table below ([ExaDev/documents.js#880](https://github.com/ExaDev/documents.js/issues/880)), checked the same way: every `doc`/`xls`/`ppt` cell is verified against `resolveCompositionPlan`'s actual resolved route for that pair plus that codec's own documented read/write scope, not guessed from the hop shape alone.
|
|
934
|
+
|
|
935
|
+
**Since [ExaDev/documents.js#1043](https://github.com/ExaDev/documents.js/issues/1043)**, `spreadsheetToWordprocessing` (`src/convert/variant-bridges.ts`) registers a one-way spreadsheet → wordprocessing content transform: `csv`/`ods`/`xls`/`xlsx` reach `docx`/`odt`/`markdown`/`rtf`/`doc` directly as a single cross-variant bridge hop (each sheet becomes an H2-headed section plus one table, hidden rows/columns excluded — formulas, print settings, comments, and anchored images/embedded objects have no wordprocessing counterpart and are silently out of scope), and reach `pptx`/`odp`/`ppt` through that same bridge plus the existing wordprocessing → presentation transform (two bridge hops). The reverse direction has no registered transform — a wordprocessing/presentation table has no cell types, formulas, or geometry of its own to recover into a spreadsheet — so every column-side cell for `xlsx`/`ods`/`csv`/`xls` (a wordprocessing/presentation format converting into a spreadsheet) is unaffected by this change.
|
|
934
936
|
|
|
935
937
|
| ↓ from \ to → | docx | pptx | xlsx | odt | odp | ods | odg | svg | odf | markdown | csv | rtf | doc | xls | ppt | wpd | pdf |
|
|
936
938
|
| ------------- | ---- | ---- | ---- | --- | --- | --- | --- | --- | --- | -------- | --- | --- | --- | --- | --- | --- | --- |
|
|
937
939
|
| **docx** | — | ~ | ✗ | ✓ | ~ | ✗ | ✗ | ✗ | – | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | – | ~ |
|
|
938
940
|
| **pptx** | ~ | — | ✗ | ~ | ✓ | ✗ | ~ | ~ | – | ~ | ✗ | ~ | ✗ | ✗ | ✗ | – | ~ |
|
|
939
|
-
| **xlsx** | ✗ | ✗ | — | ✗ | ✗ | ~ | ✗ | ✗ | – |
|
|
941
|
+
| **xlsx** | ✗ | ✗ | — | ✗ | ✗ | ~ | ✗ | ✗ | – | ✗ | ~ | ✗ | ✗ | ✗ | ✗ | – | ~ |
|
|
940
942
|
| **odt** | ✓ | ~ | ✗ | — | ~ | ✗ | ✗ | ✗ | – | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | – | ~ |
|
|
941
943
|
| **odp** | ~ | ✓ | ✗ | ~ | — | ✗ | ~ | ~ | – | ~ | ✗ | ~ | ✗ | ✗ | ✗ | – | ~ |
|
|
942
|
-
| **ods** | ✗ | ✗ | ~ | ✗ | ✗ | — | ✗ | ✗ | – |
|
|
944
|
+
| **ods** | ✗ | ✗ | ~ | ✗ | ✗ | — | ✗ | ✗ | – | ✗ | ~ | ✗ | ✗ | ✗ | ✗ | – | ~ |
|
|
943
945
|
| **odg** | ✗ | ~ | ✗ | ✗ | ~ | ✗ | — | ✓ | – | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | – | ~ |
|
|
944
946
|
| **svg** | ✗ | ~ | ✗ | ✗ | ~ | ✗ | ✓ | — | – | ✗✗ | ✗✗ | ✗ | ✗ | ✗ | ✗ | – | ~ |
|
|
945
947
|
| **odf** | – | – | – | – | – | – | – | – | — | – | – | – | – | – | – | – | → |
|
|
946
948
|
| **markdown** | ~ | ~ | ✗✗ | ~ | ~ | ✗ | ✗ | ✗✗ | – | — | ✗✗ | ~ | ✗ | ✗✗ | ✗ | – | ~ |
|
|
947
|
-
| **csv** | ✗ | ✗ | ✓ | ✗ | ✗ | ✓ | ✗ | ✗ | – |
|
|
949
|
+
| **csv** | ✗ | ✗ | ✓ | ✗ | ✗ | ✓ | ✗ | ✗ | – | ✗ | — | ✗ | ✗ | ✓ | ✗ | – | ~ |
|
|
948
950
|
| **rtf** | ~ | ~ | – | ~ | ~ | ✗ | ✗ | ✗ | – | ✗ | – | — | ✗ | – | ✗ | – | ~ |
|
|
949
951
|
| **doc** | ✗ | ✗ | – | ✗ | ✗ | ✗ | ✗ | ✗ | – | ✗ | – | ✗ | — | – | ✗ | – | ~ |
|
|
950
|
-
| **xls** | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | – |
|
|
952
|
+
| **xls** | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | – | ✗ | ~ | ✗ | ✗ | — | ✗ | – | ~ |
|
|
951
953
|
| **ppt** | ✗ | ✗ | – | ✗ | ✗ | ✗ | ✗ | ✗ | – | ✗ | – | ✗ | ✗ | – | — | – | ~ |
|
|
952
954
|
| **wpd** | → | → | → | → | → | → | → | → | – | → | → | → | → | → | → | — | → |
|
|
953
955
|
| **pdf** | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | – | ✗✗ | ✗ | ✗ | ✗ | ✗ | ✗ | – | — |
|
|
954
956
|
|
|
955
|
-
|
|
957
|
+
217 of 272 directional pairs are routable. The shared `ContentDocument` model is the hub, not PDF — 150 of those 217 directional pairs (99 counted as an undirected format relationship: 51 run both ways; 38 are one-directional only — the one-way spreadsheet → wordprocessing/presentation reach `spreadsheetToWordprocessing` added ([ExaDev/documents.js#1043](https://github.com/ExaDev/documents.js/issues/1043)): `csv`/`ods`/`xls`/`xlsx` as source, reaching every wordprocessing member directly and every presentation member through one; the remaining ten don't run either way, all of them `wpd`'s own outbound-only routes) resolve to a route whose every hop is a bridge, never touching PDF at all, confirmed by walking `resolveCompositionPlan` over every pair and checking no hop's executor is `toPdf`/`fromPdf`.
|
|
956
958
|
|
|
957
959
|
**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.
|
|
958
960
|
|
|
@@ -966,7 +968,7 @@ Read as **row → column**. `✓` lossless, `~` bounded, `✗` lossy, `✗✗` s
|
|
|
966
968
|
|
|
967
969
|
**PDF → ods** recovers what was printed, not what was entered. The printed string always survives in `displayText`; re-typed `value` is explicitly probabilistic inference.
|
|
968
970
|
|
|
969
|
-
**`markdownToPdf`/`pdfToMarkdown`** is the lossiest round trip: `markdownToPdf` is faithful, but `pdfToMarkdown` stacks reconstruction lossiness PLUS markdown's coarser vocabulary (no colour, font, size, alignment). Two structure signals do survive it (ExaDev/documents.js#584): every page boundary arrives as an exact `<!-- page break -->` marker (one per boundary — the one fact a rendered PDF states precisely), and headings are inferred from font size — each distinct size at least 2pt above the document's modal body size is a heading, ranked largest-first into `Heading1..6`, which inverts this package's own heading render sizes exactly and is a heuristic (the well-worn "largest text is the title" reading) for any other producer. Tables reach markdown as GFM pipe tables wherever the gridline-lattice gate succeeds; a table rendered without drawn gridlines (which includes every `markdownToPdf`-authored one, markdown carrying no border concept) correctly comes back as tab-separated prose rather than invented structure.
|
|
971
|
+
**`markdownToPdf`/`pdfToMarkdown`** is the lossiest round trip: `markdownToPdf` is faithful, but `pdfToMarkdown` stacks reconstruction lossiness PLUS markdown's coarser vocabulary (no colour, font, size, alignment). Two structure signals do survive it (ExaDev/documents.js#584): every page boundary arrives as an exact `<!-- page break -->` marker (one per boundary — the one fact a rendered PDF states precisely), and headings are inferred from font size — each distinct size at least 2pt above the document's modal body size is a heading, ranked largest-first into `Heading1..6`, which inverts this package's own heading render sizes exactly and is a heuristic (the well-worn "largest text is the title" reading) for any other producer. Tables reach markdown as GFM pipe tables wherever the gridline-lattice gate succeeds; a table rendered without drawn gridlines (which includes every `markdownToPdf`-authored one, markdown carrying no border concept) correctly comes back as tab-separated prose rather than invented structure. `markdownToXlsx`/`markdownToCsv` still stack the same two losses on their own encode leg — hence their `✗` cells (`markdown` → `xlsx`/`csv`/`xls`, and the bare-route `markdown` → `ods` alongside them). The reverse direction (`xlsx`/`csv`/`xls`/`ods` → `markdown`) no longer touches PDF at all since [ExaDev/documents.js#1043](https://github.com/ExaDev/documents.js/issues/1043) registered `spreadsheetToWordprocessing`: a sheet becomes a heading plus one table directly, dropping only what a wordprocessing document genuinely has no field for (formulas, print settings, comments, anchored images/embedded objects) rather than stacking `pdfToMarkdown`'s reconstruction lossiness and coarser vocabulary on top — real content loss, but a single loss rather than two stacked ones, hence `✗` rather than `✗✗`.
|
|
970
972
|
|
|
971
973
|
**The same-variant bridge pairs** (odt⇄docx, odp⇄pptx, ods⇄xlsx, csv⇄ods, csv⇄xlsx, csv⇄xls, svg⇄odg, plus rtf's own docx⇄rtf/odt⇄rtf) bypass PDF entirely — no layout engine, no reconstruction. Text, styling, tables, lists, rotated shapes survive completely wherever both formats' `ContentDocument` coverage matches. `ods⇄xlsx` has small format-boundary limits (time cells, formula dialects). Embedded formulas survive `odtToDocx` as real OOXML math. The csv pairs are bounded by what csv itself carries: toward ods/xlsx/xls nothing the csv had is lost, while writing to csv collapses each cell to its `displayText` — formulas become their rendered values, formatting disappears, and a multi-sheet source must name the sheet it wants. The svg pair carries the six vector primitives losslessly in both directions; its one asymmetry is paint defaults — SVG's absent-fill-is-black versus a drawing frame's no-fill. `docx⇄rtf`/`odt⇄rtf` are asymmetric the other way round: rtf-codec's own [Scope](../rtf-codec/README.md#scope) carries colour, font family/size, alignment, multiple sections, cell borders/shading/both merge directions, bookmarks, and tracked changes, so nothing docx/odt already lost crossing to rtf and back is lost a second time going rtf → docx/odt. What going docx/odt → rtf still drops is superscript/subscript and right-to-left text — and neither is an RTF gap: `ContentRun` carries no vertical-alignment or direction field at all, so the same content is lost crossing to _any_ format in this family, exactly as `epub-codec` reports for its own `<sub>`/`<sup>`.
|
|
972
974
|
|
|
@@ -978,7 +980,7 @@ Read as **row → column**. `✓` lossless, `~` bounded, `✗` lossy, `✗✗` s
|
|
|
978
980
|
|
|
979
981
|
**`doc`'s own bridges lose far more than `rtf`'s did, and its writer refuses rather than approximates a genuine construct kind.** doc-codec's reader (see that package's own [Status](../doc-codec/README.md#status)) reads real tables (row/column/cell structure, horizontal and vertical merge) but still drops images, style-inherited formatting, subdocuments, every section beyond the first, numbering definitions, metadata, and hyperlinks/fields — a much narrower read than docx/odt offer, so every `doc → X` cell in this table stays `✗` regardless of `X`'s own richness: that content is already gone before any target is reached, tables now surviving the crossing notwithstanding. Going the other way, `writeDocContent` writes a `ContentTable` (a table nested inside a table cell is the one genuine table-shaped refusal, alongside cell shading/borders it silently does not carry — see doc-codec's own [Tables](../doc-codec/README.md#tables) section) but still does not approximate an image or a section beyond the first — it throws `DocUnsupportedError` rather than dropping or flattening either. Every `X → doc` cell where `X`'s own content can genuinely carry an image (`docx`, `odt`, `markdown`, `rtf`, and anything reconstructed with one via a PDF pivot) stays rated `✗` on the same basis as every other `doc` cell, and for those sources that direction can still mean the conversion fails outright for a real document rather than merely losing formatting — there is no partial write for a document containing an image, though a table alone no longer forces that failure.
|
|
980
982
|
|
|
981
|
-
**`xls`'s writer never touches formulas, per-cell fonts, or images**, so `xls⇄xlsx`/`xls⇄ods` are rated `✗` rather than the `~` their full-featured `xlsx⇄ods` counterpart earns: a real workbook's formulas and per-cell font are silently dropped in both directions (neither is ever read by xls-codec, so neither can be written back either — see that package's own [Writer scope](../xls-codec/README.md#writer-scope) and [Read-side gaps](../xls-codec/README.md#read-side-gaps)), a materially bigger gap than `xlsx⇄ods`'s own "small format-boundary limits" (time cells, formula dialects). A cell's background fill and per-side borders, its own alignment, a sheet's print settings, and the workbook's own metadata do all survive the crossing — each is read and written for real by xls-codec — so the downgrade rests on formulas and font alone. `csv⇄xls` is the one `xls` pair that escapes this downgrade: csv can carry neither formulas nor per-cell fonts to begin with, so it loses nothing crossing into `xls` that it would not also lose crossing into `xlsx`/`ods` — `csv → xls` is `✓`, matching `csv → xlsx`/`csv → ods`, and `xls → csv` is `~`, matching `xlsx → csv`, since csv always collapses a cell to its `displayText` regardless of how rich the source was. `xls
|
|
983
|
+
**`xls`'s writer never touches formulas, per-cell fonts, or images**, so `xls⇄xlsx`/`xls⇄ods` are rated `✗` rather than the `~` their full-featured `xlsx⇄ods` counterpart earns: a real workbook's formulas and per-cell font are silently dropped in both directions (neither is ever read by xls-codec, so neither can be written back either — see that package's own [Writer scope](../xls-codec/README.md#writer-scope) and [Read-side gaps](../xls-codec/README.md#read-side-gaps)), a materially bigger gap than `xlsx⇄ods`'s own "small format-boundary limits" (time cells, formula dialects). A cell's background fill and per-side borders, its own alignment, a sheet's print settings, and the workbook's own metadata do all survive the crossing — each is read and written for real by xls-codec — so the downgrade rests on formulas and font alone. `csv⇄xls` is the one `xls` pair that escapes this downgrade: csv can carry neither formulas nor per-cell fonts to begin with, so it loses nothing crossing into `xls` that it would not also lose crossing into `xlsx`/`ods` — `csv → xls` is `✓`, matching `csv → xlsx`/`csv → ods`, and `xls → csv` is `~`, matching `xlsx → csv`, since csv always collapses a cell to its `displayText` regardless of how rich the source was. `xls → markdown` routes through the identical `spreadsheetToWordprocessing` transform `xlsx → markdown`/`csv → markdown` do (ExaDev/documents.js#1043) — dropping the same formulas/print-settings/comments/anchored-content, plus `xls`'s own already-dropped per-cell fonts, but nothing extra — so it earns the same `✗` those two do rather than a worse grade. `markdown → xls` has no reverse transform to take and still composes through `ods` and PDF, exactly as `markdown → xlsx`/`markdown → csv` do. A `.xls` cell grid outside BIFF8's own 65536-row/256-column limit is refused outright, the one write-side hard stop `xls-codec` shares with `doc-codec`'s own refusals.
|
|
982
984
|
|
|
983
985
|
**`ppt`'s writer drops what it can't express instead of throwing**, unlike `doc`'s: an image, table, or OLE-embedded shape is silently excluded from the written text body rather than refusing the whole conversion (see that package's own [Writing a document](../ppt-codec/README.md#writing-a-document)) — genuinely gentler failure behaviour than `doc`'s hard stop, but the loss itself is just as real, so `ppt` cells are rated `✗` on the same basis as `doc`'s rather than upgraded for failing more softly. `ppt`'s reader is narrower again than `pptx`'s own: only plain text-box shapes with basic character formatting come back at all — no images, tables, OLE objects, masters, layouts, scheme colours, or per-shape insets (see that package's own [What it does not read yet](../ppt-codec/README.md#what-it-does-not-read-yet)) — so every `ppt → X` cell already reflects that ceiling before `X`'s own capacity ever matters.
|
|
984
986
|
|
|
@@ -281,6 +281,10 @@ const TRANSFORMS = {
|
|
|
281
281
|
"presentation->drawing": (doc) => {
|
|
282
282
|
if (doc.kind !== "presentation") throw new Error("presentationToDrawing: expected a presentation ContentDocument");
|
|
283
283
|
return require_convert_variant_bridges.presentationToDrawing(doc);
|
|
284
|
+
},
|
|
285
|
+
"spreadsheet->wordprocessing": (doc) => {
|
|
286
|
+
if (doc.kind !== "spreadsheet") throw new Error("spreadsheetToWordprocessing: expected a spreadsheet ContentDocument");
|
|
287
|
+
return require_convert_variant_bridges.spreadsheetToWordprocessing(doc);
|
|
284
288
|
}
|
|
285
289
|
};
|
|
286
290
|
const RECONSTRUCTORS = {
|
|
@@ -370,7 +374,7 @@ function buildCompositionGraph() {
|
|
|
370
374
|
if (FORMAT_NODES[a].variant !== fromVariant) continue;
|
|
371
375
|
for (const b of CONTENT_FORMATS) {
|
|
372
376
|
if (FORMAT_NODES[b].variant !== toVariant) continue;
|
|
373
|
-
|
|
377
|
+
addDirected(a, b, 2);
|
|
374
378
|
}
|
|
375
379
|
}
|
|
376
380
|
}
|
|
@@ -25,7 +25,7 @@ import { writePptContent } from "../ppt/write.js";
|
|
|
25
25
|
import { throwIfAborted } from "../ports/abort.js";
|
|
26
26
|
import { reconstructDrawing, reconstructPresentation, reconstructSpreadsheet, reconstructWordprocessing } from "../layout/reconstruct.js";
|
|
27
27
|
import { stampPdfPackageTables } from "./pdf-package-tables.js";
|
|
28
|
-
import { drawingToPresentation, presentationToDrawing, presentationToWordprocessing, wordprocessingToPresentation } from "./variant-bridges.js";
|
|
28
|
+
import { drawingToPresentation, presentationToDrawing, presentationToWordprocessing, spreadsheetToWordprocessing, wordprocessingToPresentation } from "./variant-bridges.js";
|
|
29
29
|
import { UnsupportedConversionError } from "./capability.js";
|
|
30
30
|
import { buildXlsxPackageFromContent, decodePackage, encodePackage, readXlsxContent } from "ooxml.js";
|
|
31
31
|
import { assembleTree } from "document-schema.js";
|
|
@@ -280,6 +280,10 @@ const TRANSFORMS = {
|
|
|
280
280
|
"presentation->drawing": (doc) => {
|
|
281
281
|
if (doc.kind !== "presentation") throw new Error("presentationToDrawing: expected a presentation ContentDocument");
|
|
282
282
|
return presentationToDrawing(doc);
|
|
283
|
+
},
|
|
284
|
+
"spreadsheet->wordprocessing": (doc) => {
|
|
285
|
+
if (doc.kind !== "spreadsheet") throw new Error("spreadsheetToWordprocessing: expected a spreadsheet ContentDocument");
|
|
286
|
+
return spreadsheetToWordprocessing(doc);
|
|
283
287
|
}
|
|
284
288
|
};
|
|
285
289
|
const RECONSTRUCTORS = {
|
|
@@ -369,7 +373,7 @@ function buildCompositionGraph() {
|
|
|
369
373
|
if (FORMAT_NODES[a].variant !== fromVariant) continue;
|
|
370
374
|
for (const b of CONTENT_FORMATS) {
|
|
371
375
|
if (FORMAT_NODES[b].variant !== toVariant) continue;
|
|
372
|
-
|
|
376
|
+
addDirected(a, b, 2);
|
|
373
377
|
}
|
|
374
378
|
}
|
|
375
379
|
}
|
package/dist/convert/convert.cjs
CHANGED
|
@@ -173,12 +173,12 @@ function pptToPdf(bytes, options) {
|
|
|
173
173
|
function xlsxToMarkdown(bytes, options) {
|
|
174
174
|
return require_convert_composition_to_pdf.convertDocument("xlsx", "markdown", bytes, options);
|
|
175
175
|
}
|
|
176
|
-
function markdownToXlsx(bytes, options) {
|
|
177
|
-
return require_convert_composition_to_pdf.convertDocument("markdown", "xlsx", bytes, options);
|
|
178
|
-
}
|
|
179
176
|
function csvToMarkdown(bytes, options) {
|
|
180
177
|
return require_convert_composition_to_pdf.convertDocument("csv", "markdown", bytes, options);
|
|
181
178
|
}
|
|
179
|
+
function markdownToXlsx(bytes, options) {
|
|
180
|
+
return require_convert_composition_to_pdf.convertDocument("markdown", "xlsx", bytes, options);
|
|
181
|
+
}
|
|
182
182
|
function markdownToCsv(bytes, options) {
|
|
183
183
|
return require_convert_composition_to_pdf.convertDocument("markdown", "csv", bytes, options);
|
|
184
184
|
}
|
|
@@ -89,9 +89,9 @@ declare function rtfToPdf(bytes: Uint8Array<ArrayBuffer>, options?: DocumentToPd
|
|
|
89
89
|
declare function docToPdf(bytes: Uint8Array<ArrayBuffer>, options?: DocumentToPdfOptions): Uint8Array<ArrayBuffer>;
|
|
90
90
|
declare function xlsToPdf(bytes: Uint8Array<ArrayBuffer>, options?: DocumentToPdfOptions): Uint8Array<ArrayBuffer>;
|
|
91
91
|
declare function pptToPdf(bytes: Uint8Array<ArrayBuffer>, options?: DocumentToPdfOptions): Uint8Array<ArrayBuffer>;
|
|
92
|
-
declare function xlsxToMarkdown(bytes: Uint8Array<ArrayBuffer>, options?:
|
|
92
|
+
declare function xlsxToMarkdown(bytes: Uint8Array<ArrayBuffer>, options?: DocumentBridgeOptions): Uint8Array<ArrayBuffer>;
|
|
93
|
+
declare function csvToMarkdown(bytes: Uint8Array<ArrayBuffer>, options?: DocumentBridgeOptions & CsvReadOptions): Uint8Array<ArrayBuffer>;
|
|
93
94
|
declare function markdownToXlsx(bytes: Uint8Array<ArrayBuffer>, options?: ComposedDocumentOptions): Uint8Array<ArrayBuffer>;
|
|
94
|
-
declare function csvToMarkdown(bytes: Uint8Array<ArrayBuffer>, options?: ComposedDocumentOptions & CsvReadOptions): Uint8Array<ArrayBuffer>;
|
|
95
95
|
declare function markdownToCsv(bytes: Uint8Array<ArrayBuffer>, options?: ComposedDocumentOptions & CsvWriteOptions): Uint8Array<ArrayBuffer>;
|
|
96
96
|
interface OdmToPdfOptions extends DocumentToPdfOptions {
|
|
97
97
|
readonly resolveSubDocument?: (href: string) => Uint8Array<ArrayBuffer> | undefined;
|
|
@@ -89,9 +89,9 @@ declare function rtfToPdf(bytes: Uint8Array<ArrayBuffer>, options?: DocumentToPd
|
|
|
89
89
|
declare function docToPdf(bytes: Uint8Array<ArrayBuffer>, options?: DocumentToPdfOptions): Uint8Array<ArrayBuffer>;
|
|
90
90
|
declare function xlsToPdf(bytes: Uint8Array<ArrayBuffer>, options?: DocumentToPdfOptions): Uint8Array<ArrayBuffer>;
|
|
91
91
|
declare function pptToPdf(bytes: Uint8Array<ArrayBuffer>, options?: DocumentToPdfOptions): Uint8Array<ArrayBuffer>;
|
|
92
|
-
declare function xlsxToMarkdown(bytes: Uint8Array<ArrayBuffer>, options?:
|
|
92
|
+
declare function xlsxToMarkdown(bytes: Uint8Array<ArrayBuffer>, options?: DocumentBridgeOptions): Uint8Array<ArrayBuffer>;
|
|
93
|
+
declare function csvToMarkdown(bytes: Uint8Array<ArrayBuffer>, options?: DocumentBridgeOptions & CsvReadOptions): Uint8Array<ArrayBuffer>;
|
|
93
94
|
declare function markdownToXlsx(bytes: Uint8Array<ArrayBuffer>, options?: ComposedDocumentOptions): Uint8Array<ArrayBuffer>;
|
|
94
|
-
declare function csvToMarkdown(bytes: Uint8Array<ArrayBuffer>, options?: ComposedDocumentOptions & CsvReadOptions): Uint8Array<ArrayBuffer>;
|
|
95
95
|
declare function markdownToCsv(bytes: Uint8Array<ArrayBuffer>, options?: ComposedDocumentOptions & CsvWriteOptions): Uint8Array<ArrayBuffer>;
|
|
96
96
|
interface OdmToPdfOptions extends DocumentToPdfOptions {
|
|
97
97
|
readonly resolveSubDocument?: (href: string) => Uint8Array<ArrayBuffer> | undefined;
|
package/dist/convert/convert.js
CHANGED
|
@@ -172,12 +172,12 @@ function pptToPdf(bytes, options) {
|
|
|
172
172
|
function xlsxToMarkdown(bytes, options) {
|
|
173
173
|
return convertDocument("xlsx", "markdown", bytes, options);
|
|
174
174
|
}
|
|
175
|
-
function markdownToXlsx(bytes, options) {
|
|
176
|
-
return convertDocument("markdown", "xlsx", bytes, options);
|
|
177
|
-
}
|
|
178
175
|
function csvToMarkdown(bytes, options) {
|
|
179
176
|
return convertDocument("csv", "markdown", bytes, options);
|
|
180
177
|
}
|
|
178
|
+
function markdownToXlsx(bytes, options) {
|
|
179
|
+
return convertDocument("markdown", "xlsx", bytes, options);
|
|
180
|
+
}
|
|
181
181
|
function markdownToCsv(bytes, options) {
|
|
182
182
|
return convertDocument("markdown", "csv", bytes, options);
|
|
183
183
|
}
|
|
@@ -91,8 +91,75 @@ function presentationToDrawing(doc) {
|
|
|
91
91
|
pages
|
|
92
92
|
};
|
|
93
93
|
}
|
|
94
|
+
function spreadsheetCellToTableCell(cell) {
|
|
95
|
+
if (cell === void 0) return { blocks: [{
|
|
96
|
+
kind: "paragraph",
|
|
97
|
+
runs: [{ text: "" }]
|
|
98
|
+
}] };
|
|
99
|
+
return { blocks: [{
|
|
100
|
+
kind: "paragraph",
|
|
101
|
+
runs: cell.runs ?? [{ text: cell.displayText }],
|
|
102
|
+
alignment: cell.alignment
|
|
103
|
+
}] };
|
|
104
|
+
}
|
|
105
|
+
function sheetToTableBlock(sheet) {
|
|
106
|
+
const hiddenRows = new Set(sheet.rows.filter((row) => row.hidden === true).map((row) => row.index));
|
|
107
|
+
const hiddenColumns = new Set(sheet.columns.filter((column) => column.hidden === true).map((column) => column.index));
|
|
108
|
+
const cellByPosition = /* @__PURE__ */ new Map();
|
|
109
|
+
let maxRow = -1;
|
|
110
|
+
let maxColumn = -1;
|
|
111
|
+
for (const cell of sheet.cells) {
|
|
112
|
+
if (hiddenRows.has(cell.row) || hiddenColumns.has(cell.column)) continue;
|
|
113
|
+
cellByPosition.set(`${String(cell.row)}:${String(cell.column)}`, cell);
|
|
114
|
+
maxRow = Math.max(maxRow, cell.row);
|
|
115
|
+
maxColumn = Math.max(maxColumn, cell.column);
|
|
116
|
+
}
|
|
117
|
+
if (cellByPosition.size === 0) return;
|
|
118
|
+
const visibleRows = [];
|
|
119
|
+
for (let row = 0; row <= maxRow; row += 1) if (!hiddenRows.has(row)) visibleRows.push(row);
|
|
120
|
+
const visibleColumns = [];
|
|
121
|
+
for (let column = 0; column <= maxColumn; column += 1) if (!hiddenColumns.has(column)) visibleColumns.push(column);
|
|
122
|
+
const columnWidthByIndex = new Map(sheet.columns.map((column) => [column.index, column.widthPt]));
|
|
123
|
+
const columnWidthsPt = visibleColumns.map((column) => columnWidthByIndex.get(column) ?? 72);
|
|
124
|
+
return {
|
|
125
|
+
kind: "table",
|
|
126
|
+
rows: visibleRows.map((row) => ({ cells: visibleColumns.map((column) => spreadsheetCellToTableCell(cellByPosition.get(`${String(row)}:${String(column)}`))) })),
|
|
127
|
+
columnWidthsPt
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function spreadsheetToWordprocessing(doc) {
|
|
131
|
+
const blocks = [];
|
|
132
|
+
for (const sheet of doc.sheets) {
|
|
133
|
+
blocks.push({
|
|
134
|
+
kind: "paragraph",
|
|
135
|
+
headingLevel: 2,
|
|
136
|
+
runs: [{ text: sheet.name }]
|
|
137
|
+
});
|
|
138
|
+
const table = sheetToTableBlock(sheet);
|
|
139
|
+
blocks.push(table ?? {
|
|
140
|
+
kind: "paragraph",
|
|
141
|
+
runs: [{ text: "(empty sheet)" }]
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
const section = {
|
|
145
|
+
pageSize: document_schema_js.PAGE_SIZE_A4,
|
|
146
|
+
margins: {
|
|
147
|
+
topPt: 72,
|
|
148
|
+
rightPt: 72,
|
|
149
|
+
bottomPt: 72,
|
|
150
|
+
leftPt: 72
|
|
151
|
+
},
|
|
152
|
+
blocks
|
|
153
|
+
};
|
|
154
|
+
return {
|
|
155
|
+
kind: "wordprocessing",
|
|
156
|
+
metadata: doc.metadata,
|
|
157
|
+
sections: [section]
|
|
158
|
+
};
|
|
159
|
+
}
|
|
94
160
|
//#endregion
|
|
95
161
|
exports.drawingToPresentation = drawingToPresentation;
|
|
96
162
|
exports.presentationToDrawing = presentationToDrawing;
|
|
97
163
|
exports.presentationToWordprocessing = presentationToWordprocessing;
|
|
164
|
+
exports.spreadsheetToWordprocessing = spreadsheetToWordprocessing;
|
|
98
165
|
exports.wordprocessingToPresentation = wordprocessingToPresentation;
|
|
@@ -9,9 +9,13 @@ type PresentationContentDocument = Extract<ContentDocument, {
|
|
|
9
9
|
type DrawingContentDocument = Extract<ContentDocument, {
|
|
10
10
|
kind: "drawing";
|
|
11
11
|
}>;
|
|
12
|
+
type SpreadsheetContentDocument = Extract<ContentDocument, {
|
|
13
|
+
kind: "spreadsheet";
|
|
14
|
+
}>;
|
|
12
15
|
declare function wordprocessingToPresentation(doc: WordprocessingContentDocument): PresentationContentDocument;
|
|
13
16
|
declare function presentationToWordprocessing(doc: PresentationContentDocument): WordprocessingContentDocument;
|
|
14
17
|
declare function drawingToPresentation(doc: DrawingContentDocument): PresentationContentDocument;
|
|
15
18
|
declare function presentationToDrawing(doc: PresentationContentDocument): DrawingContentDocument;
|
|
19
|
+
declare function spreadsheetToWordprocessing(doc: SpreadsheetContentDocument): WordprocessingContentDocument;
|
|
16
20
|
//#endregion
|
|
17
|
-
export { drawingToPresentation, presentationToDrawing, presentationToWordprocessing, wordprocessingToPresentation };
|
|
21
|
+
export { drawingToPresentation, presentationToDrawing, presentationToWordprocessing, spreadsheetToWordprocessing, wordprocessingToPresentation };
|
|
@@ -9,9 +9,13 @@ type PresentationContentDocument = Extract<ContentDocument, {
|
|
|
9
9
|
type DrawingContentDocument = Extract<ContentDocument, {
|
|
10
10
|
kind: "drawing";
|
|
11
11
|
}>;
|
|
12
|
+
type SpreadsheetContentDocument = Extract<ContentDocument, {
|
|
13
|
+
kind: "spreadsheet";
|
|
14
|
+
}>;
|
|
12
15
|
declare function wordprocessingToPresentation(doc: WordprocessingContentDocument): PresentationContentDocument;
|
|
13
16
|
declare function presentationToWordprocessing(doc: PresentationContentDocument): WordprocessingContentDocument;
|
|
14
17
|
declare function drawingToPresentation(doc: DrawingContentDocument): PresentationContentDocument;
|
|
15
18
|
declare function presentationToDrawing(doc: PresentationContentDocument): DrawingContentDocument;
|
|
19
|
+
declare function spreadsheetToWordprocessing(doc: SpreadsheetContentDocument): WordprocessingContentDocument;
|
|
16
20
|
//#endregion
|
|
17
|
-
export { drawingToPresentation, presentationToDrawing, presentationToWordprocessing, wordprocessingToPresentation };
|
|
21
|
+
export { drawingToPresentation, presentationToDrawing, presentationToWordprocessing, spreadsheetToWordprocessing, wordprocessingToPresentation };
|
|
@@ -90,5 +90,71 @@ function presentationToDrawing(doc) {
|
|
|
90
90
|
pages
|
|
91
91
|
};
|
|
92
92
|
}
|
|
93
|
+
function spreadsheetCellToTableCell(cell) {
|
|
94
|
+
if (cell === void 0) return { blocks: [{
|
|
95
|
+
kind: "paragraph",
|
|
96
|
+
runs: [{ text: "" }]
|
|
97
|
+
}] };
|
|
98
|
+
return { blocks: [{
|
|
99
|
+
kind: "paragraph",
|
|
100
|
+
runs: cell.runs ?? [{ text: cell.displayText }],
|
|
101
|
+
alignment: cell.alignment
|
|
102
|
+
}] };
|
|
103
|
+
}
|
|
104
|
+
function sheetToTableBlock(sheet) {
|
|
105
|
+
const hiddenRows = new Set(sheet.rows.filter((row) => row.hidden === true).map((row) => row.index));
|
|
106
|
+
const hiddenColumns = new Set(sheet.columns.filter((column) => column.hidden === true).map((column) => column.index));
|
|
107
|
+
const cellByPosition = /* @__PURE__ */ new Map();
|
|
108
|
+
let maxRow = -1;
|
|
109
|
+
let maxColumn = -1;
|
|
110
|
+
for (const cell of sheet.cells) {
|
|
111
|
+
if (hiddenRows.has(cell.row) || hiddenColumns.has(cell.column)) continue;
|
|
112
|
+
cellByPosition.set(`${String(cell.row)}:${String(cell.column)}`, cell);
|
|
113
|
+
maxRow = Math.max(maxRow, cell.row);
|
|
114
|
+
maxColumn = Math.max(maxColumn, cell.column);
|
|
115
|
+
}
|
|
116
|
+
if (cellByPosition.size === 0) return;
|
|
117
|
+
const visibleRows = [];
|
|
118
|
+
for (let row = 0; row <= maxRow; row += 1) if (!hiddenRows.has(row)) visibleRows.push(row);
|
|
119
|
+
const visibleColumns = [];
|
|
120
|
+
for (let column = 0; column <= maxColumn; column += 1) if (!hiddenColumns.has(column)) visibleColumns.push(column);
|
|
121
|
+
const columnWidthByIndex = new Map(sheet.columns.map((column) => [column.index, column.widthPt]));
|
|
122
|
+
const columnWidthsPt = visibleColumns.map((column) => columnWidthByIndex.get(column) ?? 72);
|
|
123
|
+
return {
|
|
124
|
+
kind: "table",
|
|
125
|
+
rows: visibleRows.map((row) => ({ cells: visibleColumns.map((column) => spreadsheetCellToTableCell(cellByPosition.get(`${String(row)}:${String(column)}`))) })),
|
|
126
|
+
columnWidthsPt
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function spreadsheetToWordprocessing(doc) {
|
|
130
|
+
const blocks = [];
|
|
131
|
+
for (const sheet of doc.sheets) {
|
|
132
|
+
blocks.push({
|
|
133
|
+
kind: "paragraph",
|
|
134
|
+
headingLevel: 2,
|
|
135
|
+
runs: [{ text: sheet.name }]
|
|
136
|
+
});
|
|
137
|
+
const table = sheetToTableBlock(sheet);
|
|
138
|
+
blocks.push(table ?? {
|
|
139
|
+
kind: "paragraph",
|
|
140
|
+
runs: [{ text: "(empty sheet)" }]
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
const section = {
|
|
144
|
+
pageSize: PAGE_SIZE_A4,
|
|
145
|
+
margins: {
|
|
146
|
+
topPt: 72,
|
|
147
|
+
rightPt: 72,
|
|
148
|
+
bottomPt: 72,
|
|
149
|
+
leftPt: 72
|
|
150
|
+
},
|
|
151
|
+
blocks
|
|
152
|
+
};
|
|
153
|
+
return {
|
|
154
|
+
kind: "wordprocessing",
|
|
155
|
+
metadata: doc.metadata,
|
|
156
|
+
sections: [section]
|
|
157
|
+
};
|
|
158
|
+
}
|
|
93
159
|
//#endregion
|
|
94
|
-
export { drawingToPresentation, presentationToDrawing, presentationToWordprocessing, wordprocessingToPresentation };
|
|
160
|
+
export { drawingToPresentation, presentationToDrawing, presentationToWordprocessing, spreadsheetToWordprocessing, wordprocessingToPresentation };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "js.documents",
|
|
3
|
-
"version": "7.1.
|
|
3
|
+
"version": "7.1.8",
|
|
4
4
|
"description": "Bidirectional docx/pptx <-> PDF conversion and a read+write editable OOXML document model, built on ooxml.js and Zod 4 codecs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -77,20 +77,20 @@
|
|
|
77
77
|
],
|
|
78
78
|
"license": "MIT",
|
|
79
79
|
"dependencies": {
|
|
80
|
-
"archive-codec": "^1.6.
|
|
80
|
+
"archive-codec": "^1.6.4",
|
|
81
81
|
"byte-codec": "^1.4.0",
|
|
82
|
-
"doc-codec": "^2.2.
|
|
83
|
-
"document-schema.js": "^6.2.
|
|
82
|
+
"doc-codec": "^2.2.4",
|
|
83
|
+
"document-schema.js": "^6.2.3",
|
|
84
84
|
"fflate": "^0.8.3",
|
|
85
|
-
"markdown-codec": "^6.3.
|
|
86
|
-
"odf.js": "^7.1.
|
|
87
|
-
"ooxml.js": "^7.1.
|
|
88
|
-
"pdf-codec": "^4.0.
|
|
89
|
-
"ppt-codec": "^1.2.
|
|
90
|
-
"rtf-codec": "^3.0.
|
|
85
|
+
"markdown-codec": "^6.3.4",
|
|
86
|
+
"odf.js": "^7.1.5",
|
|
87
|
+
"ooxml.js": "^7.1.6",
|
|
88
|
+
"pdf-codec": "^4.0.4",
|
|
89
|
+
"ppt-codec": "^1.2.5",
|
|
90
|
+
"rtf-codec": "^3.0.4",
|
|
91
91
|
"temml": "0.13.4",
|
|
92
|
-
"wpd-codec": "^2.1.
|
|
93
|
-
"xls-codec": "^4.1.
|
|
92
|
+
"wpd-codec": "^2.1.5",
|
|
93
|
+
"xls-codec": "^4.1.5",
|
|
94
94
|
"zod": "^4.4.3"
|
|
95
95
|
},
|
|
96
96
|
"devDependencies": {
|