Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 23 additions & 22 deletions packages/documents.js/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -883,24 +883,25 @@ To run a single test file: `pnpm vitest run src/path/to/file.test.ts`.

## Fidelity

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 — bidirectionally routable to every other format here except `csv`/`xlsx` (one hop past the pathfinder's own 3-hop cap) and `odf` (which has almost no conversions with anything) — but is not yet reflected in the table below: extending it surfaced pre-existing drift between a few of the table's own cells and what the pathfinder actually routes, so both need a real per-pair audit rather than another prose edit — tracked in [ExaDev/documents.js#853](https://github.com/ExaDev/documents.js/issues/853).

| ↓ from \ to → | docx | pptx | xlsx | odt | odp | ods | odg | svg | odf | markdown | csv | pdf |
| ------------- | ---- | ---- | ---- | --- | --- | --- | --- | --- | --- | -------- | --- | --- |
| **docx** | — | ~ | – | ✓ | – | – | – | ✗ | – | ✗ | ✗ | ~ |
| **pptx** | ~ | — | – | – | ✓ | – | – | ✗ | – | – | ✗ | ~ |
| **xlsx** | – | – | — | – | – | ~ | – | ✗ | – | ✗✗ | ~ | ~ |
| **odt** | ✓ | – | – | — | ~ | – | – | ✗ | – | ✗ | ✗ | ~ |
| **odp** | – | ✓ | – | ~ | — | – | – | ✗ | – | – | ✗ | ~ |
| **ods** | – | – | ~ | – | – | — | – | ✗ | – | – | ~ | ~ |
| **odg** | – | – | – | – | – | – | — | ✓ | – | – | ✗ | ~ |
| **svg** | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | — | – | ✗✗ | ✗✗ | ~ |
| **odf** | – | – | – | – | – | – | – | – | — | – | – | → |
| **markdown** | ~ | – | ✗✗ | ~ | – | – | – | ✗✗ | – | — | ✗✗ | ~ |
| **csv** | ✗ | ✗ | ✓ | ✗ | ✗ | ✓ | ✗ | ✗ | – | ✗✗ | — | ~ |
| **pdf** | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | – | ✗✗ | ✗ | — |

111 of 132 directional pairs are routable. The shared `ContentDocument` model is the hub, not PDF — twenty bridges bypass PDF entirely.
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). 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`).

| ↓ from \ to → | docx | pptx | xlsx | odt | odp | ods | odg | svg | odf | markdown | csv | rtf | pdf |
| ------------- | ---- | ---- | ---- | --- | --- | --- | --- | --- | --- | -------- | --- | --- | --- |
| **docx** | — | ~ | ✗ | ✓ | ~ | ✗ | ✗ | ✗ | – | ✗ | ✗ | ✗ | ~ |
| **pptx** | ~ | — | ✗ | ~ | ✓ | ✗ | ~ | ~ | – | ~ | ✗ | ~ | ~ |
| **xlsx** | ✗ | ✗ | — | ✗ | ✗ | ~ | ✗ | ✗ | – | ✗✗ | ~ | – | ~ |
| **odt** | ✓ | ~ | ✗ | — | ~ | ✗ | ✗ | ✗ | – | ✗ | ✗ | ✗ | ~ |
| **odp** | ~ | ✓ | ✗ | ~ | — | ✗ | ~ | ~ | – | ~ | ✗ | ~ | ~ |
| **ods** | ✗ | ✗ | ~ | ✗ | ✗ | — | ✗ | ✗ | – | ✗✗ | ~ | ✗ | ~ |
| **odg** | ✗ | ~ | ✗ | ✗ | ~ | ✗ | — | ✓ | – | ✗ | ✗ | ✗ | ~ |
| **svg** | ✗ | ~ | ✗ | ✗ | ~ | ✗ | ✓ | — | – | ✗✗ | ✗✗ | ✗ | ~ |
| **odf** | – | – | – | – | – | – | – | – | — | – | – | – | → |
| **markdown** | ~ | ~ | ✗✗ | ~ | ~ | ✗ | ✗ | ✗✗ | – | — | ✗✗ | ~ | ~ |
| **csv** | ✗ | ✗ | ✓ | ✗ | ✗ | ✓ | ✗ | ✗ | – | ✗✗ | — | – | ~ |
| **rtf** | ~ | ~ | – | ~ | ~ | ✗ | ✗ | ✗ | – | ✗ | – | — | ~ |
| **pdf** | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | – | ✗✗ | ✗ | ✗ | — |

129 of 156 directional pairs are routable. The shared `ContentDocument` model is the hub, not PDF — twenty bridges bypass PDF entirely.

**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.

Expand All @@ -914,13 +915,13 @@ Read as **row → column**. `✓` lossless, `~` bounded, `✗` lossy, `✗✗` s

**PDF → ods** recovers what was printed, not what was entered. The printed string always survives in `displayText`; re-typed `value` is explicitly probabilistic inference.

**`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. The PDF-composed markdown bridges (`xlsxToMarkdown`/`markdownToXlsx`, `csvToMarkdown`/`markdownToCsv`) stack the same two losses in both directions — hence their `✗✗` cells.
**`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. The PDF-composed markdown bridges (`xlsxToMarkdown`/`markdownToXlsx`, `csvToMarkdown`/`markdownToCsv`) stack the same two losses in both directions — hence their `✗✗` cells. `ods ⇄ markdown` stacks the identical pair of losses even without a named bridge function: the pathfinder's own bare toPdf-then-fromPdf route carries no extra hop, but the two legs are the same render-then-reconstruct-into-markdown pair, so both directions are `✗✗` too.

**The six same-variant bridge pairs** (odt⇄docx, odp⇄pptx, ods⇄xlsx, csv⇄ods, csv⇄xlsx, svg⇄odg) bypass PDF entirely — no layout engine, no reconstruction. Text, styling, tables, lists, rotated shapes survive completely. `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 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.
**The same-variant bridge pairs** (odt⇄docx, odp⇄pptx, ods⇄xlsx, csv⇄ods, csv⇄xlsx, 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 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, and alignment, so nothing docx/odt already lost crossing to rtf and back is lost a second time going rtf → docx/odt — but going docx/odt → rtf drops superscript/subscript, cell borders/shading/vertical merge, right-to-left text, and multi-section documents collapse to one section, all real content rtf-codec's "Deliberately not handled" table names explicitly.

**The two markdown bridge pairs** bypass PDF too, but markdown's grammar has no construct for colour/font/size/alignment — `docxToMarkdown`/`odtToMarkdown` drop them (format-boundary loss, not approximation).
**The markdown and rtf bridge pairs into/out of markdown** bypass PDF too, but markdown's grammar has no construct for colour/font/size/alignment — `docxToMarkdown`/`odtToMarkdown` and rtf → markdown all drop them (format-boundary loss, not approximation). rtf carries all four natively (unlike markdown), so `markdown → rtf` loses nothing markdown had, the same asymmetry `markdown → docx`/`markdown → odt` already show.

**Four cross-variant bridges** (docx⇄pptx, odt⇄odp) go through a semantic transform — slide boundaries are heuristic, but blocks survive intact.
**Every wordprocessing-family format (docx/odt/markdown/rtf) bridges to every presentation-family format (pptx/odp), and every drawing-family format (odg/svg) bridges to every presentation-family format too, through a semantic transform** — not just the two hand-written convenience pairs (docx⇄pptx, odt⇄odp): the pathfinder generates the full cross-product from the same two transform functions (`wordprocessingToPresentation`/`presentationToWordprocessing`, `drawingToPresentation`/`presentationToDrawing`), so e.g. `odp → docx` and `odt → pptx` are real routable pairs with no named function of their own. Slide boundaries are heuristic, but blocks survive intact. A wordprocessing format reaching a drawing format (or vice versa) chains two such transforms through presentation as a hub (e.g. `docx → pptx → odg`) with no PDF pivot at all — cheaper than a PDF round trip, but compounding two structural transforms is more lossy than one, hence those cells' `✗` rather than `~`.

**`.odb` extraction** is genuine verified data extraction across all four tiers, differing by what each storage shape carries. BLOB content recovers byte-for-byte. No reverse direction.

Expand Down
Loading