js.documents 2.1.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/README.md +72 -33
  2. package/dist/codecs/registry.cjs +7 -0
  3. package/dist/codecs/registry.js +7 -0
  4. package/dist/convert/capability.cjs +5 -0
  5. package/dist/convert/capability.js +5 -0
  6. package/dist/convert/codec.cjs +10 -0
  7. package/dist/convert/codec.d.cts +3 -1
  8. package/dist/convert/codec.d.ts +3 -1
  9. package/dist/convert/codec.js +11 -3
  10. package/dist/convert/composition.cjs +17 -0
  11. package/dist/convert/composition.d.cts +5 -2
  12. package/dist/convert/composition.d.ts +5 -2
  13. package/dist/convert/composition.js +17 -0
  14. package/dist/convert/convert.cjs +16 -0
  15. package/dist/convert/convert.d.cts +13 -1
  16. package/dist/convert/convert.d.ts +13 -1
  17. package/dist/convert/convert.js +13 -1
  18. package/dist/convert/local.cjs +2 -1
  19. package/dist/convert/local.js +2 -1
  20. package/dist/convert/port.cjs +1 -0
  21. package/dist/convert/port.d.cts +2 -0
  22. package/dist/convert/port.d.ts +2 -0
  23. package/dist/convert/port.js +1 -0
  24. package/dist/diagnostics-C5-bG09J.d.cts +18 -0
  25. package/dist/diagnostics-C5-bG09J.d.ts +18 -0
  26. package/dist/edit/odg/scaffold.js +8 -8
  27. package/dist/edit/odp/scaffold.js +8 -8
  28. package/dist/edit/ods/scaffold.js +8 -8
  29. package/dist/edit/odt/scaffold.js +8 -8
  30. package/dist/index.cjs +31 -0
  31. package/dist/index.d.cts +12 -4
  32. package/dist/index.d.ts +12 -4
  33. package/dist/index.js +12 -4
  34. package/dist/latex/diagnostics.cjs +21 -0
  35. package/dist/latex/diagnostics.d.cts +2 -0
  36. package/dist/latex/diagnostics.d.ts +2 -0
  37. package/dist/latex/diagnostics.js +19 -0
  38. package/dist/latex/lint.cjs +82 -0
  39. package/dist/latex/lint.d.cts +6 -0
  40. package/dist/latex/lint.d.ts +6 -0
  41. package/dist/latex/lint.js +81 -0
  42. package/dist/latex/lower.cjs +550 -0
  43. package/dist/latex/lower.d.cts +24 -0
  44. package/dist/latex/lower.d.ts +24 -0
  45. package/dist/latex/lower.js +548 -0
  46. package/dist/latex/rational.cjs +29 -0
  47. package/dist/latex/rational.d.cts +11 -0
  48. package/dist/latex/rational.d.ts +11 -0
  49. package/dist/latex/rational.js +27 -0
  50. package/dist/latex/symbols.cjs +128 -0
  51. package/dist/latex/symbols.d.cts +16 -0
  52. package/dist/latex/symbols.d.ts +16 -0
  53. package/dist/latex/symbols.js +124 -0
  54. package/dist/latex/temml.cjs +121 -0
  55. package/dist/latex/temml.d.cts +25 -0
  56. package/dist/latex/temml.d.ts +25 -0
  57. package/dist/latex/temml.js +93 -0
  58. package/dist/markdown/math.cjs +110 -0
  59. package/dist/markdown/math.d.cts +9 -0
  60. package/dist/markdown/math.d.ts +9 -0
  61. package/dist/markdown/math.js +109 -0
  62. package/dist/markdown/read.cjs +3 -2
  63. package/dist/markdown/read.d.cts +2 -1
  64. package/dist/markdown/read.d.ts +2 -1
  65. package/dist/markdown/read.js +3 -2
  66. package/dist/markdown/write.cjs +23 -4
  67. package/dist/markdown/write.js +23 -4
  68. package/dist/metadata/write.cjs +1 -0
  69. package/dist/metadata/write.js +1 -0
  70. package/dist/model/bytes.cjs +5 -0
  71. package/dist/model/bytes.d.cts +2 -1
  72. package/dist/model/bytes.d.ts +2 -1
  73. package/dist/model/bytes.js +5 -1
  74. package/dist/model/formula.cjs +1 -1
  75. package/dist/model/formula.js +1 -1
  76. package/dist/svg/diagnostics.cjs +19 -0
  77. package/dist/svg/diagnostics.d.cts +10 -0
  78. package/dist/svg/diagnostics.d.ts +10 -0
  79. package/dist/svg/diagnostics.js +18 -0
  80. package/dist/svg/paint.cjs +817 -0
  81. package/dist/svg/paint.d.cts +19 -0
  82. package/dist/svg/paint.d.ts +19 -0
  83. package/dist/svg/paint.js +814 -0
  84. package/dist/svg/path.cjs +337 -0
  85. package/dist/svg/path.d.cts +22 -0
  86. package/dist/svg/path.d.ts +22 -0
  87. package/dist/svg/path.js +336 -0
  88. package/dist/svg/read.cjs +677 -0
  89. package/dist/svg/read.d.cts +12 -0
  90. package/dist/svg/read.d.ts +12 -0
  91. package/dist/svg/read.js +675 -0
  92. package/dist/svg/text.cjs +22 -0
  93. package/dist/svg/text.d.cts +8 -0
  94. package/dist/svg/text.d.ts +8 -0
  95. package/dist/svg/text.js +19 -0
  96. package/dist/svg/transform.cjs +206 -0
  97. package/dist/svg/transform.d.cts +23 -0
  98. package/dist/svg/transform.d.ts +23 -0
  99. package/dist/svg/transform.js +197 -0
  100. package/dist/svg/units.cjs +47 -0
  101. package/dist/svg/units.d.cts +12 -0
  102. package/dist/svg/units.d.ts +12 -0
  103. package/dist/svg/units.js +44 -0
  104. package/dist/svg/write.cjs +127 -0
  105. package/dist/svg/write.d.cts +23 -0
  106. package/dist/svg/write.d.ts +23 -0
  107. package/dist/svg/write.js +123 -0
  108. package/dist/xml/odf-text.js +2 -2
  109. package/package.json +2 -1
package/README.md CHANGED
@@ -2,9 +2,9 @@
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), 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 91 (source, target) pairs across the nine content formats and PDF, including eighteen PDF-pivot round trips (the seven layout-engine formats, plus xlsx and csv composing through ods), twenty-two 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, 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 `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).
6
6
 
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.
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
 
9
9
  ```mermaid
10
10
  graph TD
@@ -72,7 +72,7 @@ npm install documents.js
72
72
 
73
73
  ### The generic entry point: `convertDocument`
74
74
 
75
- A single function, `convertDocument`, sits behind every named conversion and reaches every pair the composition engine can route — all 91 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).
75
+ A single function, `convertDocument`, sits behind every named conversion and reaches every pair the composition engine can route — all 111 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).
76
76
 
77
77
  ```ts
78
78
  import { convertDocument } from 'documents.js';
@@ -89,10 +89,10 @@ const odtBytes = convertDocument('docx', 'odt', docxBytes, { onMathDiagnostic: (
89
89
 
90
90
  ### PDF-pivot conversions
91
91
 
92
- The fourteen round-trip ergonomic conversions between the formats with their own layout engine and PDF (docx/pptx/odt/odp/ods/odg/markdown ⇄ PDF, all round-tripping both ways), plus `xlsxToPdf`/`pdfToXlsx` and `csvToPdf`/`pdfToCsv` (each composing its ods bridge with the ods⇄pdf layout pair internally — neither xlsx nor csv has a layout engine of its own):
92
+ The sixteen round-trip ergonomic conversions between the formats with their own layout engine and PDF (docx/pptx/odt/odp/ods/odg/markdown/svg ⇄ PDF, all round-tripping both ways), plus `xlsxToPdf`/`pdfToXlsx` and `csvToPdf`/`pdfToCsv` (each composing its ods bridge with the ods⇄pdf layout pair internally — neither xlsx nor csv has a layout engine of its own):
93
93
 
94
94
  ```ts
95
- import { csvToPdf, docxToPdf, markdownToPdf, odgToPdf, odpToPdf, odsToPdf, odtToPdf, pdfToCsv, pdfToDocx, pdfToMarkdown, pdfToOdg, pdfToOdp, pdfToOds, pdfToOdt, pdfToPptx, pdfToXlsx, pptxToPdf, xlsxToPdf } from 'documents.js';
95
+ import { csvToPdf, docxToPdf, markdownToPdf, odgToPdf, odpToPdf, odsToPdf, odtToPdf, pdfToCsv, pdfToDocx, pdfToMarkdown, pdfToOdg, pdfToOdp, pdfToOds, pdfToOdt, pdfToPptx, pdfToSvg, pdfToXlsx, pptxToPdf, svgToPdf, xlsxToPdf } from 'documents.js';
96
96
 
97
97
  const pdfBytes = docxToPdf(docxBytes);
98
98
  const docxBytes2 = pdfToDocx(pdfBytes);
@@ -120,13 +120,16 @@ const markdownBytes2 = pdfToMarkdown(pdfFromMarkdown); // the lossiest conversio
120
120
 
121
121
  const pdfFromCsv = csvToPdf(csvBytes); // composes csvToOds -> odsToPdf internally
122
122
  const csvBytes2 = pdfToCsv(pdfFromCsv); // composes pdfToOds -> odsToCsv internally; recovers what was printed, then heuristically re-types it
123
+
124
+ const pdfFromSvg = svgToPdf(svgBytes); // reads the six shape primitives into a drawing ContentDocument, then the same drawing layout engine odgToPdf feeds renders it
125
+ const svgBytes2 = pdfToSvg(pdfFromSvg); // readPdf -> reconstructDrawing -> buildSvgText: vector geometry recovers near-1:1, while recovered text boxes sit outside the svg writer's vector-only scope (reported per shape, never silently dropped)
123
126
  ```
124
127
 
125
128
  Each accepts an optional `signal` (`AbortSignal`) and either `onSubstitution` (X → PDF, called per character not representable in a standard-14 font) or `sink` (PDF → X, called per recoverable parse diagnostic). Every X → PDF conversion additionally accepts `fonts` (extra `ProvidedFont` faces) and `onFontSubstitution` (per family+weight+style that resolved to something else). Neither is needed for the common case — see [Fonts](#fonts).
126
129
 
127
130
  ### Cross-format bridges
128
131
 
129
- Twenty-two bridge functions across eleven pairs bypass the PDF pivot where a direct path exists. Seven same-variant direct-copy pairs (`odtToDocx`/`docxToOdt`, `odpToPptx`/`pptxToOdp`, `odsToXlsx`/`xlsxToOds`, `csvToOds`/`odsToCsv`, `csvToXlsx`/`xlsxToCsv`, `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. Two cross-variant semantic-transform pairs (`docxToPptx`/`pptxToDocx`, `odtToOdp`/`odpToOdt`) go through `src/convert/variant-bridges.ts`. Two PDF-composed pairs (`xlsxToMarkdown`/`markdownToXlsx`, `csvToMarkdown`/`markdownToCsv`) route through PDF internally — the lossiest conversions in the package.
132
+ Twenty-four bridge functions across twelve pairs 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`. Two PDF-composed pairs (`xlsxToMarkdown`/`markdownToXlsx`, `csvToMarkdown`/`markdownToCsv`) route through PDF internally — the lossiest conversions in the package.
130
133
 
131
134
  ```ts
132
135
  import { odtToDocx, docxToOdt, markdownToDocx, docxToMarkdown } from 'documents.js';
@@ -138,7 +141,7 @@ const docxFromMarkdown = markdownToDocx(markdownBytes);
138
141
  const markdownBytes3 = docxToMarkdown(docxFromMarkdown); // colour, font family/size, and explicit alignment have no markdown source construct -- dropped on this hop
139
142
  ```
140
143
 
141
- Each takes an optional `{ signal }` — no `onSubstitution`/`sink`, since there is no font substitution or PDF-parse degradation. `odtToDocx`/`markdownToDocx`/`docxToOdt`/`docxToMarkdown` additionally take `onMathDiagnostic`, called per formula construct that degraded crossing the bridge. The csv-sourced bridges (`csvToOds`, `csvToXlsx`, `csvToMarkdown`, `csvToPdf`) take `{ delimiter }` — `'\t'` parses the same format as TSV, since a delimiter is a parse option, not a different document format — and `onCellTypeInference`, the per-decision audit channel the read shares with `pdfToOds`. The csv-target bridges (`odsToCsv`, `xlsxToCsv`, `markdownToCsv`, `pdfToCsv`) take `{ delimiter, sheet }`: csv has no second sheet, so writing a multi-sheet source refuses with `CsvSheetNotSpecifiedError` naming every sheet until a caller selects one.
144
+ Each takes an optional `{ signal }` — no `onSubstitution`/`sink`, since there is no font substitution or PDF-parse degradation. `odtToDocx`/`markdownToDocx`/`docxToOdt`/`docxToMarkdown` additionally take `onMathDiagnostic`, called per formula construct that degraded crossing the bridge. The csv-sourced bridges (`csvToOds`, `csvToXlsx`, `csvToMarkdown`, `csvToPdf`) take `{ delimiter }` — `'\t'` parses the same format as TSV, since a delimiter is a parse option, not a different document format — and `onCellTypeInference`, the per-decision audit channel the read shares with `pdfToOds`. The csv-target bridges (`odsToCsv`, `xlsxToCsv`, `markdownToCsv`, `pdfToCsv`) take `{ delimiter, sheet }`: csv has no second sheet, so writing a multi-sheet source refuses with `CsvSheetNotSpecifiedError` naming every sheet until a caller selects one. The svg-sourced bridges (`svgToOdg`, `svgToPdf`) take `onSvgDiagnostic`, the reader's per-scope-limit channel; the svg-target bridges (`odgToSvg`, `pdfToSvg`) take `{ page, onSvgDiagnostic }`: an svg is a single drawing, so writing a multi-page source refuses with `SvgMultiPageNotSpecifiedError` naming the page count until `{ page }` selects one (an index, because drawing pages are anonymous where sheets are named).
142
145
 
143
146
  ### The `DocumentConverter` port
144
147
 
@@ -154,12 +157,12 @@ const { document, diagnostics } = await converter.convert(
154
157
  );
155
158
  ```
156
159
 
157
- `DocumentFormat` includes `docx`/`pptx`/`xlsx`/`odt`/`odp`/`ods`/`odg`/`odf`/`csv`/`markdown`/`pdf` — eleven members. The port's `conversions` list is derived from `resolveCompositionPlan` plus the `odf`→`pdf` special case — 91 pairs total. `DocumentFormat` is inferred from `DocumentFormatSchema` (a real Zod schema); `DOCUMENT_FORMATS` is exported as a plain array derived from the same schema:
160
+ `DocumentFormat` includes `docx`/`pptx`/`xlsx`/`odt`/`odp`/`ods`/`odg`/`svg`/`odf`/`csv`/`markdown`/`pdf` — twelve members. The port's `conversions` list is derived from `resolveCompositionPlan` plus the `odf`→`pdf` special case — 111 pairs total. `DocumentFormat` is inferred from `DocumentFormatSchema` (a real Zod schema); `DOCUMENT_FORMATS` is exported as a plain array derived from the same schema:
158
161
 
159
162
  ```ts
160
163
  import { DOCUMENT_FORMATS, DocumentFormatSchema } from 'documents.js';
161
164
 
162
- console.log(DOCUMENT_FORMATS); // ['docx', 'pptx', 'xlsx', 'odt', 'odp', 'ods', 'odg', 'odf', 'csv', 'markdown', 'pdf']
165
+ console.log(DOCUMENT_FORMATS); // ['docx', 'pptx', 'xlsx', 'odt', 'odp', 'ods', 'odg', 'svg', 'odf', 'csv', 'markdown', 'pdf']
163
166
  DocumentFormatSchema.parse(userSuppliedFormat); // throws a ZodError for anything outside that list
164
167
  ```
165
168
 
@@ -205,7 +208,7 @@ const docxBytesAgain = buildDocumentBytes(captured, 'docx');
205
208
 
206
209
  ### Package decode/encode, metadata, and deep imports
207
210
 
208
- `decodeDocumentPackage`/`encodeDocumentPackage` dispatch docx/pptx/xlsx through `ooxml.js`'s OPC codec and odt/odp/ods/odg/odf through `odf.js`'s ODF codec, throwing `UnsupportedPackageFormatError` for `markdown`/`csv`/`pdf` (none of the three is a package — they are plain text and bytes respectively). `decodeOdbPackage` is the `.odb`-specific sibling (`.odb` is not a `DocumentFormat` member):
211
+ `decodeDocumentPackage`/`encodeDocumentPackage` dispatch docx/pptx/xlsx through `ooxml.js`'s OPC codec and odt/odp/ods/odg/odf through `odf.js`'s ODF codec, throwing `UnsupportedPackageFormatError` for `markdown`/`csv`/`svg`/`pdf` (none of the four is a package — the first three are plain text, pdf is bytes). `decodeOdbPackage` is the `.odb`-specific sibling (`.odb` is not a `DocumentFormat` member):
209
212
 
210
213
  ```ts
211
214
  import { decodeDocumentPackage, decodeOdbPackage, encodeDocumentPackage } from 'documents.js';
@@ -215,7 +218,7 @@ const docxBytesAgain = encodeDocumentPackage('docx', pkg);
215
218
  const odbPkg = decodeOdbPackage(odbBytes);
216
219
  ```
217
220
 
218
- `readDocumentMetadata`/`setDocumentMetadata` read or patch metadata across any `DocumentFormat`. `setDocumentMetadata` patches in place (source/target formats must match); `odf` is rejected in both directions, and `csv` is rejected in both directions too (RFC 4180 text has no metadata container) — `readDocumentMetadata('csv', ...)` answers an empty `LayoutMetadata` for the same reason. `readDocumentMetadata('xlsx', ...)` is a named exception: it renders via `xlsxToPdf` and reads the PDF's metadata, because a direct read and the PDF-preview path genuinely disagree on `createdIso`/`modifiedIso`/`producer`.
221
+ `readDocumentMetadata`/`setDocumentMetadata` read or patch metadata across any `DocumentFormat`. `setDocumentMetadata` patches in place (source/target formats must match); `odf` is rejected in both directions, and `csv` is rejected in both directions too (RFC 4180 text has no metadata container) — `readDocumentMetadata('csv', ...)` answers an empty `LayoutMetadata` for the same reason. `svg` reads its root `<title>` as `metadata.title` and is rejected as a `setDocumentMetadata` source/target for the mirror-image reason: `<title>` is svg's whole metadata surface, so any other override would be silently dropped by the rebuild. `readDocumentMetadata('xlsx', ...)` is a named exception: it renders via `xlsxToPdf` and reads the PDF's metadata, because a direct read and the PDF-preview path genuinely disagree on `createdIso`/`modifiedIso`/`producer`.
219
222
 
220
223
  ```ts
221
224
  import { readDocumentMetadata, setDocumentMetadata } from 'documents.js';
@@ -233,7 +236,7 @@ import { buildOdtPackage } from 'documents.js/edit/odt/content';
233
236
 
234
237
  ### Reading and building xlsx content directly
235
238
 
236
- Every other content format has its own standalone `readXContent`-shaped entry point (`readDocxContent`, `readPptxContent`, `readOdtContent`, `readOdpContent`, `readOdsContent`, `readOdgContent`) — xlsx is no longer the exception. `readXlsxContent`/`buildXlsxPackage` are `ooxml.js`'s own spreadsheet `ContentDocument` read/build pair — the same one the `ods⇄xlsx` bridge and every xlsx metadata-rebuild path already use internally — re-exported here directly rather than wrapped, since `readXlsxContent` already produces the right shape on its own. csv's `readCsvContent`/`buildCsvText` are the same kind of directly-exported stage pair, one level further in: they operate on RFC 4180 text rather than a decoded package (see `src/csv/` under Architecture).
239
+ Every other content format has its own standalone `readXContent`-shaped entry point (`readDocxContent`, `readPptxContent`, `readOdtContent`, `readOdpContent`, `readOdsContent`, `readOdgContent`) — xlsx is no longer the exception. `readXlsxContent`/`buildXlsxPackage` are `ooxml.js`'s own spreadsheet `ContentDocument` read/build pair — the same one the `ods⇄xlsx` bridge and every xlsx metadata-rebuild path already use internally — re-exported here directly rather than wrapped, since `readXlsxContent` already produces the right shape on its own. csv's `readCsvContent`/`buildCsvText` are the same kind of directly-exported stage pair, one level further in: they operate on RFC 4180 text rather than a decoded package (see `src/csv/` under Architecture). svg's `readSvgContent`/`buildSvgText` are the drawing-variant counterpart of csv's pair, operating on SVG text rather than a decoded package (see `src/svg/` under Architecture).
237
240
 
238
241
  ```ts
239
242
  import { buildXlsxPackage, decodeDocumentPackage, encodeDocumentPackage, readXlsxContent } from 'documents.js';
@@ -329,7 +332,7 @@ const layout = readPdf(pdfBytes); // -> LayoutDocument: pages of positioned text
329
332
  const bytes = writePdf(layout);
330
333
  ```
331
334
 
332
- The ten PDF round trips and fourteen PDF-bypassing bridge directions are also available as schema-validated [`z.codec()`](https://zod.dev) pairs (`pdfCodec`, `docxPdfCodec`, `pptxPdfCodec`, `odtPdfCodec`, `odpPdfCodec`, `odsPdfCodec`, `odgPdfCodec`, `xlsxPdfCodec`, `csvPdfCodec`, `markdownPdfCodec`, `odtDocxCodec`, `odpPptxCodec`, `odsXlsxCodec`, `odsCsvCodec`, `xlsxCsvCodec`, `markdownDocxCodec`, `markdownOdtCodec`) — the no-options form, adding automatic two-way schema validation. The two PDF-composed pairs have codec forms too (`xlsxMarkdownCodec`, `csvMarkdownCodec`):
335
+ The eleven 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`, `odtDocxCodec`, `odpPptxCodec`, `odsXlsxCodec`, `odsCsvCodec`, `xlsxCsvCodec`, `odgSvgCodec`, `markdownDocxCodec`, `markdownOdtCodec`) — the no-options form, adding automatic two-way schema validation. The two PDF-composed pairs have codec forms too (`xlsxMarkdownCodec`, `csvMarkdownCodec`):
333
336
 
334
337
  ```ts
335
338
  import { z } from 'zod';
@@ -460,6 +463,30 @@ const editor = openDocx(existingDocxBytes);
460
463
  const { diagnostics: ommlDiagnostics } = editor.body.appendParagraph().appendOfficeMath(mathml);
461
464
  ```
462
465
 
466
+ ### LaTeX lowering into the semantic core
467
+
468
+ A formula in the 3.2.0 schema carries two co-equal layers: `presentation` (a verbatim LaTeX string, rendering-authoritative) and `content` (a `MathExpression` semantic tree, computation-authoritative). Neither is stored derived from the other. This package owns the string-to-tree half — the lowering — and runs it wherever LaTeX enters the model:
469
+
470
+ - **Parsing** happens at the format edge through [temml](https://temml.org) (MIT, zero dependencies), pinned to the **exact version recorded in `package.json`** — `"temml": "0.13.4"`, no caret. The pin is load-bearing: the lowering consumes temml's internal parse-node API, which carries no stability guarantee across releases, and the two-layer contract says a stored presentation string has one defined parse. Bumping the pin is a deliberate act that must re-run `src/latex/lower.test.ts`, whose table cases pin the parse-node shapes the lowering consumes. temml is the one math component this ecosystem deliberately does not hand-write (a LaTeX grammar is a large surface with none of the supply-chain payoff the hand-written MathML engine has); it is pure JavaScript, its parser never touches the DOM, and the workerd suite proves the whole lowering path in a Cloudflare Workers isolate.
471
+ - **Lowering** is mechanical exactly where notation is unambiguous: `\frac` → `math:divide`, radicals → `math:sqrt` / an exact `1/n` exponent, a scripted Sigma or Product with limits → a `sum`/`prod` binder owning the rest of its term, numeric literals → exact rationals (`3.14` → `157/50`, BigInt-exact at any length), subscripts → distinct symbol identities through the symbol table (`x_1` is never `x` times `1`), superscripts → `math:pow` unless the table already curates the scripted form as one symbol. Named functions (`\sin`, `\ln`, ...) consume their argument the way binders consume their summand.
472
+ - **Everything context-starved degrades to visible data**: juxtaposition (`mc^2`, `f(x)`, `2(x+1)` — multiplication and function application are both defensible readings, and LaTeX cannot say which), overloaded operators (`\pm`, `\approx`), integrals (the grammar's binders are exactly sum and prod), `\text` prose, compound subscripts (`a_{i+1}`), binomials, `align`/`cases` environments — each becomes an `unparsed` node carrying the verbatim source span plus a named diagnostic from `LATEX_DIAGNOSTIC_CODES`. Never a parse failure, never a silent guess; a degraded juxtaposition is exactly what the round-trip-safe semantic editing the schema defines is for.
473
+ - **Symbol tables** come from the document's own prose: sentence-level "where R is…" / "let x be…" definitions seed curated entries (conservatively — precision over recall, no quantity kind is ever guessed), and glyphs nobody defined are minted so every `sym` reference resolves. The markdown read pass builds the table automatically.
474
+ - **The markdown read path runs the whole pipeline**: markdown-codec hands `$$` display blocks and `\( \)` inline spans through as raw LaTeX text, and `readMarkdownContent` lowers them into embedded formula blocks (position, content, presentation MathML from the same parse — so `markdownToPdf` typesets real math through the STIX engine, `markdownToDocx` writes real OMML, and `markdownToOdt` writes real embedded formula sub-documents). The write side reconstructs the same markdown math syntax from the verbatim presentation layer. The pass's diagnostics surface through `readMarkdownContent`'s third parameter.
475
+ - **The coherence lint** (`lintMathCoherence`) re-parses and re-lowers every stored presentation string against the document's own symbol table and compares with the stored content layer — divergence means somebody edited one layer deliberately, so it reports a **warning carrying provenance** and re-derives nothing.
476
+
477
+ ```ts
478
+ import { latexToFormula, lintMathCoherence, lowerLatex } from 'documents.js';
479
+
480
+ const { expression, diagnostics, mintedSymbols } = lowerLatex('\\sum_{i=1}^{n} \\frac{1}{i^2}');
481
+ // expression: { kind: 'sum', binder: 'i', lower: {kind:'num',numerator:'1',denominator:'1'}, ... }
482
+ // diagnostics: [] — fully mechanical; '2x' would degrade to unparsed + 'latex/juxtaposition-unparsed'
483
+
484
+ const { formula } = latexToFormula('x^2', { symbolEntries: table.symbols, source: 'my:pipeline' });
485
+ // formula: { mathml, presentation: { latex: 'x^2' }, content, provenance } — ready to embed
486
+
487
+ const warnings = lintMathCoherence(pkg); // [{ code: 'math/coherence-divergence', severity: 'warning', provenance, detail }]
488
+ ```
489
+
463
490
  ## Fonts
464
491
 
465
492
  Every X → PDF conversion resolves each typeface through a real `FontRegistry`, in this order:
@@ -506,8 +533,10 @@ The package is layered from generic primitives outward to the two conversion dir
506
533
  - **`src/omml/`** — the MathML ⇄ OMML structural translator, both directions. `write.ts` covers the identical construct set `src/mathml/layout.ts` typesets; `read.ts` covers strictly more (reads what Word authored, not just what this package writes). Lives outside `src/mathml/` because its I/O type is `ooxml.js`'s `XmlElement` and `src/mathml/` imports no package.
507
534
  - **`src/ooxml/`** — thin adapters over `ooxml.js`'s own `readDocx`/`readPptx`, wrapping results into `ContentDocument`. `docx/formula.ts` is the one local reading pass (splicing OOXML math equations). `docx/extras.ts`'s `readDocxExtras` returns comments/footnotes/headers/footers/numbering.
508
535
  - **`src/odf/`** — ODF-side counterparts: `readOdtContent`/`readOdpContent`/`readOdsContent`/`readOdgContent` are thin adapters over `odf.js`. `formula/read.ts`/`formula/detect.ts` handle embedded formula detection (genuinely new work with no `odf.js`-side equivalent).
509
- - **`src/markdown/`** — third adapter family, via `markdown-codec`. `readMarkdownContent` passes `readMarkdown`'s result straight through (it already produces a full `ContentDocument`). `buildMarkdownText` wraps `writeMarkdown`. `text.ts` is the byte↔text boundary. `MarkdownEditor` holds a mutable in-memory `ContentDocument`.
536
+ - **`src/latex/`** — the LaTeX presentation `MathExpression` lowering: `temml.ts` is the pinned-parser boundary (exact-version temml, its internal parse API guarded behind structural type guards), `lower.ts` the mechanical rules and their degradations, `symbols.ts` the glyph/command map and the prose definition scanner, `rational.ts` the exact-rational helpers, `lint.ts` the coherence lint. See [LaTeX lowering into the semantic core](#latex-lowering-into-the-semantic-core).
537
+ - **`src/markdown/`** — third adapter family, via `markdown-codec`. `readMarkdownContent` passes `readMarkdown`'s result through the math-lowering pass (`math.ts` — markdown-codec's preserved `$$` display blocks and `\( \)` inline spans become two-layer formula blocks, with the document's symbol table seeded from its own prose). `buildMarkdownText` wraps `writeMarkdown`, reconstructing markdown math syntax from formula blocks carrying a presentation layer. `text.ts` is the byte↔text boundary. `MarkdownEditor` holds a mutable in-memory `ContentDocument`.
510
538
  - **`src/csv/`** — fourth adapter family, sharing the spreadsheet variant with xlsx/ods. `records.ts` is the RFC 4180 record parser/writer (one shared `quoteCsvField`, also used by the `.odb` CSV exporter); `text.ts` is the byte↔text boundary, rejecting malformed UTF-8; `read.ts` turns records into a spreadsheet `ContentDocument` (first record as verbatim string header, data cells through the same cell-typing heuristic `pdfToOds` uses); `write.ts` turns one sheet of a spreadsheet `ContentDocument` back into records via each cell's `displayText`. TSV is the same format with `{ delimiter: '\t' }` on either side.
539
+ - **`src/svg/`** — fifth adapter family, sharing the drawing variant with odg. `text.ts` is the byte↔text boundary, rejecting malformed UTF-8; `read.ts` maps the six SVG shape primitives (rect/circle/ellipse/line/polyline/polygon/path) onto a one-page drawing `ContentDocument`, with transform lists composed as 2×3 affines and CSS lengths and the viewBox map resolved into page points; `write.ts` writes the six primitives back out, one shape element each; `path.ts` is the full SVG path-data grammar (M/L/H/V/C/S/Z plus Q/T/A and the relative forms — S/Q/T convert exactly, A is the one bounded approximation at ≤90° per cubic); `transform.ts` parses and composes the transform attribute and classifies the result by frame representability; `units.ts` resolves CSS length units and the viewBox; `paint.ts` resolves fill/stroke presentation attributes and dash styles; `diagnostics.ts` is the shared scope-limit vocabulary.
511
540
  - **`src/layout/`** — the pure conversion algorithms: `engine.ts` (wordprocessing → layout: flow, line-breaking, pagination), `slides.ts` (presentation → layout: direct placement), `sheets.ts` (spreadsheet → layout: grid, print settings, the first algorithm accepting `AbortSignal`), `drawing.ts` (drawing → layout: vector primitives + shape reuse), `reconstruct.ts` (layout → content: baseline clustering for wordprocessing/presentation, near-1:1 mapping for drawing, gridline-lattice-or-text-clustering for spreadsheet).
512
541
  - **`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.
513
542
  - **`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.
@@ -555,8 +584,15 @@ To run a single test file: `pnpm vitest run src/path/to/file.test.ts`.
555
584
  - **`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`.
556
585
  - **`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.
557
586
  - **The csv read shares `pdfToOds`'s cell-typing heuristic, with the same decision-only audit channel.** The first record is a verbatim string header (never re-typed, even when it looks like data); data cells re-type through `inferCellValue` exactly as the PDF reconstructor does — declines keep the plain string, `displayText` always carries the raw field text, and `onCellTypeInference` fires per decision, staying silent for header cells and no-candidate text. The parser drops blank records, so a record of one empty field alone cannot round-trip. Writing csv takes exactly one sheet: a multi-sheet source refuses with `CsvSheetNotSpecifiedError` naming every sheet until `{ sheet }` selects one. TSV is not a separate format — `{ delimiter: '\t' }` on either side parses or writes the same grid.
587
+ - **The svg read's scope limits are named diagnostics, never silent drops.** Text, images, `use` references, gradients/patterns, CSS style blocks, and out-of-scope opacity are each reported through `onSvgDiagnostic` with a code from `SVG_DIAGNOSTIC_CODES` (`svg/text-unsupported`, `svg/image-unsupported`, `svg/use-unsupported`, `svg/gradient-unsupported`, `svg/css-style-ignored`, `svg/opacity-ignored`, …) — the same contract as markdown's construct-mapping vocabulary. A plain vector SVG (the six shape primitives, transforms, paint) reads silently.
588
+ - **An absent SVG fill paints black — the SVG spec default, and the one visible svg⇄odg asymmetry.** The svg reader turns a missing `fill` attribute into a black fill; the svg writer leaves the drawing frame's absent fill unset rather than second-guessing it. Round-tripping odg→svg→odg therefore converts an unfilled odg shape into a black-filled one, mirroring what a browser would render from the same markup.
589
+ - **A rootless size falls back to the CSS default, and a stretched viewBox says so.** When neither `width`/`height` nor a `viewBox` is present, the read assumes the CSS default 300×150px viewport ({225, 112.5}pt) and reports `svg/default-size-assumed`; when `width`/`height` and the viewBox disagree in aspect ratio, the read maps through the stretched viewport and reports `svg/preserve-aspect-ratio-stretched` rather than silently re-proportioning the geometry.
590
+ - **Writing svg takes exactly one page.** An svg is a single drawing, so a multi-page source refuses with `SvgMultiPageNotSpecifiedError` naming the page count until `{ page }` selects one (an index, because drawing pages are anonymous where csv's sheets are named — the same contract one variant over).
591
+ - **svg→csv and svg→markdown honestly produce empty output.** The svg read has no text in scope, and neither csv nor markdown has a vocabulary for vectors, so the composition routes (via PDF into the spreadsheet/text readers) yield a document with nothing to emit — pinned as expected-empty in the round-trip matrix rather than dressed up as a conversion.
592
+ - **A rotated rect or ellipse stays a frame, with `rotationDeg`.** The read composes the transform list into one 2×3 affine and classifies it: an axis-aligned map (any scale, mirrors included) folds into the frame; a similarity rotation keeps the frame and records `rotationDeg` about the frame's centre; a shear or rotation-composed non-uniform scale narrows to a path. The affine itself is exact in every case — only which container carries it changes.
593
+ - **The path grammar's one approximation is the elliptical arc.** `A` converts endpoint-to-centre parameterisation exactly (F.6.5, with the F.6.5.6 radii correction), then approximates each arc segment with kappa-bounded cubics at ≤90° per cubic; S/Q/T convert exactly (a quadratic elevates to an exact cubic, T reflects the previous quadratic's own control).
558
594
  - **`reconstructWordprocessing`/`reconstructPresentation` recover vector primitives too**, in a nested drawing document — a rule under a heading, an underline, a cell background are all recovered as vectors (intended — discarding real content because it might be incidental is ruled out). A table's gridlines are excluded from vector recovery when the lattice claims them.
559
- - **Recovered vectors round-trip through all four readers** — `buildDocxPackage`/`buildPptxPackage` write real DrawingML; `buildOdtPackage`/`buildOdpPackage` write real `draw:rect`/`draw:ellipse`/`draw:line`/`draw:path`. The six PDF-bypassing bridges carry vector geometry across too.
595
+ - **Recovered vectors round-trip through all five vector-writing readers** — `buildDocxPackage`/`buildPptxPackage` write real DrawingML; `buildOdtPackage`/`buildOdpPackage` write real `draw:rect`/`draw:ellipse`/`draw:line`/`draw:path`; `buildSvgText` writes real SVG shape elements. The PDF-bypassing bridges between vector-carrying formats (odt⇄docx, odp⇄pptx, odt⇄odp, svg⇄odg) carry vector geometry across too.
560
596
  - **Each format wraps a vector shape differently.** OOXML: pptx gets a plain `p:sp`; docx gets a `w:drawing`/`wp:anchor` with `behindDoc="1"`/`wp:wrapNone` carrying a `wps:wsp`. ODF: odp appends to `draw:page`; odt anchors in a `text:p` with `style:horizontal-rel`/`style:vertical-rel="page"` (page-absolute coordinates) and `style:run-through="background"`.
561
597
  - **`ContentStroke.style` is not written by vector writers.** `LayoutLine`/`LayoutPath` carry the enum, but neither ODF nor DrawingML vector writers read it — a hand-built vector with `stroke.style` paints solid. Cell borders are a separate path that does set the style.
562
598
  - **`pdfToOds` recovers what was printed, not what was entered.** `reconstructSpreadsheet` tries a real gridline lattice first (`MIN_GRIDLINE_COUNT_PER_AXIS = 3`), using line positions directly as cell boundaries; absent one, clusters text into a grid from geometry. Column widths/row heights are measured, never invented. No print range/scale/repeat-rows/manual-breaks are inferred.
@@ -609,11 +645,11 @@ To run a single test file: `pnpm vitest run src/path/to/file.test.ts`.
609
645
  - **`convertSpreadsheetToLayout` returns `{ document, formulas }`** — formula CID-font glyph runs can't travel through `LayoutDocument.pages[].items`.
610
646
  - **`formulaSizePtForFrame` is one shared two-pass fit** — lay out once at reference size, rescale to fit both frame width and height, floored at 8pt. docx OMML (no geometry) uses height alone.
611
647
  - **Embedded-formula detection in odt/odp is genuinely new work** — `collectFormulaFrames`/`collectSlideFormulaFrames` mirror `odf.js`'s own walks. ods needs no detection pass (`odf.js` 2.2.0 classifies formula sub-documents directly).
612
- - **A formula that cannot typeset degrades to its plain-text stand-in, never to nothing.** `buildDocxPackage` writes real OMML; `buildOdtPackage` writes real embedded formula sub-documents. The markdown writer is the only stand-in-only path. `odmToPdf` carries formulas through as ordinary blocks.
648
+ - **A formula that cannot typeset degrades to its plain-text stand-in, never to nothing.** `buildDocxPackage` writes real OMML; `buildOdtPackage` writes real embedded formula sub-documents. The markdown writer reconstructs real `$$`/`\( \)` math for formulas carrying a presentation layer and falls back to the plain-text stand-in (StarMath, the verbatim presentation LaTeX, else `[formula]`) only for formulas with no LaTeX at all. `odmToPdf` carries formulas through as ordinary blocks.
613
649
  - **OMML read/write are deliberately asymmetric** — the reader covers more (`m:d`, `m:nary`, `m:acc`, `m:bar`, `m:func`, `m:sPre`) because it must read what Word wrote. `docx → odt → docx` round trips keep the mathematics but may change the OMML construct.
614
650
  - **The OMML translator covers exactly what `src/mathml/layout.ts` typesets.** A stretchy fence diverges: PDF stretches it, docx writes it at base size. `munderover` becomes nested `m:limUpp`/`m:limLow` (no operand scope in MathML).
615
651
  - **`sourcePath` traces a `LayoutItem` to its `ContentDocument` origin, but only within one read+layout pass** — not an edit-tracking mechanism. Since the frames fusion it survives as traceability only: the authoritative node↔position association is each content node's own `frames`, stamped at the moment of layout (or of reconstruction) rather than re-matched by string afterwards.
616
- - **`readMarkdownContent` passes `readMarkdown`'s result straight through** — `markdown-codec` already produces a full `ContentDocument`.
652
+ - **`readMarkdownContent` runs markdown-codec's result through the math-lowering pass** — `markdown-codec` already produces a full `ContentDocument`, but it deliberately carries `$$` display blocks and `\( \)` inline spans through as raw LaTeX text (styled paragraphs and marker runs); the pass lowers that LaTeX into two-layer formula blocks so markdown math typesets, edits, and computes like math from any other format (see [LaTeX lowering into the semantic core](#latex-lowering-into-the-semantic-core)).
617
653
  - **Every markdown construct-mapping gap is a documented `MarkdownDiagnosticCodes` entry** (`md/invented-page-geometry`, `md/nested-emphasis-flattened`, `md/link-title-dropped`, `md/code-block-info-string-dropped`, `md/blockquote-nested-depth`, `md/list-item-block-unlisted`, `md/list-item-multi-block-flattened`, `md/image-unresolved`, `md/raw-html-preserved-as-text`/`md/raw-html-dropped`, `md/front-matter-key-unmapped`, `md/heading-level-clamped`, `md/adjacent-links-merged`, `md/code-span-as-monospace-run`, `md/paragraph-indent-dropped`, `md/list-numid-fallback`, `md/table-cell-formatting-dropped`, `md/table-cell-multi-paragraph-joined`) — never a silent approximation.
618
654
  - **`buildMarkdownText` throws for non-`'wordprocessing'` `ContentDocument`.**
619
655
  - **`decodeMarkdownText` throws on malformed UTF-8** rather than producing U+FFFD.
@@ -623,21 +659,22 @@ To run a single test file: `pnpm vitest run src/path/to/file.test.ts`.
623
659
 
624
660
  Read as **row → column**. `✓` lossless, `~` bounded, `✗` lossy, `✗✗` severe, `→` one-way, `–` no conversion. `.odm`/`.odb` sit outside this table.
625
661
 
626
- | ↓ from \ to → | docx | pptx | xlsx | odt | odp | ods | odg | odf | markdown | csv | pdf |
627
- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
628
- | **docx** | — | ~ | – | ✓ | – | – | – | – | ✗ | ✗ | ~ |
629
- | **pptx** | ~ | — | – | – | ✓ | – | – | – | – | ✗ | ~ |
630
- | **xlsx** | – | – | — | – | – | ~ | – | – | ✗✗ | ~ | ~ |
631
- | **odt** | ✓ | – | – | — | ~ | – | – | – | ✗ | ✗ | ~ |
632
- | **odp** | – | ✓ | – | ~ | — | – | – | – | – | ✗ | ~ |
633
- | **ods** | – | – | ~ | – | – | — | – | – | – | ~ | ~ |
634
- | **odg** | – | – | – | – | – | – | — | – | – | ✗ | ~ |
635
- | **odf** | | | | | | | | — | – | | |
636
- | **markdown** | ~ | – | ✗✗ | ~ | – | – | – | – | — | ✗✗ | ~ |
637
- | **csv** | | | | | | | | | ✗✗ | — | ~ |
638
- | **pdf** | ✗ | ✗ | | ✗ | ✗ | ✗ | ✗ | – | ✗✗ | | |
639
-
640
- 91 of 110 directional pairs are routable. The `ContentDocument`/`LayoutDocument` pivots are the hub, not PDF — eighteen bridges bypass PDF entirely.
662
+ | ↓ from \ to → | docx | pptx | xlsx | odt | odp | ods | odg | svg | odf | markdown | csv | pdf |
663
+ | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
664
+ | **docx** | — | ~ | – | ✓ | – | – | – | ✗ | – | ✗ | ✗ | ~ |
665
+ | **pptx** | ~ | — | – | – | ✓ | – | – | ✗ | – | – | ✗ | ~ |
666
+ | **xlsx** | – | – | — | – | – | ~ | – | ✗ | – | ✗✗ | ~ | ~ |
667
+ | **odt** | ✓ | – | – | — | ~ | – | – | ✗ | – | ✗ | ✗ | ~ |
668
+ | **odp** | – | ✓ | – | ~ | — | – | – | ✗ | – | – | ✗ | ~ |
669
+ | **ods** | – | – | ~ | – | – | — | – | ✗ | – | – | ~ | ~ |
670
+ | **odg** | – | – | – | – | – | – | — | ✓ | – | – | ✗ | ~ |
671
+ | **svg** | | | | | | | | — | – | ✗✗ | ✗✗ | ~ |
672
+ | **odf** | | – | | | – | – | – | – | — | | | → |
673
+ | **markdown** | ~ | | ✗✗ | ~ | | | | ✗✗ | | — | ✗✗ | ~ |
674
+ | **csv** | ✗ | ✗ | | ✗ | ✗ | ✓ | ✗ | ✗ | – | ✗✗ | | ~ |
675
+ | **pdf** | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | – | ✗✗ | ✗ | — |
676
+
677
+ 111 of 132 directional pairs are routable. The `ContentDocument`/`LayoutDocument` pivots are the hub, not PDF — twenty bridges bypass PDF entirely.
641
678
 
642
679
  **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.
643
680
 
@@ -647,11 +684,13 @@ Read as **row → column**. `✓` lossless, `~` bounded, `✗` lossy, `✗✗` s
647
684
 
648
685
  **PDF → odg** is near-1:1 mapping (no clustering needed). Kind narrows upstream: rotated rects, freeform curves, multi-subpath figures become `path`.
649
686
 
687
+ **svg ⇄ PDF and PDF → svg** lay out through the same drawing engine odg feeds, so `svgToPdf` is bounded only by the svg read's documented scope; `pdfToSvg` reuses `pdfToOdg`'s near-1:1 vector recovery writing SVG shape elements instead. Recovered text boxes sit outside the svg writer's vector-only scope — reported per shape via `onSvgDiagnostic`, never silently dropped — and svg→csv/svg→markdown honestly produce empty output (no text in the read's scope, no vector vocabulary in the target).
688
+
650
689
  **PDF → ods** recovers what was printed, not what was entered. The printed string always survives in `displayText`; re-typed `value` is explicitly probabilistic inference.
651
690
 
652
691
  **`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). The PDF-composed markdown bridges (`xlsxToMarkdown`/`markdownToXlsx`, `csvToMarkdown`/`markdownToCsv`) stack the same two losses in both directions — hence their `✗✗` cells.
653
692
 
654
- **The five same-variant bridge pairs** (odt⇄docx, odp⇄pptx, ods⇄xlsx, csv⇄ods, csv⇄xlsx) 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.
693
+ **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.
655
694
 
656
695
  **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).
657
696
 
@@ -18,6 +18,9 @@ const require_markdown_text = require("../markdown/text.cjs");
18
18
  const require_csv_text = require("../csv/text.cjs");
19
19
  const require_csv_read = require("../csv/read.cjs");
20
20
  const require_csv_write = require("../csv/write.cjs");
21
+ const require_svg_text = require("../svg/text.cjs");
22
+ const require_svg_read = require("../svg/read.cjs");
23
+ const require_svg_write = require("../svg/write.cjs");
21
24
  const require_ports_abort = require("../ports/abort.cjs");
22
25
  const require_package_codec = require("../package-codec.cjs");
23
26
  let ooxml_js = require("ooxml.js");
@@ -88,6 +91,10 @@ const DOCUMENT_FORMAT_CODECS = {
88
91
  read: (bytes) => require_csv_read.readCsvContent(require_csv_text.decodeCsvText(bytes)),
89
92
  write: (content) => require_csv_text.encodeCsvText(require_csv_write.buildCsvText(content))
90
93
  } },
94
+ svg: { content: {
95
+ read: (bytes) => require_svg_read.readSvgContent(require_svg_text.decodeSvgText(bytes)),
96
+ write: (content) => require_svg_text.encodeSvgText(require_svg_write.buildSvgText(content))
97
+ } },
91
98
  pdf: { layout: {
92
99
  read: (bytes, options) => (0, pdf_codec.readPdf)(requireArrayBufferBytes(bytes), { signal: options?.signal }),
93
100
  write: (layout, options) => (0, pdf_codec.writePdf)(layout, { signal: options?.signal })
@@ -17,6 +17,9 @@ import { decodeMarkdownText, encodeMarkdownText } from "../markdown/text.js";
17
17
  import { decodeCsvText, encodeCsvText } from "../csv/text.js";
18
18
  import { readCsvContent } from "../csv/read.js";
19
19
  import { buildCsvText } from "../csv/write.js";
20
+ import { decodeSvgText, encodeSvgText } from "../svg/text.js";
21
+ import { readSvgContent } from "../svg/read.js";
22
+ import { buildSvgText } from "../svg/write.js";
20
23
  import { throwIfAborted } from "../ports/abort.js";
21
24
  import { decodeDocumentPackage, encodeDocumentPackage } from "../package-codec.js";
22
25
  import { buildXlsxPackage, readXlsxContent } from "ooxml.js";
@@ -87,6 +90,10 @@ const DOCUMENT_FORMAT_CODECS = {
87
90
  read: (bytes) => readCsvContent(decodeCsvText(bytes)),
88
91
  write: (content) => encodeCsvText(buildCsvText(content))
89
92
  } },
93
+ svg: { content: {
94
+ read: (bytes) => readSvgContent(decodeSvgText(bytes)),
95
+ write: (content) => encodeSvgText(buildSvgText(content))
96
+ } },
90
97
  pdf: { layout: {
91
98
  read: (bytes, options) => readPdf(requireArrayBufferBytes(bytes), { signal: options?.signal }),
92
99
  write: (layout, options) => writePdf(layout, { signal: options?.signal })
@@ -41,6 +41,11 @@ const FORMAT_CAPABILITIES = {
41
41
  variant: "drawing",
42
42
  hasLayoutPath: true
43
43
  },
44
+ svg: {
45
+ format: "svg",
46
+ variant: "drawing",
47
+ hasLayoutPath: true
48
+ },
44
49
  odf: {
45
50
  format: "odf",
46
51
  variant: "formula",
@@ -40,6 +40,11 @@ const FORMAT_CAPABILITIES = {
40
40
  variant: "drawing",
41
41
  hasLayoutPath: true
42
42
  },
43
+ svg: {
44
+ format: "svg",
45
+ variant: "drawing",
46
+ hasLayoutPath: true
47
+ },
43
48
  odf: {
44
49
  format: "odf",
45
50
  variant: "formula",
@@ -35,6 +35,10 @@ const markdownPdfCodec = zod.z.codec(require_model_bytes.MarkdownBytesSchema, re
35
35
  decode: (markdownBytes) => require_convert_convert.markdownToPdf(markdownBytes),
36
36
  encode: (pdfBytes) => require_convert_convert.pdfToMarkdown(pdfBytes)
37
37
  });
38
+ const svgPdfCodec = zod.z.codec(require_model_bytes.SvgBytesSchema, require_model_bytes.PdfBytesSchema, {
39
+ decode: (svgBytes) => require_convert_convert.svgToPdf(svgBytes),
40
+ encode: (pdfBytes) => require_convert_convert.pdfToSvg(pdfBytes)
41
+ });
38
42
  const odtDocxCodec = zod.z.codec(require_model_bytes.OdtBytesSchema, require_model_bytes.DocxBytesSchema, {
39
43
  decode: (odtBytes) => require_convert_convert.odtToDocx(odtBytes),
40
44
  encode: (docxBytes) => require_convert_convert.docxToOdt(docxBytes)
@@ -71,6 +75,10 @@ const xlsxCsvCodec = zod.z.codec(require_model_bytes.XlsxBytesSchema, require_mo
71
75
  decode: (xlsxBytes) => require_convert_convert.xlsxToCsv(xlsxBytes),
72
76
  encode: (csvBytes) => require_convert_convert.csvToXlsx(csvBytes)
73
77
  });
78
+ const odgSvgCodec = zod.z.codec(require_model_bytes.OdgBytesSchema, require_model_bytes.SvgBytesSchema, {
79
+ decode: (odgBytes) => require_convert_convert.odgToSvg(odgBytes),
80
+ encode: (svgBytes) => require_convert_convert.svgToOdg(svgBytes)
81
+ });
74
82
  const csvMarkdownCodec = zod.z.codec(require_model_bytes.CsvBytesSchema, require_model_bytes.MarkdownBytesSchema, {
75
83
  decode: (csvBytes) => require_convert_convert.csvToMarkdown(csvBytes),
76
84
  encode: (markdownBytes) => require_convert_convert.markdownToCsv(markdownBytes)
@@ -83,6 +91,7 @@ exports.markdownDocxCodec = markdownDocxCodec;
83
91
  exports.markdownOdtCodec = markdownOdtCodec;
84
92
  exports.markdownPdfCodec = markdownPdfCodec;
85
93
  exports.odgPdfCodec = odgPdfCodec;
94
+ exports.odgSvgCodec = odgSvgCodec;
86
95
  exports.odpPdfCodec = odpPdfCodec;
87
96
  exports.odpPptxCodec = odpPptxCodec;
88
97
  exports.odsCsvCodec = odsCsvCodec;
@@ -91,6 +100,7 @@ exports.odsXlsxCodec = odsXlsxCodec;
91
100
  exports.odtDocxCodec = odtDocxCodec;
92
101
  exports.odtPdfCodec = odtPdfCodec;
93
102
  exports.pptxPdfCodec = pptxPdfCodec;
103
+ exports.svgPdfCodec = svgPdfCodec;
94
104
  exports.xlsxCsvCodec = xlsxCsvCodec;
95
105
  exports.xlsxMarkdownCodec = xlsxMarkdownCodec;
96
106
  exports.xlsxPdfCodec = xlsxPdfCodec;
@@ -8,6 +8,7 @@ declare const odsPdfCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8
8
8
  declare const odgPdfCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>, z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>>;
9
9
  declare const xlsxPdfCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>, z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>>;
10
10
  declare const markdownPdfCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>, z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>>;
11
+ declare const svgPdfCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>, z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>>;
11
12
  declare const odtDocxCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>, z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>>;
12
13
  declare const odpPptxCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>, z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>>;
13
14
  declare const odsXlsxCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>, z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>>;
@@ -17,6 +18,7 @@ declare const xlsxMarkdownCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>,
17
18
  declare const csvPdfCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>, z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>>;
18
19
  declare const odsCsvCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>, z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>>;
19
20
  declare const xlsxCsvCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>, z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>>;
21
+ declare const odgSvgCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>, z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>>;
20
22
  declare const csvMarkdownCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>, z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>>;
21
23
  //#endregion
22
- export { csvMarkdownCodec, csvPdfCodec, docxPdfCodec, markdownDocxCodec, markdownOdtCodec, markdownPdfCodec, odgPdfCodec, odpPdfCodec, odpPptxCodec, odsCsvCodec, odsPdfCodec, odsXlsxCodec, odtDocxCodec, odtPdfCodec, pptxPdfCodec, xlsxCsvCodec, xlsxMarkdownCodec, xlsxPdfCodec };
24
+ export { csvMarkdownCodec, csvPdfCodec, docxPdfCodec, markdownDocxCodec, markdownOdtCodec, markdownPdfCodec, odgPdfCodec, odgSvgCodec, odpPdfCodec, odpPptxCodec, odsCsvCodec, odsPdfCodec, odsXlsxCodec, odtDocxCodec, odtPdfCodec, pptxPdfCodec, svgPdfCodec, xlsxCsvCodec, xlsxMarkdownCodec, xlsxPdfCodec };
@@ -8,6 +8,7 @@ declare const odsPdfCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8
8
8
  declare const odgPdfCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>, z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>>;
9
9
  declare const xlsxPdfCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>, z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>>;
10
10
  declare const markdownPdfCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>, z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>>;
11
+ declare const svgPdfCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>, z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>>;
11
12
  declare const odtDocxCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>, z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>>;
12
13
  declare const odpPptxCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>, z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>>;
13
14
  declare const odsXlsxCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>, z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>>;
@@ -17,6 +18,7 @@ declare const xlsxMarkdownCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>,
17
18
  declare const csvPdfCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>, z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>>;
18
19
  declare const odsCsvCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>, z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>>;
19
20
  declare const xlsxCsvCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>, z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>>;
21
+ declare const odgSvgCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>, z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>>;
20
22
  declare const csvMarkdownCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>, z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>>;
21
23
  //#endregion
22
- export { csvMarkdownCodec, csvPdfCodec, docxPdfCodec, markdownDocxCodec, markdownOdtCodec, markdownPdfCodec, odgPdfCodec, odpPdfCodec, odpPptxCodec, odsCsvCodec, odsPdfCodec, odsXlsxCodec, odtDocxCodec, odtPdfCodec, pptxPdfCodec, xlsxCsvCodec, xlsxMarkdownCodec, xlsxPdfCodec };
24
+ export { csvMarkdownCodec, csvPdfCodec, docxPdfCodec, markdownDocxCodec, markdownOdtCodec, markdownPdfCodec, odgPdfCodec, odgSvgCodec, odpPdfCodec, odpPptxCodec, odsCsvCodec, odsPdfCodec, odsXlsxCodec, odtDocxCodec, odtPdfCodec, pptxPdfCodec, svgPdfCodec, xlsxCsvCodec, xlsxMarkdownCodec, xlsxPdfCodec };
@@ -1,5 +1,5 @@
1
- import { CsvBytesSchema, DocxBytesSchema, MarkdownBytesSchema, OdgBytesSchema, OdpBytesSchema, OdsBytesSchema, OdtBytesSchema, PdfBytesSchema, PptxBytesSchema, XlsxBytesSchema } from "../model/bytes.js";
2
- import { csvToMarkdown, csvToOds, csvToPdf, csvToXlsx, docxToMarkdown, docxToOdt, docxToPdf, markdownToCsv, markdownToDocx, markdownToOdt, markdownToPdf, markdownToXlsx, odgToPdf, odpToPdf, odpToPptx, odsToCsv, odsToPdf, odsToXlsx, odtToDocx, odtToMarkdown, odtToPdf, pdfToCsv, pdfToDocx, pdfToMarkdown, pdfToOdg, pdfToOdp, pdfToOds, pdfToOdt, pdfToPptx, pdfToXlsx, pptxToOdp, pptxToPdf, xlsxToCsv, xlsxToMarkdown, xlsxToOds, xlsxToPdf } from "./convert.js";
1
+ import { CsvBytesSchema, DocxBytesSchema, MarkdownBytesSchema, OdgBytesSchema, OdpBytesSchema, OdsBytesSchema, OdtBytesSchema, PdfBytesSchema, PptxBytesSchema, SvgBytesSchema, XlsxBytesSchema } from "../model/bytes.js";
2
+ import { csvToMarkdown, csvToOds, csvToPdf, csvToXlsx, docxToMarkdown, docxToOdt, docxToPdf, markdownToCsv, markdownToDocx, markdownToOdt, markdownToPdf, markdownToXlsx, odgToPdf, odgToSvg, odpToPdf, odpToPptx, odsToCsv, odsToPdf, odsToXlsx, odtToDocx, odtToMarkdown, odtToPdf, pdfToCsv, pdfToDocx, pdfToMarkdown, pdfToOdg, pdfToOdp, pdfToOds, pdfToOdt, pdfToPptx, pdfToSvg, pdfToXlsx, pptxToOdp, pptxToPdf, svgToOdg, svgToPdf, xlsxToCsv, xlsxToMarkdown, xlsxToOds, xlsxToPdf } from "./convert.js";
3
3
  import { z } from "zod";
4
4
  //#region src/convert/codec.ts
5
5
  const docxPdfCodec = z.codec(DocxBytesSchema, PdfBytesSchema, {
@@ -34,6 +34,10 @@ const markdownPdfCodec = z.codec(MarkdownBytesSchema, PdfBytesSchema, {
34
34
  decode: (markdownBytes) => markdownToPdf(markdownBytes),
35
35
  encode: (pdfBytes) => pdfToMarkdown(pdfBytes)
36
36
  });
37
+ const svgPdfCodec = z.codec(SvgBytesSchema, PdfBytesSchema, {
38
+ decode: (svgBytes) => svgToPdf(svgBytes),
39
+ encode: (pdfBytes) => pdfToSvg(pdfBytes)
40
+ });
37
41
  const odtDocxCodec = z.codec(OdtBytesSchema, DocxBytesSchema, {
38
42
  decode: (odtBytes) => odtToDocx(odtBytes),
39
43
  encode: (docxBytes) => docxToOdt(docxBytes)
@@ -70,9 +74,13 @@ const xlsxCsvCodec = z.codec(XlsxBytesSchema, CsvBytesSchema, {
70
74
  decode: (xlsxBytes) => xlsxToCsv(xlsxBytes),
71
75
  encode: (csvBytes) => csvToXlsx(csvBytes)
72
76
  });
77
+ const odgSvgCodec = z.codec(OdgBytesSchema, SvgBytesSchema, {
78
+ decode: (odgBytes) => odgToSvg(odgBytes),
79
+ encode: (svgBytes) => svgToOdg(svgBytes)
80
+ });
73
81
  const csvMarkdownCodec = z.codec(CsvBytesSchema, MarkdownBytesSchema, {
74
82
  decode: (csvBytes) => csvToMarkdown(csvBytes),
75
83
  encode: (markdownBytes) => markdownToCsv(markdownBytes)
76
84
  });
77
85
  //#endregion
78
- export { csvMarkdownCodec, csvPdfCodec, docxPdfCodec, markdownDocxCodec, markdownOdtCodec, markdownPdfCodec, odgPdfCodec, odpPdfCodec, odpPptxCodec, odsCsvCodec, odsPdfCodec, odsXlsxCodec, odtDocxCodec, odtPdfCodec, pptxPdfCodec, xlsxCsvCodec, xlsxMarkdownCodec, xlsxPdfCodec };
86
+ export { csvMarkdownCodec, csvPdfCodec, docxPdfCodec, markdownDocxCodec, markdownOdtCodec, markdownPdfCodec, odgPdfCodec, odgSvgCodec, odpPdfCodec, odpPptxCodec, odsCsvCodec, odsPdfCodec, odsXlsxCodec, odtDocxCodec, odtPdfCodec, pptxPdfCodec, svgPdfCodec, xlsxCsvCodec, xlsxMarkdownCodec, xlsxPdfCodec };
@@ -19,6 +19,9 @@ const require_markdown_text = require("../markdown/text.cjs");
19
19
  const require_csv_text = require("../csv/text.cjs");
20
20
  const require_csv_read = require("../csv/read.cjs");
21
21
  const require_csv_write = require("../csv/write.cjs");
22
+ const require_svg_text = require("../svg/text.cjs");
23
+ const require_svg_read = require("../svg/read.cjs");
24
+ const require_svg_write = require("../svg/write.cjs");
22
25
  const require_layout_engine = require("../layout/engine.cjs");
23
26
  const require_layout_slides = require("../layout/slides.cjs");
24
27
  const require_ports_abort = require("../ports/abort.cjs");
@@ -42,6 +45,7 @@ const CONTENT_FORMATS = [
42
45
  "odp",
43
46
  "ods",
44
47
  "odg",
48
+ "svg",
45
49
  "csv",
46
50
  "markdown"
47
51
  ];
@@ -112,6 +116,18 @@ const FORMAT_NODES = {
112
116
  encode: (pkg) => (0, odf_js.encodePackage)(pkg),
113
117
  hasSourcePackage: true
114
118
  },
119
+ svg: {
120
+ variant: "drawing",
121
+ family: "svg",
122
+ decode: (bytes) => require_svg_text.decodeSvgText(bytes),
123
+ read: (text, options) => require_svg_read.readSvgContent(text, { onSvgDiagnostic: options?.onSvgDiagnostic }),
124
+ build: (content, options) => require_svg_write.buildSvgText(content, {
125
+ page: options?.page,
126
+ onSvgDiagnostic: options?.onSvgDiagnostic
127
+ }),
128
+ encode: (text) => require_svg_text.encodeSvgText(text),
129
+ hasSourcePackage: false
130
+ },
115
131
  markdown: {
116
132
  variant: "wordprocessing",
117
133
  family: "markdown",
@@ -147,6 +163,7 @@ const LAYOUT_CAPABLE = /* @__PURE__ */ new Set([
147
163
  "odp",
148
164
  "ods",
149
165
  "odg",
166
+ "svg",
150
167
  "markdown"
151
168
  ]);
152
169
  const TRANSFORMS = {
@@ -1,6 +1,7 @@
1
1
  import { t as ClockPort } from "../clock-C7SUuYN0.cjs";
2
2
  import { DocumentFormat } from "./port.cjs";
3
3
  import { t as ContentVariant } from "../capability-an5gSNsu.cjs";
4
+ import { SvgDiagnosticSink } from "../svg/diagnostics.cjs";
4
5
  import { CellTypeInferenceSink } from "../layout/cell-typing.cjs";
5
6
  import { t as OmmlDiagnostic } from "../shared-DLZ3IQUl.cjs";
6
7
  import { ContentDocument, DocumentPackage, FontSubstitution, ProvidedFont } from "document-schema.js";
@@ -25,9 +26,11 @@ interface UnifiedConversionOptions {
25
26
  readonly delimiter?: string;
26
27
  readonly sheet?: string;
27
28
  readonly onCellTypeInference?: CellTypeInferenceSink;
29
+ readonly page?: number;
30
+ readonly onSvgDiagnostic?: SvgDiagnosticSink;
28
31
  readonly clock?: ClockPort;
29
32
  }
30
- type ContentFormat = 'docx' | 'pptx' | 'xlsx' | 'odt' | 'odp' | 'ods' | 'odg' | 'csv' | 'markdown';
33
+ type ContentFormat = 'docx' | 'pptx' | 'xlsx' | 'odt' | 'odp' | 'ods' | 'odg' | 'svg' | 'csv' | 'markdown';
31
34
  type LayoutVariant = Exclude<ContentVariant, 'formula'>;
32
35
  interface PackageFormatNode {
33
36
  readonly variant: LayoutVariant;
@@ -40,7 +43,7 @@ interface PackageFormatNode {
40
43
  }
41
44
  interface TextFormatNode {
42
45
  readonly variant: LayoutVariant;
43
- readonly family: 'markdown' | 'csv';
46
+ readonly family: 'markdown' | 'csv' | 'svg';
44
47
  readonly decode: (bytes: Uint8Array<ArrayBuffer>) => string;
45
48
  readonly read: (text: string, options?: UnifiedConversionOptions) => ContentDocument;
46
49
  readonly build: (content: ContentDocument, options?: UnifiedConversionOptions) => string;
@@ -1,6 +1,7 @@
1
1
  import { t as ClockPort } from "../clock-C7SUuYN0.js";
2
2
  import { DocumentFormat } from "./port.js";
3
3
  import { t as ContentVariant } from "../capability-DX0lCSiI.js";
4
+ import { SvgDiagnosticSink } from "../svg/diagnostics.js";
4
5
  import { CellTypeInferenceSink } from "../layout/cell-typing.js";
5
6
  import { t as OmmlDiagnostic } from "../shared-DUOzjwcL.js";
6
7
  import { Package } from "ooxml.js";
@@ -25,9 +26,11 @@ interface UnifiedConversionOptions {
25
26
  readonly delimiter?: string;
26
27
  readonly sheet?: string;
27
28
  readonly onCellTypeInference?: CellTypeInferenceSink;
29
+ readonly page?: number;
30
+ readonly onSvgDiagnostic?: SvgDiagnosticSink;
28
31
  readonly clock?: ClockPort;
29
32
  }
30
- type ContentFormat = 'docx' | 'pptx' | 'xlsx' | 'odt' | 'odp' | 'ods' | 'odg' | 'csv' | 'markdown';
33
+ type ContentFormat = 'docx' | 'pptx' | 'xlsx' | 'odt' | 'odp' | 'ods' | 'odg' | 'svg' | 'csv' | 'markdown';
31
34
  type LayoutVariant = Exclude<ContentVariant, 'formula'>;
32
35
  interface PackageFormatNode {
33
36
  readonly variant: LayoutVariant;
@@ -40,7 +43,7 @@ interface PackageFormatNode {
40
43
  }
41
44
  interface TextFormatNode {
42
45
  readonly variant: LayoutVariant;
43
- readonly family: 'markdown' | 'csv';
46
+ readonly family: 'markdown' | 'csv' | 'svg';
44
47
  readonly decode: (bytes: Uint8Array<ArrayBuffer>) => string;
45
48
  readonly read: (text: string, options?: UnifiedConversionOptions) => ContentDocument;
46
49
  readonly build: (content: ContentDocument, options?: UnifiedConversionOptions) => string;