diff --git a/packages/documents.js/README.md b/packages/documents.js/README.md index d52273ec..b7d809eb 100644 --- a/packages/documents.js/README.md +++ b/packages/documents.js/README.md @@ -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. @@ -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 `` 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 `` 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.