js.documents 1.92.2 → 1.92.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -4
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@ graph TD
|
|
|
13
13
|
odf("odf.js")
|
|
14
14
|
pdfcodec("pdf-codec")
|
|
15
15
|
mdcodec("markdown-codec")
|
|
16
|
+
bytecodec("byte-codec")
|
|
16
17
|
documents("documents.js")
|
|
17
18
|
mcp("document-mcp")
|
|
18
19
|
cli("document-cli")
|
|
@@ -22,10 +23,13 @@ graph TD
|
|
|
22
23
|
schema --> pdfcodec
|
|
23
24
|
schema --> mdcodec
|
|
24
25
|
schema --> documents
|
|
26
|
+
schema --> bytecodec
|
|
25
27
|
ooxml --> documents
|
|
26
28
|
odf --> documents
|
|
27
29
|
pdfcodec --> documents
|
|
28
30
|
mdcodec --> documents
|
|
31
|
+
bytecodec --> pdfcodec
|
|
32
|
+
bytecodec --> documents
|
|
29
33
|
documents --> mcp
|
|
30
34
|
pdfcodec --> mcp
|
|
31
35
|
documents --> cli
|
|
@@ -37,6 +41,7 @@ graph TD
|
|
|
37
41
|
click odf "https://github.com/ExaDev/odf.js" "odf.js"
|
|
38
42
|
click pdfcodec "https://github.com/ExaDev/pdf-codec" "pdf-codec"
|
|
39
43
|
click mdcodec "https://github.com/ExaDev/markdown-codec" "markdown-codec"
|
|
44
|
+
click bytecodec "https://github.com/ExaDev/byte-codec" "byte-codec"
|
|
40
45
|
click documents "https://github.com/ExaDev/documents.js" "documents.js"
|
|
41
46
|
click mcp "https://github.com/ExaDev/document-mcp" "document-mcp"
|
|
42
47
|
click cli "https://github.com/ExaDev/document-cli" "document-cli"
|
|
@@ -564,12 +569,12 @@ The package is layered from generic primitives outward to the two conversion dir
|
|
|
564
569
|
- **`src/odf-package/`** — the ODF-side counterpart to `src/opc/`: `manifest.ts` re-exports `odf.js`'s own manifest read/build/write/sync/validate functions (`odf.js` already owns `META-INF/manifest.xml` end to end — reading, deriving, writing, syncing, and validating it — unlike `ooxml.js`'s read-only OPC relationship handling) and adds exactly one thing of its own, `syncOdfManifest`: `odf.js`'s `buildManifest` synthesises a `manifest:file-entry` for every embedded sub-document directory it finds (any `"<dir>/content.xml"` prefix) but resolves that entry's media type by file EXTENSION, which a directory has none of, so it comes out empty unless a caller supplies an override. `syncOdfManifest` derives each one from what the sub-document actually is — the single element inside its own `office:body`, the same discriminant every `odf.js` reader keys on — and every part-mutating helper here syncs through it, so adding an image to a document that already embeds a formula cannot blank the formula object's own entry on the way past. `media.ts`'s `addImageMedia` inserts a binary image part under `Pictures/` (the real-world LibreOffice/OASIS convention, confirmed against `odf.js`'s own round-trip/manifest fixtures) — one step simpler than OOXML's own `addImageMedia` (`src/opc/media.ts`) since ODF references a media part directly by its package path (`xlink:href`) rather than through a relationship-ID indirection. `formula.ts`'s `addFormulaObject` is the newer sibling and a genuinely different shape of insertion: an embedded ODF formula is not a markup vocabulary inside the host `content.xml` the way OOXML's own OMML is, it is a WHOLE NESTED DOCUMENT stored under its own directory prefix in the same zip (`Object 1/content.xml`, an `office:document-content` > `office:body` > `office:math` > `math:math` tree), referenced from the host by a `draw:frame`/`draw:object` naming that directory — precisely what `odf.js`'s own `readOdfFormula` reads back, and what `readOdfEmbeddedFormula` (`src/odf/formula/read.ts`) resolves out of the outer package's flat parts record. The formula's own MathML nodes are written straight through with no translation and no re-serialisation, since `document-schema.js`'s `MathMlNode` and `odf.js`'s `XmlNode` are structurally identical; the `math:math` element declares the MathML namespace both as the `math:` prefix and as the default, so a prefixed tree (real LibreOffice output) and a bare one (what `src/omml/read.ts` recovers from an OOXML equation) are each genuinely namespaced. `OdpSlide.addImage`/`OdpShape` (`src/edit/odp/image.ts`) is `addImageMedia`'s real caller — and, through `src/edit/odg/*`'s wholesale reuse of `OdpShape` (see the `src/edit/` entry below), `OdgPage.addImage` too; `OdtBody.appendFormula` (via `src/edit/odt/formula.ts`) is `addFormulaObject`'s; `src/odb/read.ts` also reuses `manifest.ts`'s `readManifest` directly, to check `database/script`'s own manifest-declared media type before treating it as an HSQLDB script part.
|
|
565
570
|
- **`src/edit/`** — the read-and-write editable model: live-view classes (`DocxEditor`/`DocxParagraph`/`DocxRun`/`DocxTable`, `PptxEditor`/`PptxSlide`/`PptxShape`, `OdtEditor`/`OdtParagraph`/`OdtRun`/`OdtTable`/`OdtList`, `OdpEditor`/`OdpSlide`/`OdpShape`, `OdsEditor`/`OdsSheet`/`OdsCell`, `OdgEditor`/`OdgPage`/`OdgBoxVector`/`OdgLineVector`/`OdgPathVector`) wrapping the actual `XmlElement` objects inside a decoded `Package`, plus `buildDocxPackage`/`buildPptxPackage`/`buildOdtPackage`/`buildOdpPackage`/`buildOdsPackage`/`buildOdgPackage` bridging a `ContentDocument` to a fresh package built entirely through those same primitives — `pdfToOdt`/`pdfToOdp`/`pdfToOds`/`pdfToOdg` each call the matching one. `DocxParagraph.appendOfficeMath` and `OdtBody.appendFormula` are the formula-writing primitives, and they are deliberately shaped differently because the two formats embed a formula in genuinely different ways: `appendOfficeMath` appends a real OMML display equation (`m:oMathPara` > `m:oMath`) built by `src/omml/write.ts` INLINE in the paragraph, while `appendFormula` writes a whole nested formula sub-document into the package (`src/odf-package/formula.ts`) and appends a `draw:frame`/`draw:object` referencing it. `buildDocxPackage`/`buildOdtPackage` use them to write an embedded formula as genuine, editable math in each format instead of a plain-text stand-in. `src/edit/odp/*` reuses `src/edit/odt/*`'s own paragraph/run/list/style-interning classes WHOLESALE rather than reimplementing them for presentations: a `draw:frame`'s `draw:text-box` holds the identical `text:p`/`text:span` content model `office:text` does, interned into the identical `content.xml` `office:automatic-styles` registry (`src/edit/odt/props.ts`'s `applyStyleChange`) — `OdpShape.appendParagraph`/`.paragraphs()`/`.addList()` return real `OdtParagraph`/`OdtList` instances, not odp-specific lookalikes. The genuinely new odp-specific work is `draw:page`/`draw:frame` mechanics (a slide is a `draw:page`, a shape's geometry is explicit `svg:x`/`svg:y`/`svg:width`/`svg:height` rather than pptx's placeholder-inheritance-heavy model) and rotation: `OdpShape.rotationDeg` is a genuine `draw:transform` setter built on `odf.js`'s own `applyOdfTransform`/`resolveOdfShapeGeometry` (`typed/shared/transform.ts`) — the write-side inverse of the exact function odf.js's own reader uses. `PptxShape.rotationDeg` (`src/edit/pptx/shape.ts`) is the DrawingML analogue, a plain `a:xfrm/@rot` attribute setter (60,000ths of a degree, clockwise, ECMA-376 20.1.7.6) needing no group-composition logic of its own, since `ooxml.js`'s own `composeShapeRotationDeg` already collapses to a bare passthrough of `xfrm.rotationDeg` for a top-level, ungrouped shape. That write side now lives in `src/edit/geometry.ts` (`buildTransformAttr`/`applyOdfGeometry`), a peer of the per-format edit directories rather than inside `odp/`, because `OdgBoxVector.rotationDeg`/`OdgPathVector.rotationDeg` need the identical machinery for `draw:rect`/`draw:ellipse`/`draw:path` — odf.js resolves all four element kinds through one `resolveOdfShapeGeometry`, so there is exactly one correct inverse of it. A table INSIDE a slide shape (not a document-level table) is now writable too: `OdpSlide.addTable` builds a `draw:frame` whose only child is a `table:table` directly (no `draw:text-box` wrapper) and reuses `OdtTable`/`buildTable` WHOLESALE for it, the same content-model-is-identical-wherever-it-lives argument `OdpShape`'s own paragraph/list reuse already rests on; `PptxSlide.addTable` (`src/edit/pptx/table.ts`) is the genuinely new DrawingML-side work, since a table shape lives in its own `p:graphicFrame` — a shape kind distinct from `p:sp`/`p:pic`, with its own frame on a direct `p:xfrm` child rather than nested in a `p:spPr` — and a DrawingML table's own merge model is a THIRD distinct convention from both docx's gridSpan-collapses-the-row scheme and ODF's covered-table-cell elements: every row always carries exactly as many `a:tc` as there are grid columns, and a covered cell is marked by a plain `hMerge`/`vMerge="1"` attribute on that same element, never an omitted or a differently-tagged one. `src/edit/ods/*` has no docx/pptx/odt/odp analogue to reuse for its core concern (cell addressing) but still reuses `src/edit/odt/*`'s style interning and `src/edit/odt/content.ts`'s `populateParagraph` for cell text content — `src/edit/ods/address.ts` is the write-side counterpart to `odf.js`'s own read-side `table:number-*-repeated`-aware cursor: setting a distant cell's value splits the covering repeated run in place at that one position rather than materialising every cell in between, exactly mirroring the read-side hazard `odf.js`'s own `typed/shared/a1.ts` already solved. `src/edit/ods/print-settings.ts` is the newest addition: `OdsSheet.printSettings`'s own getter/setter, mining `styles.xml`'s `office:automatic-styles`/`office:master-styles` directly (a part no other `src/edit/ods/*` module needed to touch before) rather than `content.xml` alone, reusing `odf.js`'s own exported `findStyleElement`/`resolvePageLayoutProperties`/`parsePageSize`/`parseMargins` for the read half and `src/edit/odt/automatic-styles.ts`'s `nextStyleName` (already generic over which `office:automatic-styles` element it scans) for the write half's own fresh-name minting. `src/edit/odg/*` reuses `OdpShape`/`buildTextBoxFrame`/`insertImageFrameMedia` WHOLESALE for `draw:frame` text/image content (a drawing page's `draw:frame` content model and geometry resolution — rotation included — are byte-for-byte identical to a presentation's, both resolved through `odf.js`'s own shared `readDrawFrame`), so there is no separate `OdgShape` class at all; the genuinely new work is the vector-primitive classes (a per-kind attribute vocabulary: `svg:x`/`y`/`width`/`height` for rect/ellipse/path, `svg:x1`/`y1`/`x2`/`y2` for a line) and their own fill/stroke, which needed a small, self-contained graphic-family style writer (`src/edit/odg/style.ts`) since `odf.js`'s own `StyleRegistry` recognises `'graphic'` as a style family but its `StylePropertiesSchema` only ever models text/paragraph formatting — it has no fill/stroke fields and never emits a `style:graphic-properties` element. A path vector's own `svg:d` is generated by `src/edit/odg/svg-path.ts`, the write-side inverse of `odf.js`'s own `typed/shared/path.ts` parser — always absolute, always space-separated commands, anchoring `svg:viewBox` at `"0 0 {widthPt} {heightPt}"` so the written numbers are the exact source `ContentPathPoint` values with no rescaling arithmetic either way (see Gotchas below for the cross-check against that exact parser). That vector writer is no longer odg-only: `buildVectorElement`/`appendVectorTo` (`src/edit/odg/vector.ts`) are the single dispatch point `OdgPage.addVector`, `OdpSlide.addVector`, and `OdtBody.appendVectors` all go through, so a `draw:rect`/`draw:ellipse`/`draw:line`/`draw:path` is built exactly one way whichever ODF document kind it lands in — the same wholesale-reuse argument `OdpShape`'s own paragraph/list reuse rests on. `src/edit/drawingml/vector.ts` is the OOXML half of the same idea and a peer of the per-format directories for the same reason `src/edit/geometry.ts` is: it holds everything inside a DrawingML shape-properties element, which docx and pptx express identically (`CT_ShapeProperties` is one type in both), leaving only the per-format wrapper to `src/edit/docx/vector.ts` (a page-anchored `w:drawing`/`wp:anchor` carrying a `wps:wsp`) and `src/edit/pptx/vector.ts` (a plain `p:sp`). See the vector write-side gotchas below for the geometry mapping and the anchoring choices each makes.
|
|
566
571
|
- **`src/fonts/`** — source-embedded font extraction, and the registry composition every X → PDF conversion builds from it (see [Fonts](#fonts) above for the resolution order this produces). `obfuscation.ts` implements ECMA-376 Part 4, 2.8.1: `deriveFontKey` turns a `w:fontKey` GUID into the 16-byte XOR key — reading its 32 hex digits as byte pairs in REVERSE order, so `key[0]` is the GUID's LAST pair, verified against the specification's own worked example — and `deobfuscateEmbeddedFont` applies it twice across the part's first 32 bytes. One function covers docx and pptx both, by sniffing the leading sfnt signature FIRST and only deobfuscating bytes that are not already a recognisable font, rather than branching on source format: pptx's own `.fntdata` parts are stored clear and carry no font key at all, and a docx producer that stored a clear part stays readable too. `ooxml.ts` resolves `word/fontTable.xml` (or `ppt/presentation.xml`) through the package's own relationship graph rather than assuming a conventional path, reads each `w:embedRegular`/`w:embedBold`/`w:embedItalic`/`w:embedBoldItalic` (or `p:regular`/`p:bold`/`p:italic`/`p:boldItalic`) reference, and produces pdf-codec's `ProvidedFont` shape. `odf.ts` does the same for `style:font-face`'s `svg:font-face-src`/`svg:font-face-uri` — no relationship indirection, no obfuscation, and a face's weight/style taken from `loext:font-weight`/`loext:font-style` where a producer wrote them and from the font's OWN `OS/2` `fsSelection` bits where it did not (the better signal of the two: a `loext` attribute is a producer's claim about a file, `fsSelection` is that file's own declaration about itself). `registry.ts`'s `createDocumentFontRegistry` composes a source package plus any caller-supplied faces into a real `FontRegistry`, expressing the whole precedence chain as data (`sourceFonts` ahead of `fonts` ahead of the vendored substitutes) rather than as a branch. A face is deliberately never filtered by what the document actually uses: an embedded face is normally subsetted, so a character this package synthesises rather than reads can legitimately be absent from a face that is otherwise exactly right, and that is resolved per character by pdf-codec's own `onMissingGlyph`, not by dropping the whole face.
|
|
567
|
-
- **`src/mathml/`** — a MathML presentation-layer typesetting engine, comparable in scope to pdf-codec's own standard-14 text-layout half — genuinely self-contained: no import from `model`, `pdf-codec`, or `odf.js` at all
|
|
572
|
+
- **`src/mathml/`** — a MathML presentation-layer typesetting engine, comparable in scope to pdf-codec's own standard-14 text-layout half — genuinely self-contained: no import from `model`, `pdf-codec`, or `odf.js` at all, consuming only the `MathBox`/`MathFontMetrics`/`MathStretchResult` port contracts from `document-schema.js` (the neutral shared-schema package) and its own locally-mirrored `MathMlNode` input. `nodes.ts` defines `MathMlNode`/`MathMlElement` as a local, structurally-compatible mirror of `odf.js`'s own `XmlNode` (the same "mirror the shape, don't import the package" trick `src/interop.test.ts` already proves holds between `ooxml.js` and `odf.js`), so `odf.js`'s `readOdfFormula`'s real return value type-checks against it with zero cast. `variant.ts` maps `mathvariant` to the Unicode Mathematical Alphanumeric Symbols block (Latin/Greek/digits, including the block's own well-known Letterlike-Symbols hole-fillers — italic small h, eleven Script/Fraktur/Double-struck capitals — generated directly from Unicode's own `UnicodeData.txt`, not transcribed by hand). `operators.ts` is a deliberately bounded operator dictionary (lspace/rspace/stretchy/largeop/movablelimits per operator), not the MathML3 spec's own multi-thousand-entry table. `layout.ts` is the recursive box-model engine itself (`mrow`/`mi`/`mn`/`mo`/`mtext`/`mspace`/`msub`/`msup`/`msubsup`/`munder`/`mover`/`munderover`/`mfrac`/`msqrt`/`mroot`/`mtable`/`mtr`/`mtd`/`mstyle`/`semantics`, plus a text-content fallback with a diagnostic for anything else), driven entirely by the injected `MathFontMetrics` port (`metrics.ts`) rather than any font-parsing code of its own — pdf-codec's own `math-font.ts` is the real implementation, consumed only through this structural port, never imported directly. `compose.ts`/`radical.ts`/`length.ts` are its own small geometry helpers (baseline-offset box placement, the hand-drawn hooked radical sign `layout.ts` now uses only as a fallback when a font declares no √ MathVariants construction at all, MathML length-unit parsing). `layout.ts` additionally stretches a row's own vertical fences through the same `MathFontMetrics` port (its `stretch` method resolves the font's OpenType MATH `MathVariants` data into positioned glyph IDs), emitting them as `MathAssembledGlyphs` items — the one item kind addressed by glyph ID rather than by Unicode text, because most of the glyphs such a construction names have no code point at all. Output is a flat `MathBox` (positioned glyph runs, rules, strokes, and assembled glyph placements, box-local top-left/y-down coordinates), passed with zero cast into pdf-codec's `writePdf({ formulas })` — see pdf-codec's own README for the structural-typing mechanism that makes this work across a package boundary with no shared class or branded type.
|
|
568
573
|
- **`src/omml/`** — the MathML ⇄ OMML (Office Math Markup Language, ECMA-376 Part 1 §22.1's own `m:` vocabulary) structural translator, both directions. `write.ts`'s `buildOfficeMath`/`buildOfficeMathParagraph` are the write side, the counterpart to `src/mathml/`'s own typesetting engine, covering the identical construct set deliberately, so a formula rendered to PDF and the same formula written into a docx degrade in exactly the same places rather than one being silently better than the other: each MathML construct maps onto its real OMML element (`mfrac` → `m:f`, `msqrt`/`mroot` → `m:rad` with `m:radPr/m:degHide` and the degree/radicand order reversed, `msub`/`msup`/`msubsup` → `m:sSub`/`m:sSup`/`m:sSubSup`, `munder`/`mover` → `m:limLow`/`m:limUpp` and `munderover` → the two nested, `mtable`/`mtr`/`mtd` → `m:m`/`m:mr`/`m:e` with per-column `m:mcs`/`m:mc` justification, and every token element → an `m:r`/`m:t` run whose `mathvariant` becomes OMML's own `m:scr` script + `m:sty` style pair). `read.ts`'s `readOfficeMath`/`collectOfficeMathElements` are the read side, the structural inverse of every one of those mappings, and read STRICTLY MORE than the writer writes — deliberately, since the writer only ever has to express what MathML can say while the reader has to cope with whatever Word itself authored: `m:d` (Word's representation of every parenthesised sub-expression), `m:nary` (a sum/product/integral with limits AND its own operand), `m:acc`, `m:bar`, `m:func`, and `m:sPre` each have one exact MathML inverse and no writer counterpart at all. Both directions emit no geometry, measure nothing, and load no font — this is a vocabulary translation, not a rendering. The directory lives outside `src/mathml/` for that directory's own isolation rule: `write.ts`'s whole output type (and `read.ts`'s whole input type) is `ooxml.js`'s `XmlElement`, and `src/mathml/` imports no package at all. `shared.ts` holds what neither direction owns: the `OmmlDiagnostic` shape both report through, the one `mathvariant` ⇄ `m:scr`/`m:sty` table each reads in its own direction, and `mi`'s own intrinsic-variant default. `buildDocxPackage` and `readDocxContent` are their real callers; a construct with no counterpart in the target vocabulary degrades on its own, with a diagnostic, exactly as `src/mathml/layout.ts`'s own `unsupported` fallback does for the PDF path.
|
|
569
574
|
- **`src/ooxml/`** — resolves a `Package` into a `ContentDocument`: `docx/read.ts` and `pptx/read.ts` are now thin adapters over `ooxml.js`'s own `readDocx`/`readPptx`, wrapping their `{ metadata, sections }`/`{ metadata, slides }` result into `ContentDocument`'s `wordprocessing`/`presentation` shape. The docx style cascade (`docDefaults` → named-style `basedOn` chains → paragraph-mark run properties → character styles → direct formatting), the pptx placeholder → layout → master → theme inheritance cascade, and DrawingML geometry/colour resolution all now live upstream in `ooxml.js` itself, not in this package. `docx/formula.ts` is the one piece of genuinely local reading work left: a second, independent pass over the same `word/document.xml`, splicing every OOXML math equation `src/omml/read.ts` recovers into the sections `readDocx` produced — needed because `readDocx` has no `m:oMath` handling at all, exactly the way `src/odf/odt/read.ts` needs its own pass for a formula `odf.js`'s `readOdt` likewise does not read. Positioning is derived rather than approximated, and by a shorter route than the ODF side's own block-counting mirror needs: every `w:p` produces exactly one top-level `ContentParagraph` block and nothing else produces one, so the Nth `w:p` in the body IS the Nth paragraph-kind block. A `w:p` carrying nothing but its equation is CONSUMED by the formula block rather than emitted alongside it, which is what keeps a `docx → odt → docx` round trip from accumulating one blank paragraph per formula per hop. `docx/extras.ts`'s `readDocxExtras` is a second, independent re-projection of that same `readDocx` call, for the data `readDocxContent` genuinely cannot carry through `ContentDocument`'s section/block shape at all: comments, footnotes, headers/footers, and numbering (`abstractNum`/`num`) definitions. It calls `readDocx` a second time rather than being fused onto `readDocxContent`'s own return value — an accepted cost matching every other "each pipeline stage independently exported" pair in this codebase — and reuses `ooxml.js`'s own `Comment`/`Footnote`/`NumberingDefinitions` types directly rather than mirroring them locally.
|
|
570
575
|
- **`src/odf/`** — the ODF-side counterpart to `src/ooxml/`, resolving an `odf.js` `Package` into a `ContentDocument`: `odt/read.ts`'s `readOdtContent` is a thin adapter over `odf.js`'s own `readOdt`, wrapping its `{ metadata, sections }` result into the identical `wordprocessing` shape `readDocxContent` produces — the concrete proof that odt and docx genuinely share one pivot and one layout engine. `odp/read.ts`'s `readOdpContent` is the same adapter over `odf.js`'s own `readOdp`, wrapping `{ metadata, slides }` into the identical `presentation` shape `readPptxContent` produces. `ods/read.ts`'s `readOdsContent` wraps `odf.js`'s `readOds`'s `{ metadata, sheets }` into the `spreadsheet` `ContentDocument` variant, and `odg/read.ts`'s `readOdgContent` wraps `odf.js`'s `readOdg`'s `{ metadata, pages }` into the `drawing` variant — `odg` still has no OOXML-side sibling adapter at all (no drawing-equivalent OOXML format this package reads); `ods` now does, `ooxml.js`'s own `readXlsxContent`/`buildXlsxPackage`, consumed directly by `src/convert/convert.ts`'s `odsToXlsx`/`xlsxToOds` bridge and by `src/codecs/registry.ts`'s own xlsx `content` codec (see the `src/codecs/` entry below — the latter is what lets `readDocumentMetadata`/`setDocumentMetadata`/`buildDocumentBytes` treat xlsx uniformly with the rest of `DocumentFormat`) but deliberately not re-exported from this package's own public surface, mirroring the `readDocx`/`readPptx` non-re-export choice above. `buildOdtPackage`/`buildOdpPackage`/`buildOdsPackage`/`buildOdgPackage` (`src/edit/{odt,odp,ods,odg}/content.ts`) each bridge a `ContentDocument` back to a fresh package built on that format's own live-view editor, closing the PDF → odt/odp/ods/odg direction (`pdfToOdt`/`pdfToOdp`/`pdfToOds`/`pdfToOdg` each call the matching one) — see the `pdfToOds` gotcha below for `buildOdsPackage`'s own printSettings-writing addition. `formula/read.ts`'s `readOdfFormulaContent`/`readOdfEmbeddedFormula` are the same thin-adapter pattern over `odf.js`'s own `readOdfFormulaDocument`, for a standalone `.odf` (the whole `'formula'`-kind `ContentDocument`) and an embedded sub-object (its bare `ContentFormula`) respectively — the latter reading the sub-object's own `content.xml` directly out of the outer package's flat `Package.parts` record, no separate unzip step needed; `formula/detect.ts`'s `collectFormulaFrames`/`collectSlideFormulaFrames` are genuinely new work with no `odf.js`-side equivalent at all — `odf.js`'s own `readDrawFrameContent` doesn't recognise a `draw:object`-bearing `draw:frame` yet, so `odt/read.ts` and `odp/read.ts` each run one of these as a second pass over the same package's raw `content.xml` to find and inject a formula's own embedded-object block. `collectFormulaFrames` is a deep walk (a frame directly in the container, one nested inside a `draw:g` group with that group's own `draw:transform` composed exactly as `walkDrawShapes` composes it, and one anchored inline inside a paragraph's own run content); `collectSlideFormulaFrames` replicates `odf.js`'s own `walkDrawShapes` traversal precisely so each formula's `ContentShape` index is derived rather than guessed. See the Gotchas entry below for where each detected formula's block actually lands.
|
|
571
576
|
- **`src/markdown/`** — a third, independent counterpart to `src/ooxml/`/`src/odf/`, resolving markdown text into a `ContentDocument` via the external [`markdown-codec`](https://github.com/ExaDev/markdown-codec) dependency rather than a package format: `read.ts`'s `readMarkdownContent` is a thin adapter over `markdown-codec`'s own `readMarkdown`, re-stamping `documents.js`'s own `CONTENT_FORMAT_VERSION` onto a fresh envelope (`markdown-codec`'s `readMarkdown` already produces a full `document-schema.js` `ContentDocument`, structurally identical to but nominally separate from this package's local one) — mirroring `readOdtContent`/`readDocxContent` exactly, and the concrete third proof (after odt/docx) that this pivot and layout engine are genuinely format-agnostic. `write.ts`'s `buildMarkdownText` is the reverse, a thin wrapper over `markdown-codec`'s own `writeMarkdown` — deliberately living beside `read.ts` rather than under `src/edit/markdown/`, since `MarkdownEditor` (`src/edit/markdown/editor.ts`) calls it directly as its own `toMarkdownText` rather than this module reaching back into `src/edit/`. `MarkdownEditor` does now exist, alongside `DocxEditor`/`OdtEditor`/etc., but it holds a mutable in-memory `ContentDocument` rather than a real `XmlElement` tree inside a decoded `Package` — markdown has no such tree at all — so every `MarkdownParagraph`/`MarkdownRun`/`MarkdownTable`/`MarkdownTableCell` it produces holds a direct reference into that plain object instead, and saving is nothing more than calling `buildMarkdownText` again. `text.ts`'s `decodeMarkdownText`/`encodeMarkdownText` are the byte↔text boundary neither `readMarkdown`/`writeMarkdown` nor `markdownCodec`'s own `MarkdownBytesSchema` sit on (both operate on strings, not bytes) — the step every bytes-in/bytes-out ergonomic conversion in `convert.ts` needs, using a fatal-mode `TextDecoder` so a non-UTF-8 input throws immediately rather than silently producing replacement characters.
|
|
572
|
-
- **`src/layout/`** — the pure conversion algorithms, importing `model`, (for formula placement) `mathml`, and
|
|
577
|
+
- **`src/layout/`** — the pure conversion algorithms, importing `model`, (for formula placement) `mathml`, and consuming port contracts from `document-schema.js` (`TextMeasurer`, `StyledRun`/`WrappedLine`/etc., `MathFontMetrics`/`MathBox`) plus byte/image utilities from `byte-codec` (`crc32`, `decodePng`, `readJpegInfo`). The layout engine owns its own pure primitives (`wrapRunsToWidth` in `src/layout/text-layout.ts`, `rotatePointAboutCenter` in `src/model/geometry.ts`), receives a `mathMetricsAt` factory via injection (built by `src/convert/convert.ts` from pdf-codec's `loadMathFont`), and reaches into pdf-codec for only two deliberately PDF-read-natured residuals: `resolveStandardFont`/`STANDARD_METRICS` in `reconstruct.ts` (reconstructing a `ContentDocument` from a PDF-sourced `LayoutDocument` inherently needs standard-14 font metrics). Everything else — the font-resolution `TextMeasurer`, the math-font loader, `readPdf`/`writePdf` — is consumed by `src/convert/convert.ts` (the composition layer that owns concrete pdf-codec imports) and injected into the layout engine as ports, never imported directly by `src/layout/` itself: `engine.ts` (`ContentDocument` wordprocessing → `LayoutDocument`: flow, line-breaking, pagination — fed identically by docx-, odt-, and markdown-sourced content; also returns `WordprocessingLayoutResult.formulas`, every embedded formula block it laid out via `src/mathml`'s `layoutFormula`, positioned in PDF page space — see the Gotchas entry below on why a formula can't become an ordinary `LayoutItem`), `slides.ts` (`ContentDocument` presentation → `LayoutDocument`: direct EMU-to-point placement, no pagination needed — fed identically by pptx- and odp-sourced content; also exports `convertShape`, the single-`ContentShape`-to-`LayoutItem[]` conversion `drawing.ts` below reuses verbatim, now optionally formula-aware via its own trailing `formulaContext` parameter so `drawing.ts`'s existing call site keeps compiling unchanged), `sheets.ts` (`ContentDocument` spreadsheet → `LayoutDocument`: resolve the print range, build cumulative column/row offsets skipping hidden ones, reserve header/repeat-row-column space, resolve an explicit or non-iterative fit-to-page scale, partition into column/row bands honouring manual breaks with the same "an oversized item gets its own band and overflows rather than looping" guarantee `engine.ts`'s `ensureRoom` documents, emit pages in `downThenOver`/`overThenDown` order, then per page paint cell backgrounds/gridlines/cell borders/headers/cell text, honouring a cell's own `alignment`/`verticalAlignment` where it declares one and falling back to the value-kind default and bottom where it doesn't, with `###`/spill-then-truncate overflow handling — the first layout algorithm in this package that accepts an `AbortSignal`, since a 50k-cell sheet needs cancellation where a docx/pptx page count never did; also returns `SpreadsheetLayoutResult.formulas`, every cell-anchored embedded formula it laid out via `src/mathml`'s `layoutFormula`, resolved against the anchor cell's own already-positioned axis geometry plus the frame's cell-relative offset and positioned in PDF page space — the sheets-side counterpart to `engine.ts`'s and `slides.ts`'s own formula output), `drawing.ts` (`ContentDocument` drawing → `LayoutDocument`: one `ContentDrawPage` per PDF page, direct placement like `slides.ts`, with one new emission path — an unrotated `ContentVector` `rect`/`ellipse`/`line` maps onto the pre-existing `LayoutRect`/`LayoutEllipse`/`LayoutLine` kinds, a `path` vector's local, viewBox-relative subpath points are resolved through the vector's own frame offset then a single page-space flip into a `LayoutPath` value, and a *rotated* rect/ellipse/path becomes a `LayoutPath` of rotated points since neither `LayoutRect` nor `LayoutEllipse` models rotation; the page's `shapes` and `vectors` are merged into one true-paint-order walk through their shared `paintOrder` field rather than painted as two sequential arrays), `reconstruct.ts` (`LayoutDocument` → `ContentDocument`: `reconstructWordprocessing`/`reconstructPresentation` do baseline-proximity line clustering, then paragraph/text-block clustering from geometry — PDF has no semantic paragraph or shape structure to recover, only positioned glyphs; `reconstructDrawing` does no clustering at all, since a drawing has no such structure to infer in the first place — every `LayoutItem` maps close to 1:1 back onto a `ContentVector` `rect`/`ellipse`/`line`/`path` or a `ContentShape`, in the exact z-order it was painted, bucketed into `ContentDrawPageSchema`'s own `shapes`/`vectors` arrays with each item's walk position stamped as its `paintOrder`, so the relative order between the two arrays survives; `reconstructSpreadsheet` tries a real gridline lattice first — scanning the page's `LayoutLine`/stroked-single-segment-`LayoutPath` items for enough parallel horizontal and vertical lines at consistent positions to call it a printed grid, using those line positions directly as cell boundaries when found — and falls back to text-position clustering otherwise, reusing this same module's `clusterIntoLines` for rows and a parallel recurring-x-position generalisation of `clusterIntoParagraphs`'s own `dominantLeftX` for columns; every recovered cell is a bare string, column widths/row heights are genuinely measured from whichever geometry was used, and no print range/scale/repeat-rows/repeat-columns/manual-breaks are ever inferred).
|
|
573
578
|
- **`src/hsqldb/`** — the `.odb` decoders, in two tiers over two genuinely different on-disk storage shapes a HSQLDB table can use. `script.ts` (Tier 1): a small, bounded HSQLDB TEXT-script-format (`hsqldb.script_format=0`) DDL/DML text parser, not a database engine — `parseHsqldbScript(bytes)` extracts `CREATE TABLE`'s own column names/types and `INSERT INTO`'s own row values into `HsqldbTable[]`, tolerating (skipping) every other statement kind real HSQLDB output emits that this package has no use for (users, grants, sequences, indexes, views), and throwing `HsqldbScriptParseError` for anything matching neither list. `rowformat.ts`/`cache.ts` (Tier 2): a CACHED table's own binary row-store format — LibreOffice's embedded-HSQLDB default (`database.isStoredFileAccess()` switches `hsqldb.default_table_type` to `cached` specifically for storage-backed access, confirmed against the decompiled engine source) — a CACHED table's DDL still lives in `database/script` as ordinary TEXT (Tier 1 parses it unmodified) but its row *data* lives in a separate binary page-cache file, `database/data`. `rowformat.ts` decodes one column's own binary field at a time (`HsqldbDataCursor`, a big-endian `DataView` cursor; `readHsqldbColumnValue`, one branch per SQL type code); `cache.ts` walks a table's own AVL row-position tree (`readHsqldbCachedTableRows`, following each row's persisted `iLeft`/`iRight` child positions recursively, needing no key-comparison or free-list logic at all — a deleted row is already unlinked from the tree before its space can be reused, so a traversal rooted at the tree's current root only ever reaches live rows), rooted at the position `parseHsqldbIndexRoots` recovers from each table's own `SET TABLE ... INDEX'...'` script line, using `parseHsqldbProperties`'s reading of `database/properties` (cache-file scale, engine version) to resolve byte offsets; `decodeHsqldbCachedTables` is the orchestration `src/odb/read.ts` calls, splicing real rows into every table with an index-root line and leaving every other table (MEMORY/TEXT, or a genuinely empty CACHED table — HSQLDB never writes an index-root line for one) exactly as Tier 1 already produced it. `binary-script.ts` (Tier 4): HSQLDB's own whole-script BINARY (`hsqldb.script_format=1`) and COMPRESSED (`=3`) serialisations of `database/script` itself — `parseHsqldbBinaryScript` reads the leading `org.hsqldb.Result` record carrying the database's DDL, rejoins its statements into exactly the TEXT-format script text the same database would have written at `script_format=0`, feeds that to Tier 1, and then decodes the per-table row sections that follow through `rowformat.ts`'s existing per-column decoder; `inflateHsqldbCompressedScript` is the zlib unwrap `=3` needs first, `fflate`'s `unzlibSync`, the one place in `src/hsqldb/` with a dependency beyond `document-schema.js`. All tiers mirror pdf-codec's own isolation discipline: `script.ts` imports only `document-schema.js`'s `ContentCellValue` type; `rowformat.ts` imports the same plus nothing else; `cache.ts` imports only those two and `script.ts`'s own types — no odf.js `Package`/`XmlElement` knowledge anywhere in `src/hsqldb/` — the caller is responsible for handing every function its raw bytes/text already extracted from a real `.odb` package. `HsqldbTable`/`HsqldbColumn` are also the shared pivot shape `src/firebird/`'s own Tier 3 decoder below produces. See Gotchas for Tier 2's own version scope and verification account.
|
|
574
579
|
- **`src/firebird/`** — the Tier 3 `.odb` decoder: a reader for Firebird's own gbak logical-backup format (`database/firebird.fbk`), the artifact a real Firebird-embedded `.odb` actually contains — see the README's own Gotchas entry below for the empirical finding that this is NOT a raw on-disk ODS page dump, the single largest correction this subsystem's own design went through. `reader.ts` holds the two distinct byte-level primitives the format mixes (`FirebirdBackupReader`, the generic little-endian tag+length+value attribute framing every `rec_*`/`att_*` record uses, plus its own RLE/"PackBits"-style decompression for `att_data_data` when the backup is compressed; `XdrReader`, the big-endian, 4-byte-aligned RFC 1832 XDR decoding a row's own field values use once compression is peeled off). `blr-types.ts` maps a field's own BLR type opcode (`att_field_type`) onto its physical storage representation, sourced directly from Firebird's own `blr.h`/`align.h`. `date.ts` restates Firebird's own MJD-epoch DATE and 1/10000-second-tick TIME encoding, taken from `NoThrowTimeStamp.cpp`. `schema.ts` walks `rec_relation`/`rec_field` (column definitions gbak has ALREADY resolved from the live engine's system tables at backup time — see the Gotchas entry). `data.ts` walks `rec_relation_data`/`rec_data` (a relation's own rows, addressed by name), decoding each row's XDR-and-possibly-RLE-compressed field-value sequence into `ContentCellValue[]`. `backup.ts`'s `readFirebirdBackup` is the top-level entry point, producing the identical `HsqldbTable[]` shape `parseHsqldbScript` does.
|
|
575
580
|
- **`src/odb/`** — the decoder-selection and pivot-mapping layer sitting between odf.js's `.odb` support and `src/hsqldb/`/`src/firebird/`: `read.ts`'s `readOdbTables(pkg)` calls odf.js's own `readOdbInventory` to classify the package's connection (throwing `OdbNoEmbeddedDataSourceError` for an external-only datasource) and its embedded engine, then routes a genuine HSQLDB TEXT script to `parseHsqldbScript` and a BINARY/COMPRESSED one to `src/hsqldb/binary-script.ts`'s `parseHsqldbBinaryScript` (which recovers the identical TEXT-format DDL either way), then — whenever a `database/data` part is present — hands that result to `src/hsqldb/cache.ts`'s `decodeHsqldbCachedTables` to splice in every CACHED table's real rows (a `.odb` with no CACHED table at all, the common case, never even looks for `database/data`, leaving the script-derived result untouched), or routes a Firebird `database/firebird.fbk` part to `readFirebirdBackup` — throwing `OdbUnsupportedFormatError` for an embedded engine, or an engine storage shape, it has no reader for at all. `spreadsheet.ts`'s `odbTablesToSpreadsheetDocument` maps `HsqldbTable[]` onto the same `ContentSheet`-based `ContentDocument` spreadsheet variant `readOdsContent`/`buildOdsPackage` already produce and consume, feeding `odbToXlsx`'s call into `buildXlsxPackage` directly. `csv.ts`'s `buildOdbTableCsv` writes exactly one named table as CSV bytes, with no `ContentSheet`/xlsx machinery involved at all, throwing `OdbTableNotSpecifiedError`/`OdbTableNotFoundError` (naming every available table) when the caller's own `table` option doesn't resolve to exactly one table.
|
|
@@ -583,7 +588,7 @@ The package is layered from generic primitives outward to the two conversion dir
|
|
|
583
588
|
- **`src/metadata/`** — cross-format metadata read/write, both dispatched through `DOCUMENT_FORMAT_CODECS` (above) rather than a hand-written per-format switch. `read.ts`'s `readDocumentMetadata` resolves a `LayoutMetadata` for any of the ten `DocumentFormat`s, with one deliberately-kept named exception: xlsx does **not** dispatch through the registry's own `content` codec at all, instead rendering through `xlsxToPdf` and reading the resulting PDF's own metadata, because a direct `readXlsxContent(...).metadata` and that PDF-preview path disagree on real fields (`createdIso`/`modifiedIso`/`producer`) — confirmed directly rather than assumed (`read.test.ts`'s own xlsx case), so switching xlsx onto the uniform path here would silently change what this function reports. `write.ts`'s `setDocumentMetadata` patches `title`/`author`/`subject`/`keywords` in place without converting format: a `pdf` source/target patches the parsed `LayoutDocument` directly, and every other `REBUILD_FORMATS` member (`docx`/`pptx`/`odt`/`odp`/`ods`/`odg`/`markdown`/`xlsx`) rebuilds a fresh package from that format's own `ContentDocument` via the registry's `content` codec — xlsx joined this set once the registry gained a real xlsx codec (`src/codecs/registry.ts`), so it is no longer rejected the way it once was; `odf` is still rejected outright in both directions (no write path back out at all).
|
|
584
589
|
- **`src/package-codec.ts`** — `decodeDocumentPackage`/`encodeDocumentPackage`/`decodeOdbPackage` (Usage above), the format-aware counterpart to `ooxml.js`'s/`odf.js`'s own `decodePackage`/`encodePackage`. Dispatches docx/pptx/xlsx through `ooxml.js`'s OPC codec and odt/odp/ods/odg/odf through `odf.js`'s ODF codec by a plain format-membership lookup, throwing `UnsupportedPackageFormatError` (a named class, matching this package's own "recognised but unsupported" convention — `OdbUnsupportedFormatError`, `UnsupportedFontSourceFormatError`) for `markdown`/`pdf`, neither of which has a raw-package concept at all. `decodeOdbPackage` decodes `.odb` bytes through the identical `odf.js` `decodePackage` regardless — `.odb` is at the raw-zip-container level an ordinary ODF package — but is deliberately kept out of `decodeDocumentPackage`'s own `DocumentFormat`-keyed dispatch, since `'odb'` is not, and cannot be, a `DocumentFormat` member (see the `.odb` Architecture/Gotchas entries below); there is no `encodeOdbPackage`, since nothing in this package's `.odb` support ever writes a new `.odb` file.
|
|
585
590
|
|
|
586
|
-
Dependency direction among this package's own local modules is downward and checkable, with one deliberate exception (`layout`, noted below): `mathml`/`ports` import nothing local (`mathml` is fully self-contained — no dependency on `model`, `document-schema.js`, or any ODF package, since it consumes only its own locally-mirrored `MathMlNode` input and its own injected `MathFontMetrics` port); `model` imports nothing local at all any more — `formula.ts`'s former type-only `MathMlNode` import from `mathml` is gone with the local `EmbeddedFormula` type it served, since document-schema.js now owns a fully-specified `MathMlNode` of its own; `ooxml/*` imports no local module at all (now a thin adapter over `ooxml.js`'s own `readDocx`/`readPptx` — see the `src/ooxml/` entry above — with no `model`/`xml/*` dependency of its own left, since `ContentDocument`/`CONTENT_FORMAT_VERSION` now come straight from `document-schema.js`; no PDF knowledge either); `odf/*` imports `model` only, and only for `formula.ts`'s block/document builders and `geometry.ts`'s `Box`/`PAGE_SIZE_A4` (its own `ContentDocument`/`CONTENT_FORMAT_VERSION` usage is `document-schema.js`-direct too now — no PDF knowledge, no `xml/*` — `odf.js` already owns its own XML query helpers); `markdown` imports `model` only, and only for `formula.ts`'s stand-in text on the write side (`write.ts` flattens a formula block markdown cannot represent), plus the external `markdown-codec` dependency directly (no PDF knowledge, no odf.js/ooxml.js knowledge at all — the one adapter package in this family whose source format is not a zip archive); `omml` imports `mathml` (its node helpers, operator dictionary, `mathvariant` type, and length parser) and `xml/*` (`fragment.ts`'s `el`/`txt`, `entities.ts`'s `encodeXmlText`) only, plus `ooxml.js` for its own `XmlElement` output type — never `model`, `layout`, or any ODF package, and never in the other direction: `mathml` still imports nothing local at all, which is exactly why this translator is a sibling of it rather than a file inside it; `hsqldb` imports `document-schema.js` only (no odf.js knowledge); `firebird` imports `document-schema.js` (its own row/schema decoding, `ContentCellValue` only) and `hsqldb` (`HsqldbTable`/`HsqldbColumn`, a type-only import for its own output shape — the deliberate pivot-sharing point between Tier 1 and Tier 3) but no odf.js knowledge at all; `layout` imports `model`+`mathml`+`ports`, plus
|
|
591
|
+
Dependency direction among this package's own local modules is downward and checkable, with one deliberate exception (`layout`, noted below): `mathml`/`ports` import nothing local (`mathml` is fully self-contained — no dependency on `model`, `document-schema.js`, or any ODF package, since it consumes only its own locally-mirrored `MathMlNode` input and its own injected `MathFontMetrics` port); `model` imports nothing local at all any more — `formula.ts`'s former type-only `MathMlNode` import from `mathml` is gone with the local `EmbeddedFormula` type it served, since document-schema.js now owns a fully-specified `MathMlNode` of its own; `ooxml/*` imports no local module at all (now a thin adapter over `ooxml.js`'s own `readDocx`/`readPptx` — see the `src/ooxml/` entry above — with no `model`/`xml/*` dependency of its own left, since `ContentDocument`/`CONTENT_FORMAT_VERSION` now come straight from `document-schema.js`; no PDF knowledge either); `odf/*` imports `model` only, and only for `formula.ts`'s block/document builders and `geometry.ts`'s `Box`/`PAGE_SIZE_A4` (its own `ContentDocument`/`CONTENT_FORMAT_VERSION` usage is `document-schema.js`-direct too now — no PDF knowledge, no `xml/*` — `odf.js` already owns its own XML query helpers); `markdown` imports `model` only, and only for `formula.ts`'s stand-in text on the write side (`write.ts` flattens a formula block markdown cannot represent), plus the external `markdown-codec` dependency directly (no PDF knowledge, no odf.js/ooxml.js knowledge at all — the one adapter package in this family whose source format is not a zip archive); `omml` imports `mathml` (its node helpers, operator dictionary, `mathvariant` type, and length parser) and `xml/*` (`fragment.ts`'s `el`/`txt`, `entities.ts`'s `encodeXmlText`) only, plus `ooxml.js` for its own `XmlElement` output type — never `model`, `layout`, or any ODF package, and never in the other direction: `mathml` still imports nothing local at all, which is exactly why this translator is a sibling of it rather than a file inside it; `hsqldb` imports `document-schema.js` only (no odf.js knowledge); `firebird` imports `document-schema.js` (its own row/schema decoding, `ContentCellValue` only) and `hsqldb` (`HsqldbTable`/`HsqldbColumn`, a type-only import for its own output shape — the deliberate pivot-sharing point between Tier 1 and Tier 3) but no odf.js knowledge at all; `layout` imports `model`+`mathml`+`ports`, plus port contracts from `document-schema.js` (`TextMeasurer`, `StyledRun`/`WrappedLine`/etc., `MathFontMetrics`/`MathBox`) and byte/image utilities from `byte-codec` (`crc32`, `decodePng`, `readJpegInfo`), with only two deliberately PDF-read-natured residuals reaching into `pdf-codec` directly (`resolveStandardFont`/`STANDARD_METRICS` in `reconstruct.ts` — see the `src/layout/` entry above for exactly which); `odf-package` imports odf.js only (no local dependency, mirroring `opc`'s relationship to `ooxml.js`); `fonts` imports no local module at all either — only `ooxml.js`/`odf.js` for the two package shapes it reads and `pdf-codec` for the `ProvidedFont`/`FontRegistry` shapes it produces, so it sits beside `layout` rather than under it despite both feeding the same conversion; `odb` imports `hsqldb`+`firebird`+`model`+`odf-package`+odf.js only, and its own `odb/sql` and `odb/formula` subtrees import strictly less than that — `odb/values.ts` plus `document-schema.js`'s `ContentCellValue` plus `hsqldb`'s `HsqldbTable` type for the former, and `odb/values.ts` plus `ContentCellValue` plus `odb/sql`'s `SqlResultSet` type for the latter, with odf.js reaching `odb/formula` only through its one `definition.ts` adapter; `odb/report` is the one subtree that imports *more* than `odb` itself rather than less, since rendering is where the two halves finally meet — `odb/sql`, `odb/formula`, `odb/read.ts`, `hsqldb`'s `displayTextFor`, `model`'s `PAGE_SIZE_A4`, `document-schema.js`'s content vocabulary, and odf.js's `OdbReport` shape — and it still keeps each of those to one module: `Package` reaches only `source.ts`/`content.ts`, and `ContentDocument` only `render.ts`; `convert` composes everything else, including `fonts` and `pdf-codec` directly for `readPdf`/`writePdf`/`loadMathFont`/`createFontMeasurer`/`createFontRegistry` and `markdown-codec` indirectly via `markdown/read.ts`/`markdown/write.ts`/`markdown/text.ts`. Beyond this package's own local modules, six external dependencies each own a distinct concern with no overlap: `ooxml.js` (docx/pptx/xlsx ⇄ JSON), `odf.js` (odt/ods/odp/odg ⇄ JSON), `document-schema.js` (the shared `ContentDocument`/`LayoutDocument` schemas AND the port contracts — `TextMeasurer`, `ProvidedFont`/`FontSubstitution`, the `MathBox`/`MathFontMetrics` family), `pdf-codec` (the PDF codec itself, plus the text-layout/font-resolution primitives built on it), `byte-codec` (generic byte/image utilities — ByteWriter, CRC-32, deflate/inflate, PNG/JPEG encode/decode), and `markdown-codec` (CommonMark+GFM ⇄ `ContentDocument`). No `PdfObject`/`PdfDict`/`PdfStream` type appears anywhere in this package at all — that type is pdf-codec's own internal concern now, never exposed across the package boundary.
|
|
587
592
|
|
|
588
593
|
## Build, test, and lint
|
|
589
594
|
|
|
@@ -748,6 +753,8 @@ Neither direction is round-trip-lossless, and no conversion is the exact inverse
|
|
|
748
753
|
|
|
749
754
|
**The two markdown cross-format bridge pairs (`markdownToDocx`/`docxToMarkdown`, `markdownToOdt`/`odtToMarkdown`) bypass the PDF pivot entirely too, exactly like the three pairs above — but "no PDF-pivot lossiness" is not the same claim as "no lossiness at all", and conflating the two here would misdescribe what these specifically preserve.** There is genuinely no layout engine and no geometry-based reconstruction anywhere in either bridge's own call path (proven the same way the three pairs above are, by `src/convert/bridges.test.ts`'s own spy-based "the layout engine was never called" assertions) — `markdownToDocx`/`markdownToOdt` carry a heading's `Heading1`-style `styleId`, a bold/italic run, list membership and nesting level, and GFM table structure through to a real docx/odt `ContentDocument` with zero approximation, and `docxToMarkdown`/`odtToMarkdown` carry the reverse just as faithfully for whatever markdown itself can represent. The asymmetry is upstream of the bridge mechanism, in what CommonMark/GFM's own grammar has room for at all: a docx/odt run's colour, explicit font family/size, and paragraph alignment have no markdown source construct to survive as, so `docxToMarkdown`/`odtToMarkdown` drop them — not because the bridge approximates anything, but because there is nothing to carry them in. Going the other way, `markdownToDocx`/`markdownToOdt` never invent formatting markdown never expressed, so nothing is lost on that hop that wasn't already absent from the source. This is real, permanent, format-boundary lossiness, on exactly one side of the pair — a different shape from `ods ⇄ xlsx`'s own several small, independent format-boundary gaps (percentage/currency, time/date, formula dialect), but a real loss all the same, not the "categorically different, no round-trip-lossless caveat at all" case the three original bridge pairs are.
|
|
750
755
|
|
|
756
|
+
**Four cross-variant content bridges (`docxToPptx`/`pptxToDocx`, `odtToOdp`/`odpToOdt`) bypass the PDF pivot too, but through a genuine semantic transform rather than a direct content copy.** A flow document has no slide boundaries and a deck has no flow, so `wordprocessingToPresentation` (src/convert/variant-bridges.ts) splits a document's blocks into slides at heading/page-break boundaries, and `presentationToWordprocessing` concatenates every slide's shapes' blocks into one flow section. Both directions are APPROXIMATIONS — slide boundaries are a heuristic, not a recovered structure — but the blocks themselves (paragraphs, tables, images, run styling, list membership) survive intact through both transforms. Proven by `src/convert/bridges.test.ts`'s own dedicated cross-variant round-trip tests, including the "the layout engine was never called" assertion.
|
|
757
|
+
|
|
751
758
|
**`.odb` table extraction (`readOdbTables`, all four tiers) is a genuine, verified data extraction, not an approximation — but it recovers only what a `.odb`'s own embedded database storage actually carries, which differs by tier.** Tier 1 (HSQLDB TEXT script) parses real DDL/DML text, so a table's own declared column types survive as the literal SQL clause they were declared with, and row values are the literal `INSERT` statement literals. Tier 4 (HSQLDB whole-script BINARY/COMPRESSED) is Tier 1's own equal in fidelity, not a degraded variant of it: the DDL it recovers is the identical statement text a TEXT-format script would have carried, and the row values it decodes come from the same per-column binary encoding Tier 2 reads, verified against the engine's own JDBC read-back of both real fixtures. Tier 2 (HSQLDB CACHED-table binary row store) shares Tier 1's own DDL-derived column types — a CACHED table's DDL still lives in `database/script` as ordinary TEXT — but decodes its actual row *values* from a separate binary page-cache file, `database/data`, cross-verified field-by-field against a real HSQLDB JDBC oracle on the identical fixture (see the Gotchas entry above). Tier 3 (Firebird) decodes a real gbak backup stream — every cell value, `NULL`, and column name is genuinely read from the file, cross-verified field-by-field against real LibreOffice's own SDBC query on the identical fixture (see the Gotchas entry above for the full verification transcript) — but a column's own `HsqldbColumn.type` label is *synthesised* from the field's binary metadata (BLR type + length + scale), not lifted from source SQL text the way Tier 1/2's is, since a gbak backup carries no DDL text at all. No tier recovers a database's own forms, reports, or queries (names only, never content — see the gotcha above), and none has a reverse (xlsx/CSV → `.odb`) direction. BLOB column content is genuinely recovered too, byte-for-byte — see the dedicated Gotchas entry above for the record shape and the base64 `data:` URI a binary blob arrives as, which is a `ContentCellValue` schema gap rather than a decoding one. Tier 3 retains two real, bounded, honestly-scoped gaps, both documented in code comments at the exact spot each applies: no FB4+-only types (`INT128`/`DECFLOAT`, i.e. a `NUMERIC`/`DECIMAL` column wider than 18 digits of precision), which is a hard environmental limit rather than a decoding shortcut — see the Gotchas entry above for the empirical confirmation that LibreOffice's own bundled engine cannot declare such a column at all, so no `.odb` exists to verify a decoder against; and a blob-VALUED metadata *attribute* (a relation/field/index/trigger's own description, default value, or BLR body) uses a different, compound wire encoding this reader's generic attribute-skip does not yet handle — never encountered by any real fixture this reader was verified against, but a real gap on a `.odb` whose tables carry comments or computed columns.
|
|
752
759
|
|
|
753
760
|
**Running a `.odb`'s own saved query (`parseSelect`/`evaluateSelect`) is exact within its grammar, and a hard failure outside it — never an approximation.** Unlike every conversion above, there is no fidelity spectrum here: a statement either falls inside `src/odb/sql/parser.ts`'s closed grammar, in which case the rows it returns are the rows SQL defines for it (three-valued NULL logic, NULL-aware aggregates, stable multi-column ordering — see the Gotchas entries above for each decision spelled out), or it falls outside, in which case it throws with the construct named. Nothing in between: the engine never drops a clause it cannot handle and returns the rest. What it is *not* is a database — there is no query planner, no index, no transaction, no cursor, and every row of the table is materialised in memory by `readOdbTables` before a single predicate runs. Verified end to end against a real saved query in a real LibreOffice-generated `.odb`: `src/odb/sql/query.test.ts` reads `form-and-report.odb`'s own `HighValueSales` command out of the package via `readOdbInventory` (rather than restating it), runs it against the same package's real six-row `SALES` table decoded by the Tier 3 Firebird reader, and asserts the exact four surviving rows in the exact order its three-term mixed-direction `ORDER BY` demands.
|
|
@@ -777,7 +784,8 @@ Commits follow Conventional Commits (`feat:`, `fix:`, `test:`, `chore:`, …), e
|
|
|
777
784
|
- [ooxml.js](https://github.com/ExaDev/ooxml.js) — the sibling package this depends on for all docx/pptx/xlsx ⇄ JSON handling and cascade-resolved typed reading, including its own `readXlsxContent`/`buildXlsxPackage` (a `ContentDocument`-shaped xlsx reader/writer pair), consumed directly by `src/convert/convert.ts`'s `odsToXlsx`/`xlsxToOds` bridge and by `src/codecs/registry.ts`'s own xlsx content codec (which in turn drives `readDocumentMetadata`/`setDocumentMetadata`/`buildDocumentBytes`) but not re-exported from this package's own public surface.
|
|
778
785
|
- [document-schema.js](https://github.com/ExaDev/document-schema.js) — the sibling package that owns `ContentDocument`/`LayoutDocument` themselves; `ooxml.js`, `odf.js`, `pdf-codec`, `markdown-codec`, and `documents.js` all import from it rather than each maintaining an independent copy.
|
|
779
786
|
- [markdown-codec](https://github.com/ExaDev/markdown-codec) — the sibling package this depends on for CommonMark+GFM ⇄ `ContentDocument` handling (`readMarkdown`/`writeMarkdown`), also built on `document-schema.js`. A dependency of `documents.js` for: this package's `MarkdownBytesSchema` (`src/model/bytes.ts`), which checks well-formed UTF-8 the same way that package's own `MarkdownBytesSchema` does; `src/markdown/read.ts`'s `readMarkdownContent`, a thin adapter over `markdown-codec`'s own `readMarkdown`, feeding `markdownToPdf`/`pdfToMarkdown` and the `markdownToDocx`/`markdownToOdt` bridges (`src/convert/convert.ts`); `src/markdown/write.ts`'s `buildMarkdownText`, the same adapter over `markdown-codec`'s own `writeMarkdown`, feeding `pdfToMarkdown` and the `docxToMarkdown`/`odtToMarkdown` bridges. markdown is the third format (after docx and odt) proven to share the `wordprocessing` `ContentDocument` variant and its layout engine.
|
|
780
|
-
- [pdf-codec](https://github.com/ExaDev/pdf-codec) — the sibling package this depends on for the hand-written PDF codec itself (`readPdf`/`writePdf`/`pdfCodec`), extracted from this repository: parsing arbitrary real-world PDFs and generating new ones, the embedded STIX Two Math font, and the text-measurement/font-resolution
|
|
787
|
+
- [pdf-codec](https://github.com/ExaDev/pdf-codec) — the sibling package this depends on for the hand-written PDF codec itself (`readPdf`/`writePdf`/`pdfCodec`), extracted from this repository: parsing arbitrary real-world PDFs and generating new ones, the embedded STIX Two Math font, and the text-measurement/font-resolution primitives `src/convert/convert.ts` consumes (injected into the layout engines as ports). See [Architecture](#architecture) above for exactly where the boundary between the two packages sits, and pdf-codec's own README for its internals.
|
|
788
|
+
- [byte-codec](https://github.com/ExaDev/byte-codec) — the sibling package this depends on for generic byte/image utilities (ByteWriter, CRC-32, deflate/inflate, PNG encode/decode, JPEG header reading) — pure code with zero PDF knowledge, extracted from pdf-codec's own `src/bytes/`+`src/image/` subgraph. Both pdf-codec and documents.js consume them from this neutral home.
|
|
781
789
|
- [odf.js](https://github.com/ExaDev/odf.js) — a sibling package doing the equivalent lossless-codec job for the OpenDocument Format (odt/ods/odp/odg/…), also built on `document-schema.js`. A dependency of `documents.js` for: this package's `Odt`/`Ods`/`Odp`/`OdgBytesSchema` (`src/model/bytes.ts`), which validate against its `ODF_MEDIA_TYPES` table; `src/interop.test.ts`, a type-level guard that `ooxml.js`'s and `odf.js`'s raw `XmlElement`/`XmlNode`/`Attribute`/`Package` container types stay structurally compatible; `src/odf/odt/read.ts`'s `readOdtContent`, a thin adapter over `odf.js`'s own `readOdt`, feeding `odtToPdf`/`pdfToOdt` (`src/convert/convert.ts`); `src/odf/odp/read.ts`'s `readOdpContent`, the same adapter over `odf.js`'s `readOdp`, feeding `odpToPdf`/`pdfToOdp`; `src/odf/ods/read.ts`'s `readOdsContent`, the same adapter over `odf.js`'s `readOds`, feeding `odsToPdf`/`pdfToOds`, and reused directly by `src/edit/ods/print-settings.ts`'s own `readSheetPrintSettings` (`findStyleElement`/`resolvePageLayoutProperties`/`parsePageSize`/`parseMargins`, the same style-chain-resolution primitives `readOds`'s own `readPrintSettings` is built on); `src/odf/odg/read.ts`'s `readOdgContent`, the same adapter over `odf.js`'s `readOdg` — including its own `typed/shared/path.ts`, the real-LibreOffice-output-verified `svg:d`/`draw:points` parser this package's `writePath` content is ultimately sourced from, and which `src/edit/odg/svg-path.ts`'s `buildSvgPathData` (the write-side inverse) also cross-checks its own output against directly — feeding `odgToPdf`/`pdfToOdg` (the latter re-reading a rebuilt package's own real geometry through this same `readOdg`, not just writing one); `src/odf/formula/read.ts`'s `readOdfFormulaContent`/`readOdfEmbeddedFormula`, thin adapters over `odf.js`'s own `readOdfFormula`, feeding `odfToPdf` and the odt/odp embedded-formula paths respectively; `src/edit/odt/*`'s `StyleRegistry`/`resolveStyle` (style interning), `src/edit/odp/shape.ts`'s `applyOdfTransform`/`resolveOdfShapeGeometry` (rotation), and `src/edit/odt/automatic-styles.ts`'s `ensureAutomaticStyles`/`nextStyleName` (reused by `src/edit/odg/style.ts`'s own graphic-family style writer and `src/edit/ods/print-settings.ts`'s own page-layout/master-page/table-style minting), all consumed directly rather than reimplemented. odt, odp, ods, and odg → `ContentDocument` reading and PDF conversion are now all integrated both ways.
|
|
782
790
|
- [STIX Two Math](https://github.com/stipub/stixfonts) — the embedded math font `odfToPdf` (and the odt/odp embedded-formula paths) render through. Vendored, parsed, and embedded entirely within `pdf-codec` now (this repository no longer carries the font asset directly) — see that package's own README for the exact source commit/version and licensing (OFL-1.1) provenance.
|
|
783
791
|
- [firebirdsql/firebird](https://github.com/FirebirdSQL/firebird) — the ground truth `src/firebird/` is built against, since Firebird's own gbak backup format has no ratified public specification: `src/burp/burp.h` (the `rec_type`/`att_type` enumerations and their own per-block numbering, and the backup-format version history), `src/burp/backup.epp`/`restore.epp` (the write/read reference implementation `src/firebird/reader.ts`'s attribute framing and RLE decompression are restated from), `src/burp/canonical.cpp` (the per-SQL-type XDR shape a row's own field values use), `src/burp/mvol.cpp` (the backup-header attributes and their own presence-means-true encoding), `src/common/xdr.cpp` (the underlying big-endian XDR primitive encodings, including the `xdr_hyper` high-word-first ordering this reader's own construction initially got backwards), `src/jrd/align.h`/`src/include/firebird/impl/blr.h` (the BLR-type-opcode-to-physical-storage-type mapping), and `src/common/classes/NoThrowTimeStamp.cpp` (the DATE/TIME encoding algorithms). Not a dependency of this package at build or runtime — read and cited as source material only, per commit state at the time `src/firebird/` was built.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "js.documents",
|
|
3
|
-
"version": "1.92.
|
|
3
|
+
"version": "1.92.3",
|
|
4
4
|
"description": "Bidirectional docx/pptx <-> PDF conversion and a read+write editable OOXML document model, built on ooxml.js and Zod 4 codecs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -59,11 +59,11 @@
|
|
|
59
59
|
"license": "MIT",
|
|
60
60
|
"dependencies": {
|
|
61
61
|
"byte-codec": "^1.0.2",
|
|
62
|
-
"document-schema.js": "^2.4.
|
|
62
|
+
"document-schema.js": "^2.4.2",
|
|
63
63
|
"fflate": "^0.8.3",
|
|
64
|
-
"markdown-codec": "^1.1.
|
|
65
|
-
"odf.js": "^2.4.
|
|
66
|
-
"ooxml.js": "^2.8.
|
|
64
|
+
"markdown-codec": "^1.1.12",
|
|
65
|
+
"odf.js": "^2.4.5",
|
|
66
|
+
"ooxml.js": "^2.8.4",
|
|
67
67
|
"pdf-codec": "^2.0.0",
|
|
68
68
|
"zod": "^4.4.3"
|
|
69
69
|
},
|