js.documents 1.64.0 → 1.66.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.
- package/README.md +20 -11
- package/dist/color-C98dn-pT.d.ts +2 -0
- package/dist/edit/docx/content.cjs +5 -0
- package/dist/edit/docx/content.js +5 -0
- package/dist/edit/docx/image.cjs +1 -0
- package/dist/edit/docx/image.d.cts +2 -2
- package/dist/edit/docx/image.d.ts +2 -2
- package/dist/edit/docx/image.js +1 -1
- package/dist/edit/docx/paragraph.cjs +10 -1
- package/dist/edit/docx/paragraph.d.cts +4 -3
- package/dist/edit/docx/paragraph.d.ts +4 -3
- package/dist/edit/docx/paragraph.js +13 -4
- package/dist/edit/docx/props.d.ts +1 -1
- package/dist/edit/docx/run.cjs +8 -1
- package/dist/edit/docx/run.d.cts +2 -2
- package/dist/edit/docx/run.d.ts +2 -2
- package/dist/edit/docx/run.js +8 -2
- package/dist/edit/docx/vector.cjs +69 -0
- package/dist/edit/docx/vector.d.cts +6 -0
- package/dist/edit/docx/vector.d.ts +6 -0
- package/dist/edit/docx/vector.js +68 -0
- package/dist/edit/drawingml/vector.cjs +113 -0
- package/dist/edit/drawingml/vector.d.cts +9 -0
- package/dist/edit/drawingml/vector.d.ts +9 -0
- package/dist/edit/drawingml/vector.js +109 -0
- package/dist/edit/odg/content.cjs +1 -26
- package/dist/edit/odg/content.js +1 -26
- package/dist/edit/odg/page.cjs +14 -1
- package/dist/edit/odg/page.d.cts +5 -3
- package/dist/edit/odg/page.d.ts +5 -3
- package/dist/edit/odg/page.js +14 -1
- package/dist/edit/odg/style.cjs +17 -3
- package/dist/edit/odg/style.d.cts +1 -0
- package/dist/edit/odg/style.d.ts +1 -0
- package/dist/edit/odg/style.js +17 -3
- package/dist/edit/odg/vector.cjs +65 -3
- package/dist/edit/odg/vector.d.cts +2 -2
- package/dist/edit/odg/vector.d.ts +2 -2
- package/dist/edit/odg/vector.js +63 -4
- package/dist/edit/odp/content.cjs +4 -1
- package/dist/edit/odp/content.js +5 -2
- package/dist/edit/odp/shape.cjs +1 -1
- package/dist/edit/odp/shape.d.cts +1 -1
- package/dist/edit/odp/shape.d.ts +1 -1
- package/dist/edit/odp/shape.js +1 -1
- package/dist/edit/odp/slide.cjs +4 -0
- package/dist/edit/odp/slide.d.cts +8 -5
- package/dist/edit/odp/slide.d.ts +9 -6
- package/dist/edit/odp/slide.js +4 -0
- package/dist/edit/odt/content.cjs +5 -0
- package/dist/edit/odt/content.d.ts +2 -2
- package/dist/edit/odt/content.js +5 -0
- package/dist/edit/odt/editor.cjs +7 -0
- package/dist/edit/odt/editor.d.cts +3 -2
- package/dist/edit/odt/editor.d.ts +5 -4
- package/dist/edit/odt/editor.js +7 -0
- package/dist/edit/odt/list.cjs +13 -0
- package/dist/edit/odt/list.d.cts +1 -1
- package/dist/edit/odt/list.d.ts +1 -1
- package/dist/edit/odt/list.js +13 -0
- package/dist/edit/odt/paragraph.d.ts +1 -1
- package/dist/edit/odt/run.d.ts +1 -1
- package/dist/edit/odt/table.d.ts +1 -1
- package/dist/edit/pptx/content.cjs +5 -2
- package/dist/edit/pptx/content.js +5 -2
- package/dist/edit/pptx/shape.cjs +2 -5
- package/dist/edit/pptx/shape.d.ts +1 -1
- package/dist/edit/pptx/shape.js +2 -5
- package/dist/edit/pptx/slide.cjs +7 -0
- package/dist/edit/pptx/slide.d.cts +6 -4
- package/dist/edit/pptx/slide.d.ts +8 -6
- package/dist/edit/pptx/slide.js +7 -0
- package/dist/edit/pptx/table.d.ts +1 -1
- package/dist/edit/pptx/vector.cjs +17 -0
- package/dist/edit/pptx/vector.d.cts +6 -0
- package/dist/edit/pptx/vector.d.ts +6 -0
- package/dist/edit/pptx/vector.js +16 -0
- package/dist/{image-BfRLAD9B.d.cts → image-DRqXQmEg.d.cts} +2 -1
- package/dist/{image-BfRLAD9B.d.ts → image-DRqXQmEg.d.ts} +2 -1
- package/dist/index.cjs +2 -2
- package/dist/index.d.cts +7 -7
- package/dist/index.d.ts +13 -13
- package/dist/index.js +2 -2
- package/dist/layout/shared.cjs +1 -0
- package/dist/layout/shared.js +1 -0
- package/dist/{list-BuBM9rwQ.d.cts → list-BzcN-LTJ.d.cts} +3 -0
- package/dist/{list-CvKxhy3y.d.ts → list-nlhN5bat.d.ts} +4 -1
- package/dist/markdown/write.cjs +1 -1
- package/dist/markdown/write.js +1 -1
- package/dist/mathml/compose.cjs +8 -0
- package/dist/mathml/compose.js +8 -0
- package/dist/mathml/layout-types.d.cts +14 -2
- package/dist/mathml/layout-types.d.ts +14 -2
- package/dist/mathml/layout.cjs +62 -3
- package/dist/mathml/layout.js +62 -3
- package/dist/mathml/metrics.d.cts +17 -1
- package/dist/mathml/metrics.d.ts +17 -1
- package/dist/mathml/operators.cjs +1 -1
- package/dist/mathml/operators.js +1 -1
- package/dist/model/color.d.ts +1 -1
- package/dist/model/embedded-drawing.cjs +47 -0
- package/dist/model/embedded-drawing.d.cts +4 -2
- package/dist/model/embedded-drawing.d.ts +4 -2
- package/dist/model/embedded-drawing.js +46 -1
- package/dist/{paragraph-CoUi_88j.d.ts → paragraph-DndYbWuT.d.ts} +1 -1
- package/dist/{run-D2rTWHbt.d.ts → run-BhYv2wJf.d.ts} +3 -2
- package/dist/{run-CJfh8rv3.d.ts → run-DtrwZwN6.d.ts} +1 -1
- package/dist/{run-B3GNScJH.d.cts → run-P0b2pDdX.d.cts} +2 -1
- package/dist/{shape-BkVcwPcf.d.ts → shape-B-TjU_Ej.d.ts} +1 -1
- package/dist/{shape-CXZ6fejV.d.cts → shape-C8qDhn0l.d.cts} +1 -1
- package/dist/{shape-CVXedeL7.d.ts → shape-XGp__l-J.d.ts} +2 -2
- package/dist/{table-DqfqMkVq.d.ts → table-BHeUmLzZ.d.ts} +1 -1
- package/dist/{table-DvS_7UQS.d.ts → table-DQpKafyl.d.ts} +1 -1
- package/dist/{vector-Puuqu_IT.d.cts → vector-D03KX8Ux.d.cts} +15 -2
- package/dist/{vector-Puuqu_IT.d.ts → vector-D03KX8Ux.d.ts} +15 -2
- package/package.json +3 -3
- package/dist/color-Bo228AY2.d.ts +0 -2
package/README.md
CHANGED
|
@@ -190,7 +190,7 @@ console.log(Object.values(numbering)[0]?.levels['0']?.format); // numbering is k
|
|
|
190
190
|
|
|
191
191
|
`openPptx`/`createPptx` and `PptxSlide`/`PptxShape` are the pptx equivalent (`slide.addTextBox`, `slide.addImage`, `shape.setParagraphs` for multi-paragraph styled text).
|
|
192
192
|
|
|
193
|
-
`openOdt`/`createOdt` and `OdtParagraph`/`OdtRun`/`OdtTable`/`OdtList` are the odt equivalent, built on ODF's own style-name-referencing model (`run.bold = true` interns or reuses a named `style:style` in `office:automatic-styles`, rather than writing an inline attribute — see [Conventions](#conventions) below). `editor.body.appendFormula(formula, frame)` writes a real embedded formula: a whole nested ODF formula sub-document inside the same package, referenced from a `draw:frame`/`draw:object`, which is how ODF embeds a formula at all (see [Architecture](#architecture)'s `src/odf-package/` entry) — the odt counterpart to `DocxParagraph.appendOfficeMath`. `openOdp`/`createOdp` and `OdpSlide`/`OdpShape` are the odp equivalent of `PptxSlide`/`PptxShape` (`slide.addTextBox`, `slide.addImage`, `slide.notes`), and reuse `OdtParagraph`/`OdtRun`/`OdtList` directly for a shape's own text content — a `draw:frame`'s `draw:text-box` holds the identical `text:p`/`text:span` model `office:text` does, interned into the same `content.xml` style registry:
|
|
193
|
+
`openOdt`/`createOdt` and `OdtParagraph`/`OdtRun`/`OdtTable`/`OdtList` are the odt equivalent, built on ODF's own style-name-referencing model (`run.bold = true` interns or reuses a named `style:style` in `office:automatic-styles`, rather than writing an inline attribute — see [Conventions](#conventions) below). A list item reads back as well as appends: `OdtListItem.paragraphs()` and `.nestedLists()` return live views on its own `text:p` children and any `text:list` nested inside it (the read counterparts to `appendParagraph`/`addNestedList`), and `.text` is those paragraphs newline-joined, matching `OdtTableCell.text`/`OdpShape.text`'s own convention — a nested list's text belongs to that list's own items, not to the item containing it, since ODF nests lists structurally rather than flagging membership per paragraph. `editor.body.appendFormula(formula, frame)` writes a real embedded formula: a whole nested ODF formula sub-document inside the same package, referenced from a `draw:frame`/`draw:object`, which is how ODF embeds a formula at all (see [Architecture](#architecture)'s `src/odf-package/` entry) — the odt counterpart to `DocxParagraph.appendOfficeMath`. `openOdp`/`createOdp` and `OdpSlide`/`OdpShape` are the odp equivalent of `PptxSlide`/`PptxShape` (`slide.addTextBox`, `slide.addImage`, `slide.notes`), and reuse `OdtParagraph`/`OdtRun`/`OdtList` directly for a shape's own text content — a `draw:frame`'s `draw:text-box` holds the identical `text:p`/`text:span` model `office:text` does, interned into the same `content.xml` style registry:
|
|
194
194
|
|
|
195
195
|
```ts
|
|
196
196
|
import { createOdp } from 'documents.js';
|
|
@@ -220,7 +220,7 @@ sheet.cell(500, 50).value = { kind: 'boolean', value: true }; // does not materi
|
|
|
220
220
|
const bytes = editor.toBytes();
|
|
221
221
|
```
|
|
222
222
|
|
|
223
|
-
`createOdg`/`openOdg` and `OdgEditor`/`OdgPage` are the drawing equivalent — a page-level container (`draw:page`), extended with the vector-primitive setters a drawing carries that a presentation typically doesn't. `OdgPage.addTextBox`/`.addImage` return real `OdpShape` instances (draw:frame's content model is byte-for-byte identical between odp and odg — see [Architecture](#architecture)); `addRect`/`addEllipse`/`addLine`/`addPath` return `OdgBoxVector`/`OdgLineVector`/`OdgPathVector`, writing real `draw:rect`/`draw:ellipse`/`draw:line`/`draw:path` elements. A vector's own paint order is purely document order — the same convention real LibreOffice output already uses, so an earlier `add*` call paints behind a later one, with no `draw:z-index` attribute ever written.
|
|
223
|
+
`createOdg`/`openOdg` and `OdgEditor`/`OdgPage` are the drawing equivalent — a page-level container (`draw:page`), extended with the vector-primitive setters a drawing carries that a presentation typically doesn't. `OdgPage.addTextBox`/`.addImage` return real `OdpShape` instances (draw:frame's content model is byte-for-byte identical between odp and odg — see [Architecture](#architecture)); `addRect`/`addEllipse`/`addLine`/`addPath` return `OdgBoxVector`/`OdgLineVector`/`OdgPathVector`, writing real `draw:rect`/`draw:ellipse`/`draw:line`/`draw:path` elements — `addPath` takes whatever `ContentSubpath[]` the caller passes, lines and cubics both, with no fixed or preset shape vocabulary of its own. `OdgPage.vectors()` is the read counterpart to those four (`shapes()` is the counterpart to `addTextBox`/`addImage`): it returns a live handle on every vector already on the page, in paint order, as an `OdgVector` union discriminated on `kind` (`'rect'`/`'ellipse'`/`'line'`/`'path'`, the same vocabulary `ContentVector` uses) — so a vector's fill, stroke, frame, and rotation stay editable long after the `add*` call that created it, exactly like every other live view in this package. A vector's own paint order is purely document order — the same convention real LibreOffice output already uses, so an earlier `add*` call paints behind a later one, with no `draw:z-index` attribute ever written.
|
|
224
224
|
|
|
225
225
|
```ts
|
|
226
226
|
import { createOdg } from 'documents.js';
|
|
@@ -488,14 +488,14 @@ Two honest limits, both structural rather than provisional. An embedded face is
|
|
|
488
488
|
|
|
489
489
|
The package is layered from generic primitives outward to the two conversion directions:
|
|
490
490
|
|
|
491
|
-
- **`src/model/`** — thin, documents.js-specific additions on top of the sibling [`document-schema.js`](https://github.com/ExaDev/document-schema.js) package, which now owns the two pivot models themselves: `LayoutDocument` (the PDF-side pivot: pages of positioned text/image/rect/line/ellipse/path/link items, PDF-native coordinates and units — `LayoutPath` is a general vector path, one or more subpaths of line/cubic segments sharing one fill/fillRule/stroke, the item kind `writePath`, pdf-codec's own content-write.ts, turns into PDF `m`/`l`/`c`/`h` content-stream operators) and `ContentDocument` (the semantic pivot: a discriminated union of `wordprocessing`, `presentation`, `spreadsheet`, `drawing`, and `formula` variants — the first four sharing paragraph/run/table/image building blocks, `drawing`'s own `ContentVector` vocabulary — rect/ellipse/line/path — being the vector-primitive counterpart to the shared `ContentShape`, and `formula` carrying a real MathML tree rather than any of them) are both imported, not defined here — `document-schema.js` exists specifically so `ooxml.js`, `odf.js`, `pdf-codec`, and `documents.js` share one schema instead of each maintaining an independent, drift-prone copy. What remains local: `bytes.ts` (magic-byte-validated `Uint8Array` schemas for docx/pptx/PDF, plus `Odt`/`Ods`/`Odp`/`OdgBytesSchema`, which check the package's actual declared media type against `odf.js`'s `ODF_MEDIA_TYPES` table rather than only the generic ZIP signature the OOXML schemas are limited to), `units.ts` (OOXML EMU/twip/point/half-point conversions), and `geometry.ts`/`color.ts`/`style.ts`, each now mostly a thin re-export of `document-schema.js`'s `Box`/`Margins`/`PageSize`/`Color`/`Alignment`/`LayoutFont` — the one genuinely PDF-specific piece each still adds locally is `geometry.ts`'s `flipY` (the top-left/y-down ↔ bottom-left/y-up space conversion between OOXML/ODF and PDF coordinates); `LayoutFont`/`DEFAULT_LAYOUT_FONT` moved to `document-schema.js` too (since `LayoutText`, part of the pivot, needs the field), leaving only the standard-14 font *resolution* logic that consumes it (pdf-codec's own `fonts.ts`/`font-read.ts`) as PDF-specific, now external to this package entirely. `ContentDocument`/`ContentDocumentSchema`/`CONTENT_FORMAT_VERSION` themselves have no local file at all any more — every consumer imports them directly from `document-schema.js`, which owns the envelope as well as everything it wraps. `paint-order.ts`'s `mergeByPaintOrder` merges a drawing page's two arrays (`shapes`, `vectors`) back into one true-paint-order walk through the shared `paintOrder` field both carry; it lives here rather than beside either caller because `src/layout/drawing.ts` and `src/edit/odg/content.ts` both need the identical merge and `src/layout/*` deliberately imports no `odf.js`/`edit` code. `formula.ts` holds the small helpers around document-schema.js's own `ContentFormula`: the `'formula'`-kind `ContentDocument` envelope, the `ContentEmbeddedObjectBlock` an odt/odp reader produces for an inline formula, the narrowing back out of such a block, and the plain-text stand-in (`formulaPlaceholderText`) every consumer that cannot typeset MathML writes instead. It declares no formula type of its own — the side-channel `EmbeddedFormula` it used to define is gone, replaced by the real schema type. `PositionedFormula` (the equivalent side-channel shape for a `LayoutDocument`) now lives in `pdf-codec` itself, which redeclares its own structurally-identical copy of it and of `MathBox` — see [Architecture](#architecture) below and pdf-codec's own README for why a real `MathBox` this package's `layoutFormula` produces crosses that package boundary with zero cast or wrapper.
|
|
491
|
+
- **`src/model/`** — thin, documents.js-specific additions on top of the sibling [`document-schema.js`](https://github.com/ExaDev/document-schema.js) package, which now owns the two pivot models themselves: `LayoutDocument` (the PDF-side pivot: pages of positioned text/image/rect/line/ellipse/path/link items, PDF-native coordinates and units — `LayoutPath` is a general vector path, one or more subpaths of line/cubic segments sharing one fill/fillRule/stroke, the item kind `writePath`, pdf-codec's own content-write.ts, turns into PDF `m`/`l`/`c`/`h` content-stream operators) and `ContentDocument` (the semantic pivot: a discriminated union of `wordprocessing`, `presentation`, `spreadsheet`, `drawing`, and `formula` variants — the first four sharing paragraph/run/table/image building blocks, `drawing`'s own `ContentVector` vocabulary — rect/ellipse/line/path — being the vector-primitive counterpart to the shared `ContentShape`, and `formula` carrying a real MathML tree rather than any of them) are both imported, not defined here — `document-schema.js` exists specifically so `ooxml.js`, `odf.js`, `pdf-codec`, and `documents.js` share one schema instead of each maintaining an independent, drift-prone copy. What remains local: `bytes.ts` (magic-byte-validated `Uint8Array` schemas for docx/pptx/PDF, plus `Odt`/`Ods`/`Odp`/`OdgBytesSchema`, which check the package's actual declared media type against `odf.js`'s `ODF_MEDIA_TYPES` table rather than only the generic ZIP signature the OOXML schemas are limited to), `units.ts` (OOXML EMU/twip/point/half-point conversions), and `geometry.ts`/`color.ts`/`style.ts`, each now mostly a thin re-export of `document-schema.js`'s `Box`/`Margins`/`PageSize`/`Color`/`Alignment`/`LayoutFont` — the one genuinely PDF-specific piece each still adds locally is `geometry.ts`'s `flipY` (the top-left/y-down ↔ bottom-left/y-up space conversion between OOXML/ODF and PDF coordinates); `LayoutFont`/`DEFAULT_LAYOUT_FONT` moved to `document-schema.js` too (since `LayoutText`, part of the pivot, needs the field), leaving only the standard-14 font *resolution* logic that consumes it (pdf-codec's own `fonts.ts`/`font-read.ts`) as PDF-specific, now external to this package entirely. `ContentDocument`/`ContentDocumentSchema`/`CONTENT_FORMAT_VERSION` themselves have no local file at all any more — every consumer imports them directly from `document-schema.js`, which owns the envelope as well as everything it wraps. `paint-order.ts`'s `mergeByPaintOrder` merges a drawing page's two arrays (`shapes`, `vectors`) back into one true-paint-order walk through the shared `paintOrder` field both carry; it lives here rather than beside either caller because `src/layout/drawing.ts` and `src/edit/odg/content.ts` both need the identical merge and `src/layout/*` deliberately imports no `odf.js`/`edit` code. `formula.ts` holds the small helpers around document-schema.js's own `ContentFormula`: the `'formula'`-kind `ContentDocument` envelope, the `ContentEmbeddedObjectBlock` an odt/odp reader produces for an inline formula, the narrowing back out of such a block, and the plain-text stand-in (`formulaPlaceholderText`) every consumer that cannot typeset MathML writes instead. It declares no formula type of its own — the side-channel `EmbeddedFormula` it used to define is gone, replaced by the real schema type. `PositionedFormula` (the equivalent side-channel shape for a `LayoutDocument`) now lives in `pdf-codec` itself, which redeclares its own structurally-identical copy of it and of `MathBox` — see [Architecture](#architecture) below and pdf-codec's own README for why a real `MathBox` this package's `layoutFormula` produces crosses that package boundary with zero cast or wrapper. `embedded-drawing.ts` is `formula.ts`'s exact counterpart for the `'drawing'` `objectKind`: `buildDrawingBlock` packages a page's recovered `ContentVector`s as a `ContentEmbeddedObjectBlock` carrying a one-page drawing `ContentDocument` (the only container in the shared schema with a `vectors` array at all, which is why a recovered rect lives there rather than directly in `ContentSection.blocks`/`ContentSlide.shapes`), `drawingOfBlock` narrows back out of one, and `embeddedDrawingVectors` flattens a block's vectors into whatever coordinate space the container about to write them uses — translating by the block's own frame plus the container's, never scaling.
|
|
492
492
|
- **The hand-written PDF codec, and the generic byte/image primitives it depends on, are now the external [`pdf-codec`](https://github.com/ExaDev/pdf-codec) dependency** rather than local `src/pdf/`/`src/bytes/`/`src/image/` directories — see that package's own README for its internal architecture (the object model, cross-reference handling, content-stream interpreter, standard-14 font resolution, the embedded math-font writer, and the generic byte/PNG/JPEG primitives it exports for a layout engine like this package's own `src/layout/` to build on).
|
|
493
493
|
- **`src/ports/`** — the injectable ports this package's own "identity, clock, and observability are first-class ports" convention calls for: `abort.ts`'s `throwIfAborted` (a signal-check helper called at row loop boundaries in `src/layout/sheets.ts`/`reconstruct.ts` — the codebase has no `await` point for cancellation to hook into implicitly, since the local pipeline is synchronous end to end, so every long-running loop checks explicitly instead; `pdf-codec` needed the identical helper for its own page loops and now carries its own independently-duplicated copy rather than depending on this package for it) and `clock.ts`'s `ClockPort`/`systemClock`/`fixedClock` (an injectable "now", for deterministic PDF output in tests). `ClockPort` is exported and tested in isolation but not yet consumed by any conversion path — `writePdf`'s own `/CreationDate`/`/ModDate` come directly from `LayoutDocument.metadata.createdIso`/`modifiedIso` when present, with nothing in pdf-codec's own write path calling `new Date()` to fill in a missing one, so there is currently no real call site for `ClockPort` to inject into. A real, tracked gap in wiring, not a documentation gap: a future default-timestamp write path should consume it rather than reaching for `new Date()` directly.
|
|
494
494
|
- **`src/xml/`** and **`src/opc/`** — parent-aware XML query/mutation and OPC package mechanics (relationship IDs, content-type entries, atomic media-part insertion) built over `ooxml.js`'s `Package`/`XmlNode`, needed because `ooxml.js`'s own XML nodes have no parent pointers and `ooxml.js` never writes new parts into an existing package. `src/xml/odf-text.ts` is the one ODF-specific module in this directory: `encodeOdfText`/`decodeOdfText` convert between a plain string and ODF's own whitespace-run element sequence (`text:s` for a run of two or more literal spaces, `text:tab`, `text:line-break` — all three occupy real character positions in an ODF paragraph but are ELEMENTS, not text-node characters, unlike docx's flat `w:t` run text) — see the Gotchas entry below on why every ODF text getter in this codebase must call `decodeOdfText`, never `ooxml.js`'s own plain-text-node `textContent()`.
|
|
495
495
|
- **`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.
|
|
496
|
-
- **`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).
|
|
496
|
+
- **`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.
|
|
497
497
|
- **`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.
|
|
498
|
-
- **`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 (not even `document-schema.js`), matching `src/layout/`'s own "pure conversion algorithm" isolation one tier further down. `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, a hand-drawn hooked radical sign built from line segments rather than a bare glyph substitute, MathML length-unit parsing). Output is a flat `MathBox` (positioned glyph runs, rules, and
|
|
498
|
+
- **`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 (not even `document-schema.js`), matching `src/layout/`'s own "pure conversion algorithm" isolation one tier further down. `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, a hand-drawn hooked radical sign built from line segments rather than a bare glyph substitute, 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.
|
|
499
499
|
- **`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.
|
|
500
500
|
- **`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.
|
|
501
501
|
- **`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 (see below) 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.
|
|
@@ -551,7 +551,10 @@ To run a single test file: `pnpm vitest run src/path/to/file.test.ts`.
|
|
|
551
551
|
- **`OdpShape.rotationDeg` writes a real `draw:transform`, built on `odf.js`'s own transform machinery.** It is the write-side inverse of `odf.js`'s `resolveOdfShapeGeometry` (`typed/shared/transform.ts`), built on that module's own exported `applyOdfTransform` rather than a hand-rolled rotation matrix, so it inherits that module's own empirically-verified rotate/translate composition order and sign convention by construction. `buildOdpPackage` writes a rotated shape's rotation back correctly — verified both by this package's own tests and by opening a fresh, editor-built `.odp` in actual LibreOffice. `PptxShape.rotationDeg` (see the `src/edit/` Architecture entry above) is the DrawingML-side counterpart, and a rotated shape now round-trips through `odpToPptx`/`pptxToOdp` both ways too (`src/convert/bridges.test.ts`'s own dedicated rotation test).
|
|
552
552
|
- **`readPdf` recovers a rect, an ellipse, and a line as their own `LayoutRect`/`LayoutEllipse`/`LayoutLine` kinds, not merely as generic paths — pdf-codec's own shape-pattern detection, and the reason every vector kind now survives a `odgToPdf` → `pdfToOdg` round trip.** PDF has exactly one shape operator (`re`, itself defined as a four-point rectangle subpath) and no ellipse or line operator at all, so a writer has no way to record what a path *was*; pdf-codec recovers it from the geometry instead — an axis-aligned closed four-corner subpath is a `LayoutRect` (any combination of fill and stroke, and a 90°-rotated CTM as well as an unrotated one), a closed subpath of four cubic segments meeting its bounding box at the four cardinal points with kappa-ratio control points is a `LayoutEllipse`, and an open single-straight-segment stroke-only subpath is a `LayoutLine`. See pdf-codec's own README for the tolerances and the honest caveat that these are bounded heuristics: a false positive can change an item's *kind*, never its geometry. What still narrows to a generic `LayoutPath`: an off-axis rotation, a freeform curve, a multi-subpath figure. A practical consequence for `pdfToOds`: a gridline written by `sheets.ts`'s own `renderGridlines` now comes back as a real `LayoutLine`, but `reconstructSpreadsheet`'s lattice detection still accepts the stroked-single-segment `LayoutPath` shape too, so a hand-built `LayoutDocument` and one from a producer other than `readPdf` detect identically.
|
|
553
553
|
- **`pdfToOds` re-types a recovered cell heuristically, and this is explicitly PROBABILISTIC BEST-EFFORT RECOVERY, not a fidelity guarantee.** A rendered PDF genuinely never carries a spreadsheet cell's own typed value — a page holds only the string the authoring application chose to print — so every re-typed value below is an inference from that string alone, and a string that looks exactly like a number may genuinely have *been* a string in the source spreadsheet (a part number, a version, a phone extension). Nothing in this package can tell those apart with certainty, and no further heuristic would change that. What is guaranteed: `ContentSheetCell.displayText` is a required field carrying the rendered string verbatim regardless of what was inferred from it, so the printed form is never lost. `src/layout/cell-typing.ts` re-types only where the string has exactly **one** defensible reading, which resolves to four concrete requirements: the decimal must be exactly representable as a JS number (checked by round-tripping it, not by a digit-count limit — this is what keeps a 19-digit barcode a string); the separators must be unambiguous (`.` reads as the decimal separator and `,` as grouping, but a lone comma group like `"1,234"` is **declined**, since the competing European reading of the identical string is 1.234, a thousandfold error — `"1,234,567"` and `"1,234.50"` have no such competing reading and are accepted); a leading zero (`"007"`, `"01.5"`) is declined outright, since a spreadsheet never prints a numeric value with one; and a date's component roles must be stated by the text itself — ISO ordering (`"2024-01-15"`) or a named month (`"15 Jan 2024"`, `"Jan 15, 2024"`) is accepted, an all-numeric separated date (`"01/02/2024"`) is declined regardless of whether one component happens to exceed 12 in that particular cell, because resolving it per cell would type one column inconsistently. `TRUE`/`FALSE` are re-typed as booleans; `Yes`/`No`/`Y`/`N`/`On`/`Off` are **declined**, since no mainstream spreadsheet prints a boolean that way by default, so a `"Yes"` cell is far more likely genuine text. Percentages recover ODF's own fraction convention (`"15%"` → `0.15`); a currency symbol names an ISO code only where it identifies exactly one (`£`→GBP, `€`→EUR; `$` and `¥` re-type as `currency` with the code left `undefined`). `'time'`/`'dateTime'`/`'error'` are deliberately out of scope, and a formula is never claimed. Two ways to tell an inferred value from an untouched one: `value.kind !== 'string'` is itself the flag, and `ReconstructOptions.onCellTypeInference` reports every decision — both a re-typing (with the rule that fired) and a deliberate refusal (with the named ambiguity), the latter being information the output alone cannot carry, since a declined cell is indistinguishable from one that was never number-shaped at all. `inferCellValue` is exported standalone for a caller who wants to replay the same decision over their own text.
|
|
554
|
-
- **`reconstructWordprocessing`/`reconstructPresentation`
|
|
554
|
+
- **`reconstructWordprocessing`/`reconstructPresentation` recover a page's vector primitives too, in a nested drawing document, and all four OOXML/ODF builders now write them out as real shapes.** Both directions used to filter each page down to its text and image items and discard every stroke and fill; they now run the same `layoutItemToVector` classification `reconstructDrawing` does (one implementation, not two) and carry the result in a `ContentEmbeddedObjectBlock` whose `objectKind` is `'drawing'` and whose nested `document` is a real one-page drawing `ContentDocument` — the shared schema's own designed mechanism, since `ContentSection.blocks` and `ContentSlide.shapes` have no vector vocabulary of their own. One honest consequence of the recovery itself: a PDF does not distinguish a stroke drawn to decorate from one drawn as structure, so a rule under a heading, an underline (pdf-codec writes one as a filled rectangle), and a table cell's background fill are all recovered as vectors — that is intended, since discarding real content because it *might* be incidental is exactly the silent loss this package's conventions rule out, but it does mean a reconstructed document carries more than its text alone. A table's own gridlines are the one case deliberately not double-counted: when the table recovery above claims a lattice, the strokes that formed it are excluded from vector recovery. On the write side, `buildDocxPackage` and `buildPptxPackage` emit real DrawingML (`a:prstGeom prst="rect"/"ellipse"/"line"`, and a genuine `a:custGeom` with `a:moveTo`/`a:lnTo`/`a:cubicBezTo`/`a:close` for a path), and `buildOdtPackage`/`buildOdpPackage` emit real `draw:rect`/`draw:ellipse`/`draw:line`/`draw:path` — see the dedicated write-side gotcha below for how each format wraps them. `pdfToMarkdown` still drops the block entirely rather than emitting a marker for it: a rect carries no text to stand in for, unlike a formula, and CommonMark has no vector construct regardless.
|
|
555
|
+
- **A recovered vector reaches the output FILE for all four formats, but not back through this package's own readers.** `buildDocxPackage`/`buildPptxPackage`/`buildOdtPackage`/`buildOdpPackage` all write real vector shapes now, so a `pdfToDocx`/`pdfToPptx`/`pdfToOdt`/`pdfToOdp` output opens in Word, PowerPoint, or LibreOffice with the recovered geometry in place. Re-reading that same file through `readDocxContent`/`readPptxContent`/`readOdtContent`/`readOdpContent` does **not** give the embedded-drawing block back: those four are thin adapters over `ooxml.js`'s `readDocx`/`readPptx` and `odf.js`'s `readOdt`/`readOdp`, none of which reads vector geometry into a `ContentDocument` at all — and for the ODF pair, `ContentSection.blocks`/`ContentSlide.shapes` have no vector vocabulary to read one into regardless. The practical consequence: the six PDF-bypassing bridges (`odtToDocx` and friends) carry no vectors across, since a bridge is `readXContent` → `buildYPackage` and the read half drops them. Closing this is reader-side work — the OOXML/ODF mirror of the second pass `src/odf/formula/detect.ts` already runs for embedded formulas — a tracked, bounded gap, not a silent one.
|
|
556
|
+
- **Each format wraps a vector shape in the construct that format actually has for one, and the two families each share one writer.** `src/edit/drawingml/vector.ts` holds everything inside a DrawingML shape-properties element (`a:xfrm`, the preset or custom geometry, `a:solidFill`/`a:noFill`, `a:ln`) — identical for docx and pptx, since `CT_ShapeProperties` is one type in both. Only the wrapper differs: pptx gets a plain `p:sp` on the slide's `p:spTree` (no `p:txBody` at all — a geometric primitive carries no text, and inventing an empty paragraph would make `readPptx` report a text shape where the source had pure geometry), while docx gets a `w:drawing`/`wp:anchor` positioned `relativeFrom="page"` on both axes, with `behindDoc="1"` and `wp:wrapNone`, carrying a `wps:wsp` inside `a:graphicData` — the wordprocessingShape extension part, the only DrawingML vocabulary WordprocessingML has for a non-picture shape (the pre-2010 alternative is VML, deprecated by ECMA-376 itself and deliberately not written). Anchoring rather than `wp:inline` is what preserves the recovered page-absolute coordinates; the honest limit is that the anchor still belongs to a paragraph, so which page the geometry lands on follows that paragraph if the document reflows differently in Word than it laid out in the source PDF. On the ODF side, `buildOdtPackage`/`buildOdpPackage` import `src/edit/odg/vector.ts`'s writer wholesale rather than reimplementing it — `draw:rect`/`draw:ellipse`/`draw:line`/`draw:path` carry byte-for-byte the same attribute vocabulary in a text document, a presentation, and a drawing, and `odf.js`'s own `readDrawPageContent` reads all three through one function. odp appends them directly to the slide's `draw:page` (a slide positions geometry against the page, exactly as a drawing page does); odt anchors them in a `text:p` of their own with `text:anchor-type="paragraph"` **plus** `style:horizontal-rel`/`style:vertical-rel="page"` in the graphic style, since a recovered vector's coordinates are page-absolute and a paragraph-relative reference would offset every shape by wherever its anchor paragraph flowed to. `style:wrap="run-through"`/`style:run-through="background"` are the ODF counterpart of docx's `wp:wrapNone`/`behindDoc="1"` pair.
|
|
557
|
+
- **`ContentStroke.style` (`solid`/`dashed`/`dotted`/`double`) is not written by any vector writer, ODF or DrawingML.** Nothing in this package produces one — `LayoutLine` and `LayoutPath` both carry a stroke of colour and width only (`document-schema.js`'s `layout.ts`), so no reconstruction path can populate it — and `a:prstDash` has no `double` member to map the fourth value onto regardless. A hand-built `ContentVector` setting it consequently paints solid. A real, bounded gap rather than an oversight.
|
|
555
558
|
- **`pdfToOds` recovers what was printed, not what was entered.** `reconstructSpreadsheet` (`src/layout/reconstruct.ts`) tries a real gridline lattice first: it scans the page's `LayoutLine`/stroked-single-segment-`LayoutPath` items (see the `interpret.ts` gotcha above) for enough parallel horizontal and vertical lines at consistent positions to call it a printed grid (`MIN_GRIDLINE_COUNT_PER_AXIS = 3` per axis, i.e. at least a 2×2 grid, and a span-consistency check that rejects a scatter of unrelated short strokes — a page border or a couple of decorative rules — as not a genuine lattice), and uses those line positions DIRECTLY as cell boundaries when found. Absent a lattice, it clusters text into a grid from geometry alone instead: rows reuse `clusterIntoLines` verbatim (a spreadsheet cell's own text is never wrapped across lines, so a text line already IS a row), and columns generalise `clusterIntoParagraphs`'s own single `dominantLeftX` to several recurring x-position anchors, first merging directly-adjacent same-line fragments (`splitLineByLargeGaps`, the same >2em-gap signal `reconstructPresentation`'s own block clustering uses) so a cell whose text arrived as several run-level-split `LayoutText` items isn't scattered across spurious columns. Column widths and row heights are genuinely measured from whichever geometry was used (drawn gridline gaps, or measured text/anchor extents), never invented. Every recovered cell always carries its own extracted `displayText` verbatim, and additionally carries a **heuristically re-typed** `value` wherever `src/layout/cell-typing.ts` finds exactly one defensible reading of that string; a formula is still never claimed. See the dedicated heuristic-re-typing gotcha below and [Fidelity](#fidelity) for the full framing. `buildOdsPackage` (`src/edit/ods/content.ts`) is `pdfToOds`'s own package-building half, mirroring `buildOdtPackage`/`buildOdpPackage`/`buildOdgPackage`'s role for `pdfToOdt`/`pdfToOdp`/`pdfToOdg`.
|
|
556
559
|
- **`OdsSheet.printSettings` (`src/edit/ods/print-settings.ts`) now round-trips every field `ContentSheetPrintSettingsSchema` carries, not just the five it started with.** `pageSize`/`margins`/`gridlines`/`headers`/`pageOrder` resolve through the `table:style-name` → `style:style[family="table"]` → `style:master-page-name` → `style:master-page` → `style:page-layout` → `style:page-layout-properties` chain (`odf.js`'s own exported `findStyleElement`/`resolvePageLayoutProperties`/`parsePageSize`/`parseMargins`); the setter mints a fresh `style:page-layout` + `style:master-page` + `style:style[family="table"]` triple and repoints the sheet's own `table:style-name` to it on every call, the same append-only style-editing convention `src/edit/odg/style.ts` already documents. The remaining, previously-unimplemented fields are now implemented too: `printRange` reads/writes `table:print-ranges` directly on `table:table`; `scalePercent`/`fitToPages` read/write `style:scale-to` and `style:scale-to-X`/`style:scale-to-Y` on the page-layout-properties element; `repeatColumns`/`repeatRows` are read via `scanTableStructure`, a scoped-down mirror of `odf.js`'s own private `readTable`'s table-wide column/row cursor tracking (the same walk that function performs before ever calling its own `readPrintSettings`), and written by moving the real `table:table-column`/`table:table-row` elements covering the given range into a fresh `table:table-header-columns`/`table:table-header-rows` wrapper; `manualBreaks` read/write `fo:break-before="page"` on the named row/column's own style. Writing `repeatRows`/`repeatColumns` required teaching `address.ts`'s row/column addressing that a row/column may now live nested one level inside a header wrapper rather than as a direct `table:table` child, so a subsequent cell/column/row write against a wrapped index finds the real element instead of creating a spurious duplicate outside it; the width/height and manual-break writers all target the same `style:table-column-properties`/`style:table-row-properties` element, so each reads the column/row's current style first and mints a fresh style carrying the merged result, rather than a naive single-property mint clobbering whatever an earlier call had already set. No known gap remains in `ContentSheetPrintSettingsSchema` coverage.
|
|
557
560
|
- **`OdsSheet` now has a real column-width/row-height setter (`setColumnWidth`/`setRowHeight`, `src/edit/ods/column-row.ts`), closing a gap that escalated from cosmetic to a genuine correctness bug once `xlsxToPdf`/`pdfToXlsx` started composing through `buildOdsPackage` internally.** `OdsSheet.cell()`'s own column/row-materialisation (`address.ts`) creates a real, explicit `table:table-column`/`table:table-row` element for any position a caller ever addresses, but previously never gave it a width/height style. This is a genuinely different failure shape from a column/row with NO element at all: `sheets.ts`'s own `resolveAxis` only falls back to `DEFAULT_COLUMN_WIDTH_PT`/`DEFAULT_ROW_HEIGHT_PT` for an index with no `ContentSheetColumn`/`ContentSheetRow` entry whatsoever — an explicit-but-unstyled element reads back at `widthPt`/`heightPt` 0 (`odf.js`'s own `resolveColumnWidthPt`/`readRowLayout`), and that explicit zero wins over the fallback. While `buildOdsPackage`'s own output was only ever a terminal deliverable (`pdfToOds`, or a caller's own `readOdsContent` round trip), this was cosmetic: a real app reopening it would use its own defaults instead of the source's. `xlsxToPdf` (`xlsxToOds` then `odsToPdf`) made it a real bug instead — the intermediate ods bytes get laid out again by `convertSpreadsheetToLayout`, and a zero-size grid collapses every cell onto the same physical position rather than merely losing precision. `setColumnWidth`/`setRowHeight` mint a fresh `style:style[family="table-column"|"table-row"]` per column/row and repoint its own `table:style-name`, the same append-only style-minting convention `writeSheetPrintSettings`/`src/edit/odg/style.ts` already establish; `buildOdsPackage` now calls both for every `ContentSheetColumn`/`ContentSheetRow` a source sheet carries. Column/row HIDDEN state and `ContentSheetImage`/formula `embeddedObjects` are no longer gaps either, closed in the same phase: `OdsSheet.setColumnHidden`/`setRowHidden` set or clear `table:visibility="collapse"` directly on the `table:table-column`/`table:table-row` element — a plain attribute, not a style property, so it never interacts with the width/height setters above — and `buildOdsPackage` calls one of these for every column/row whose `hidden` field is `true`. `OdsSheet.addImage` (`src/edit/ods/floating.ts`) writes a real floating `draw:frame`/`draw:image` into `table:shapes` (the ODF 1.3 content-model container for spreadsheet floating shapes, always `table:table`'s own first child in a package this editor builds), resolving a `ContentSheetImage`'s `anchorRow`/`anchorColumn` plus `offsetXPt`/`offsetYPt` to an absolute `svg:x`/`svg:y` by summing the real, currently-declared width/height of every column/row strictly before the anchor (header-wrapper-aware, hidden columns/rows contributing zero, falling back to the same default column/row size the layout engine assumes once the walk runs past what the sheet has declared) — reusing `addImageMedia` for the binary part and manifest entry, the same mechanism `src/edit/odp/image.ts` already uses for a slide. `OdsSheet.addEmbeddedObject` writes a real embedded ODF formula sub-document for `objectKind === 'formula'` (reusing `addFormulaObject`, the same mechanism `OdtBody.appendFormula` already uses); every other `objectKind` (`wordprocessing`/`presentation`/`spreadsheet`/`drawing`) is left unwritten, a documented, bounded gap mirroring `buildOdtPackage`'s identical narrowing for a `'drawing'` embedded object, since embedding one would mean writing that document's own package as a nested OLE sub-object, which no writer in this codebase implements. `buildOdsPackage` calls both for every sheet's images/embedded objects, after every column/row width/height/hidden call, so an image's own anchor resolves against the sheet's final, real column/row sizing. This is write-only for now: `odf.js`'s own `readOds` does not read ods floating shapes or embedded objects back at all (`images` hardcoded to `[]`, `embeddedObjects` never set — see the embedded-formula-detection gotcha below), so there is no `ContentDocument` re-read to verify a round trip against; every test verifies the real written package/XML structure directly instead, via `odf.js`'s own query/manifest/formula-reading primitives and a genuine zip encode/decode round trip.
|
|
@@ -564,7 +567,7 @@ To run a single test file: `pnpm vitest run src/path/to/file.test.ts`.
|
|
|
564
567
|
- **A drawing page's `shapes` and `vectors` are two separate arrays, but their true relative paint order is carried by a shared `paintOrder` field on both.** `ContentDrawPageSchema` (`document-schema.js`) still keeps text/image/table content (`shapes`) and vector primitives (`vectors`) apart, but `ContentVector` and `ContentShape` each carry a `paintOrder` — one monotonically increasing per-page document index `odf.js`'s own reader stamps on every element it walks (`typed/draw/shapes.ts`'s `walkDrawPageContent`/`paintOrderKey`, honouring a real `draw:z-index` where a producer wrote one, falling back to document position otherwise). `convertDrawingToLayout` merges the two arrays back into one true-paint-order walk through that field (`src/model/paint-order.ts`'s `mergeByPaintOrder`), `reconstructDrawing` stamps the same field from its own single walk over a page's recovered items, and `buildOdgPackage` appends in the same merged order (document order *is* paint order in a written `.odg` — this package never emits a `draw:z-index`). A page that genuinely interleaves the two mid-stack — a text label between two rectangles, a rectangle over a picture — consequently paints in the order its author built it, and survives `convertDrawingToLayout` → `reconstructDrawing` with that interleaving intact. The historical "every vector paints before every shape" rule survives only as the documented fallback for a page missing `paintOrder` anywhere (a hand-built `ContentDocument`, or one produced before the field existed), since an item with no value has no defensible position to be sorted into and inventing one would silently reorder content.
|
|
565
568
|
- **A rotated vector primitive renders as a `LayoutPath`, not as a rotated `LayoutRect`/`LayoutEllipse` — because neither of those carries a rotation field at all.** `ContentVectorSchema`'s `rect`/`ellipse`/`path` variants each carry a real `rotationDeg` (the `line` variant does not, and needs none — two endpoints already encode any orientation a line can have), `odf.js`'s own reader resolves one through the same `resolveOdfShapeGeometry` a `draw:frame` uses, and `OdgBoxVector.rotationDeg`/`OdgPathVector.rotationDeg` write one back through the same shared `applyOdfGeometry` (`src/edit/geometry.ts`) `OdpShape.rotationDeg` uses. `convertDrawingToLayout` resolves a rotated vector into a `LayoutPath` whose own points are the shape's corners/curve controls after rotation — a rotated rect becomes a genuine four-point closed subpath, a rotated ellipse its own four cubics rotated — since `LayoutRectSchema`/`LayoutEllipseSchema` model no rotation and only `LayoutText`/`LayoutImage` do (pdf-codec rotates those two through a text/image transformation matrix, which a path-painting operator sequence has no equivalent of). Nothing is approximated by this: an affine rotation maps a straight edge to a straight edge and a cubic Bézier to a cubic Bézier exactly. What a PDF round trip cannot preserve is the `rotationDeg` *field* — a recovered path records where the corners ended up, never that a right-angled box was turned to get there — so `pdfToOdg` returns a rotated rect as an unrotated `path` vector whose geometry is genuinely rotated, the same kind-narrowing every other vector already documents below.
|
|
566
569
|
- **`ContentVector`'s `path` variant's `fillRule` is never populated by the reader — always `undefined`, which `writePath` treats as nonzero.** `odf.js`'s `readDrawPathVector` does not currently resolve an evenodd fill rule from real ODF output, so every path this pipeline reads paints with PDF's default nonzero winding rule. `LayoutPathSchema`/`writePath` fully support `fillRule: 'evenodd'` regardless — a caller constructing a `LayoutPath` (or a future `ContentVector` producer) directly can still set it; it just never arrives via `odgToPdf` today.
|
|
567
|
-
- **A cell's declared border renders solid whatever its `style` says
|
|
570
|
+
- **A cell's declared border still renders solid whatever its `style` says — but the reason moved from "the layout schema has nowhere to carry a dash pattern" to "pdf-codec doesn't read the field yet".** `ContentSheetCellSchema` and `ContentTableCellSchema` both carry real per-cell `background`/`borders` (and `ContentSheetCellSchema` also `alignment`/`verticalAlignment`), `odf.js`'s own reader populates every one of them from a cell's resolved style chain, and `sheets.ts`/`engine.ts` render all of them: a background becomes a real `LayoutRect`, each declared border edge a real `LayoutLine`, and a cell's own alignment/vertical alignment override the value-kind default and the bottom default respectively. `ContentBorder.style` (`solid`/`dashed`/`dotted`/`double`) now genuinely reaches the `LayoutDocument`: as of `document-schema.js` 2.1.0, `LayoutLineSchema`/`LayoutPathSchema` both carry that same optional `style` enum, and `pushCellBorderLines` (`src/layout/shared.ts`, shared by both `engine.ts`'s docx/odt table-cell borders and `sheets.ts`'s ods/xlsx sheet-cell borders) sets it from `ContentBorder.style` on every emitted `LayoutLine` — the model-to-model plumbing this task closed. What still renders solid regardless is the PDF byte output: the installed `pdf-codec` 1.8.0's own `content-write.ts` `writeLine`/`writePath` always emit a plain stroke (`S`) operator with no dash array, so a non-solid `style` has nowhere to render differently yet — a real, separate, pdf-codec-side gap now, not a documents.js schema one. Rendering `double` as two hand-offset parallel lines was still considered and rejected regardless of which package's gap this is: the offset distance is nowhere in the model, so it would be an invented constant standing in for information the source never carried.
|
|
568
571
|
- **Ordinary text in PDF output now resolves through a real font registry rather than the standard 14 alone, and the standard 14 are only the last resort in that chain.** In order: the source document's own embedded faces (docx's `word/fontTable.xml`, pptx's `p:embeddedFontLst`, ODF's `office:font-face-decls` — see [Fonts](#fonts)), then any face the caller supplied through `options.fonts`, then pdf-codec's vendored Carlito/Caladea faces (genuinely metric-compatible with Calibri/Cambria, and embedded as real subsetted TrueType programs), then the standard 14. Helvetica/Times-Roman remain metric-compatible substitutes for Arial/Times New Roman, so a document asking for either still resolves to a standard font and embeds nothing. What is still not covered: a family with no embedded face, no caller-supplied face, and no vendored substitute — Aptos, say, or any third-party typeface — still renders through the nearest standard-14 face with a width-correction factor, so line wrapping and pagination will drift slightly from what Word itself would produce. Expect a faithful visual approximation there, not a line-identical reproduction. MathML formula rendering (`odfToPdf`, and formulas embedded inside odt/odp) is separate from all of this and always was: it embeds the real STIX Two Math font, which is not a registry-resolvable face and cannot be overridden by `options.fonts` — see the CFF-embedding gotcha below, and pdf-codec's own README, for the exact scope of that embedding (the whole `CFF ` table, not glyph-subsetted).
|
|
569
572
|
- **Justified paragraphs now stretch real inter-word gaps in all three layout engines — the flow one (`engine.ts`), the direct-placement one (`slides.ts`), and the spreadsheet one (`sheets.ts`).** `justifyLineGapsPt` (`src/layout/shared.ts`) recovers each wrapped line's own word-gap positions from a line's per-fragment `xOffsetPt` (a genuine gap wider than floating-point noise means a space stood there; two touching fragments are one word split across a run boundary, and stay touching), divides the line's slack evenly across every detected gap, and returns an all-zero shift whenever there is nothing to stretch (fewer than two fragments, no detected gap, or a line already at or past its target width — this function only ever adds space, never compresses). `layoutParagraphFlow`/`layoutParagraphInCell` (`engine.ts`, covering docx/odt paragraphs, docx/odt tables, and odm-assembled chapters) and `layoutParagraph` (`slides.ts`, covering pptx/odp shape text and slide-table cells) call it for every wrapped, non-final line of a `'justify'`-aligned paragraph; the paragraph's own final line (or a paragraph that never wraps at all) stays left-aligned, matching Word/LibreOffice/Impress's own convention. `sheets.ts`'s `renderCellText` calls it too, but a spreadsheet cell only ever renders one line by this module's own documented scope, so the "non-final line" case only arises when a cell's source text carries an explicit line break — `wrapRunsToWidth` then produces more than one line, of which only the first is ever rendered, and that first line is the genuinely non-final one a justified cell stretches; justification is skipped outright when that line already overflowed its cell (the numeric-`###`/string-spill-or-truncate fragments no longer reflect the natural layout the stretch needs) or for an ordinary single-line cell, matching every real spreadsheet application's own "justify only wraps, never a single line" behaviour. `alignmentOffsetPt` itself still returns `0` for `'justify'` in all three files, unchanged — the whole-line offset it computes is the wrong shape for inter-word stretching, which is why the stretch lives in a second, sibling function each caller applies on top, not a new branch inside it. No known gap remains in inter-word justification across any layout engine this package has.
|
|
570
573
|
- **Reading arbitrary real-world PDFs, encrypted-PDF support, and unsupported image filters (`CCITTFaxDecode`/`JBIG2Decode`/`JPXDecode`) are all pdf-codec's own scope boundaries now, not this package's.** In short: the parser targets cleanly-generated output from mainstream producers rather than adversarial-input robustness; `/Encrypt` in the trailer throws rather than attempting decryption, even for the common empty-user-password case; scanned-fax and JPEG2000 images are skipped with a diagnostic while the rest of the page still reads (JPEG and PNG both pass through losslessly). See pdf-codec's own README for the full statement of each.
|
|
@@ -595,15 +598,21 @@ To run a single test file: `pnpm vitest run src/path/to/file.test.ts`.
|
|
|
595
598
|
- **FB4+-only types (`INT128`/`DECFLOAT`) are not a deferred decoding task but an environmental hard stop: LibreOffice cannot produce a `.odb` containing one, so there is no real fixture to verify a decoder against, and this package does not guess a wire format from documentation alone.** LibreOffice 26.2.5.2 bundles `libfbclient.dylib.3.0.7` and `security3.fdb`, and its embedded engine reports itself as `Firebird (engine12) / 3.0.7` through SDBC's own `getDatabaseProductVersion`. Confirmed empirically rather than inferred from those version numbers, by attempting each type's own DDL against that engine through the same headless UNO route the fixtures are generated with: `INT128` fails with `SQL error code = -607 ... Specified domain or source column INT128 does not exist`; `DECFLOAT(16)` and `DECFLOAT(34)` fail with `-104 Token unknown`; `TIMESTAMP WITH TIME ZONE` fails with `-104 Token unknown - WITH` (the same reason this reader's `unsupported-tz` physical type is unreachable in practice); `NUMERIC(38,2)` fails with `-842 Precision must be from 1 to 18`, which is precisely the ceiling above which Firebird would need `INT128` storage; and `NUMERIC(18,2)` — the last width FB3 supports — succeeds. `decodeRowValues` accordingly throws a named error for these physical types rather than decoding them speculatively. A Firebird 4/5 server could be run separately to produce such a backup, but the result would be a standalone `.fbk` at a different `att_backup_format` version than the 10 this reader pins, and no `.odb` could ever contain it — widening the reader for a file its only caller cannot encounter would be speculation, not root-cause work.
|
|
596
599
|
- **Headless LibreOffice command-line macro dispatch (`soffice {file} {macro:///Library.Module.Name}`) needed two real, non-obvious environment fixes to run at all in this sandbox, beyond the ones already documented for HSQLDB/odm fixture generation.** (1) A prior session's forcefully-killed `soffice` process leaves macOS's own native "reopen windows after a crash" alert showing on every subsequent launch — invisible in headless/`--invisible` mode (no window to click), so `soffice` hangs indefinitely in `-[NSAlert runModal]` waiting for a response that can never arrive; `defaults write org.libreoffice.script ApplePersistenceIgnoreState -bool true` (plus removing `~/Library/Saved Application State/org.libreoffice.script.savedState`) disables it. (2) `soffice "macro:///Library.Module.Name"` with no document argument silently does nothing at all — per `soffice --help`'s own usage text, the `{file}` argument is not optional (`{file} {macro:///Library.Module.MacroName}`); a session invoking a macro with no real work to do on a document still needs a real (even trivial) file argument for the macro to actually dispatch.
|
|
597
600
|
- **A Firebird gbak backup stream's own wire format mixes two genuinely different byte-level encodings, confirmed only by testing against real bytes, not solely from reading the engine's source.** Every `rec_*`/`att_*` tag-and-attribute structure is little-endian ("VAX order", `isc_vax_integer`), one length-prefix byte per value; a row's own field-value sequence (once any RLE compression is peeled off) is standard RFC 1832 XDR — big-endian, every value (even a nominally 16-bit `SSHORT`) widened to a 4-byte-aligned unit, opaque byte runs zero-padded to the next 4-byte boundary. A genuine 64-bit-integer word-order bug (high 32 bits transmitted first, not low-first as `xdr_hyper`'s own in-memory `temp_long` array layout suggests on first reading) was caught exactly this way: a `DECIMAL(10,2)` column decoded to a nonsense value on the first real-fixture test run, not from a source-reading mistake that was obvious in advance.
|
|
598
|
-
- **STIX Two Math is embedded as a whole, unmodified `CFF ` table rather than glyph-subsetted
|
|
599
|
-
- **A
|
|
601
|
+
- **STIX Two Math is embedded as a whole, unmodified `CFF ` table rather than glyph-subsetted — pdf-codec's own font-embedding scope decision, not this package's.** See pdf-codec's own README for the full CFF-embedding scope statement.
|
|
602
|
+
- **A stretchy fence in an `mrow` now genuinely stretches to its content, via the font's own `MathVariants` data — but only on the VERTICAL axis, and only for an `mo` the operator dictionary calls stretchy.** `layoutRowChildren` (`src/mathml/layout.ts`) targets twice the larger of the row's own non-stretchy children's half-extents about the maths axis (MathML's default `symmetric` behaviour for a fence), asks the `MathFontMetrics.stretch` port for a construction reaching it, and emits a `MathAssembledGlyphs` item: one or more glyph IDs at explicitly computed positions, rather than the Unicode text a `MathGlyphRun` carries. It has to be glyph IDs — every pre-built larger variant in this font is unencoded, as are the radical's and the over-brace's assembly pieces, with only the bracket family's own pieces given code points (the U+239B–U+23AD block). What genuinely stretches today: parentheses, square brackets, curly braces, floor/ceiling, angle brackets, and the vertical-bar/norm pair, each drawn either from a larger pre-built variant or from a real multi-part assembly sized to the content. What does not, and why:
|
|
603
|
+
- **`msqrt`/`mroot` radical signs** keep the hand-drawn hooked sign + vinculum `src/mathml/radical.ts` builds. This is not a `MathVariants` gap — the font's own radical construction stretches perfectly well, and a bare stretchy `<mo>√</mo>` in an `mrow` does now use it — but a radical *construct* needs a vinculum spanning its radicand's width, which no vertical glyph construction supplies, so switching it over means replacing the whole hand-built sign, not just its stem.
|
|
604
|
+
- **Horizontal stretching** (an over/under-brace, `U+23DE`/`U+23DF`, spanning its base in `munder`/`mover`) is not wired up. Everything below the layout engine already handles it — the port takes an `axis`, pdf-codec measures a horizontal construction correctly, and the item kind is axis-agnostic — but `layoutUnderOver` would need to derive its target from a box's *width* rather than from a row's height, and nothing in this module calls `stretch` with `'horizontal'` today.
|
|
605
|
+
- **A multi-character `mo`** (`"||"`, an operator with combining marks) is never stretched: the font's `MathVariants` data is keyed per glyph, so there is no single construction to look up.
|
|
606
|
+
- **A stretched fence's own glyphs have no ToUnicode mapping, so pdf-codec wraps them in an `/ActualText` span carrying the operator's real text.** Text extraction, search, and copy/paste still recover `(` from a six-piece assembled bracket. A fence the base glyph already covers is left as an ordinary `MathGlyphRun` rather than converted to glyph IDs, so a plain inline `(x + 1)` renders and extracts exactly as it did before any of this existed.
|
|
607
|
+
- **The operator dictionary calls `∑`/`∏`/`⋃` and the rest of the big-operator family NOT stretchy, matching MathML3's own dictionary.** This became load-bearing once a stretchy operator genuinely stretches: STIX Two Math *does* declare vertical `MathVariants` for a summation sign, so a wrong `stretchy: true` there would visibly deform a `∑` standing next to a tall fraction. A big operator grows by selecting a larger designed size in display style — the `largeop` mechanism — never by stretching to its row.
|
|
608
|
+
- **A token element's (`mi`/`mn`/`mo`/`mtext`) own box height is the union of its characters' real per-glyph ink bounds, falling back to the font's nominal design ascent/descent only for a glyph that carries none.** `src/mathml/` still parses no glyph outlines itself — the bounds arrive through `MathFontMetrics.glyph`'s own `inkAscentPt`/`inkDescentPt`, which pdf-codec computes by walking the embedded font's Type 2 charstrings (see its README's own `cff-bounds.ts` note). The fallback is not dead code: a glyph that draws nothing (a space) or whose charstring that reader declines to walk reports neither bound, and takes the font-wide nominal extent instead, which is also what a `MathFontMetrics` implementation with no outline parsing at all would supply for every glyph.
|
|
600
609
|
- **The MathML operator dictionary (`src/mathml/operators.ts`) is a deliberately bounded ~60-entry table, not the MathML3 specification's own multi-thousand-entry, form-dependent (prefix/infix/postfix) one.** It covers arithmetic, relational, set/logic, calculus big-operators, fences, and punctuation — the operators real formulas overwhelmingly use — with one entry per character regardless of which position it appears in, falling back to a single sane infix-shaped default (thick-space spacing, no stretch/largeop/movablelimits) for anything else.
|
|
601
610
|
- **`mover`/`munder`/`munderover` centre an over/under-script geometrically over the wider of the two boxes, not at the base glyph's own font-declared accent-attachment point (`MathTopAccentAttachment`, which the embedded font's `MathGlyphInfo` subtable DOES carry and this package DOES parse — see the CFF-embedding gotcha above — just not consumed here).** Visually correct for the common case of a single-character base (geometric centre ≈ optical centre for a roughly symmetric glyph); measurably different only for a multi-character or asymmetric base under a genuine `accent="true"` mark. A real, bounded simplification, not a data gap — the metric this would need is already being parsed for a different purpose.
|
|
602
611
|
- **Greek `mathvariant` mapping covers the plain alphabet, nabla (∇), partial differential (∂), and the six OpenType/Unicode Greek "symbol variant" glyphs** (lunate epsilon/theta/kappa/phi/rho/pi symbols — U+03F5/U+03D1/U+03F0/U+03D5/U+03F1/U+03D6 — styled to bold, italic, bold-italic, bold-sans-serif, and sans-serif-bold-italic; Unicode never assigned symbol-variant glyphs for plain sans-serif, script, fraktur, or double-struck). Every entry is generated directly from Unicode's own `UnicodeData.txt` (see `src/mathml/variant.ts`'s own generation note) rather than transcribed by hand.
|
|
603
612
|
- **Embedded-formula detection inside odt/odp is genuinely new work with no `odf.js`-side equivalent (`readDrawFrameContent` doesn't recognise a `draw:object`-bearing `draw:frame` at all yet — see the `src/odf/` architecture entry above), and each format's own placement is now derived from the exact walk `odf.js` itself used, rather than approximated.** For **odt** (`src/odf/odt/read.ts`): a formula frame is found wherever it actually is — a direct child of `office:text`, one nested inside a `draw:g` group, one anchored inline inside a paragraph's own run content (`text:anchor-type="as-char"`, the shape LibreOffice writes for a formula typed into a sentence), and one inside a list item's own paragraph. Each block lands at its **true position** among the paragraphs/tables `odf.js` already read, because this adapter mirrors `readOdt`'s own `readBlocks` walk to *count* how many `ContentBlock`s each `office:text` child contributes — the per-element bookkeeping that was previously missing and forced every formula to be appended at the end (a `text:list` unwraps into one `ContentParagraph` per item at every nesting level, so "one raw child = one block" does not hold, which is exactly why counting rather than indexing is required). Two bounded, honest details remain: an *inline* formula's block is placed immediately **after** the paragraph containing it rather than truly inside it (`ContentRun` is text-only, so `ContentBlock` has no inline slot for an embedded object, and splitting the paragraph around the formula would invent a boundary the source never had), and an inline frame carries `svg:width`/`svg:height` but no `svg:x` — so its recovered frame is the declared size at a zero origin the text flow replaces, which is all the wordprocessing layout engine reads from it anyway. For **odp** (`src/odf/odp/read.ts`): every formula on every slide is detected, groups included. `collectSlideFormulaFrames` replicates `odf.js`'s own `walkDrawShapes` traversal exactly — document order, recursing into a `draw:g`'s children with that group's own `draw:transform` composed, one shape per `draw:frame` whose geometry `readDrawFrame` resolves and none for any it cannot — so the shape index it counts *is* the index `readOdp` assigned. The previous "skip the whole slide if it contains any `draw:g`" narrowing existed only because the old correspondence was "Nth top-level frame = `shapes[N]`", which a group breaks by splicing its own frames into the same flat array; deriving the index from the same walk removes the ambiguity rather than working around it. **ods embedded-formula detection is still not implemented, and the blocker is upstream, re-verified against the installed `odf.js` 2.0.0 rather than assumed**: its own `readSheet` returns `{ name, cells, columns, rows, images: [], printSettings }` — `images` hardcoded empty, `embeddedObjects` never set — and `readOds` walks only the `table:table` children of `office:spreadsheet`, so a floating `draw:frame` on a sheet is never visited at all. There is consequently no anchor-resolution result for a detection pass to derive a position from, which is precisely what odt's block counting and odp's shape indices give their own passes; `src/layout/sheets.ts` accordingly has no formula-handling branch, with a comment stating exactly this.
|
|
604
613
|
- **A formula crossing a boundary that cannot typeset it degrades to its own plain-text stand-in — its StarMath annotation, or the literal `[formula]` — never to nothing. The docx bridges are no longer part of that list.** `buildDocxPackage` now writes a genuine OMML display equation (`m:oMathPara` > `m:oMath`, structurally translated by `src/omml/write.ts` — see the architecture entry above), so a formula crossing `odtToDocx`, or reaching a docx through any other `buildDocxPackage` caller, arrives as real, editable Word math rather than text. The stand-in survives there for exactly one case: a formula whose MathML produces no OMML content at all (an empty `mathml` array). An individual MathML construct with no OMML counterpart degrades on its own, *inside* the equation, as a literal-text run with an `unsupported-element` diagnostic reported through `buildDocxPackage`'s own `onMathDiagnostic` (threaded from `odtToDocx`/`markdownToDocx`'s `DocumentBridgeOptions`) — it never drags the whole formula down to text. `buildOdtPackage` is no longer on that list either: it writes a real embedded formula sub-document (a nested `Object N/content.xml` with its own `draw:frame`/`draw:object` reference and manifest entry — see the `src/odf-package/` architecture entry), with the identical single-case fallback, a formula carrying no MathML nodes at all. The markdown writer is the only genuinely stand-in-only path left, since CommonMark/GFM has no math construct whatsoever. **`odmToPdf` is not part of this list either**: a chapter's formula is an ordinary block inside that chapter's own `ContentDocument`, so it survives concatenation into the combined document exactly as a paragraph does and renders as genuine typeset MathML. That used to be a documented gap — the formulas travelled in a side-channel map keyed by `sourcePath`, and re-keying every entry against the combined document's own renumbered block indices was intractable — which moving a formula's content *into* the `ContentDocument` removed outright rather than solved.
|
|
605
614
|
- **OMML is read as well as written, but the two directions are deliberately not symmetric in coverage.** `readDocxContent` recovers a docx equation as a real `ContentEmbeddedObjectBlock` carrying its own MathML — the identical shape `readOdtContent` produces for an ODF embedded formula — so `docxToPdf` typesets a Word-authored equation, and `odt → docx → odt` carries a formula through as a formula. The reader covers strictly more than the writer emits, because it has to read what Word wrote rather than only what this package wrote: `m:d`, `m:nary`, `m:acc`, `m:bar`, `m:func`, and `m:sPre` have exact MathML inverses and no writer counterpart at all (see the `src/omml/` architecture entry). What that asymmetry costs in practice: a `docx → odt → docx` round trip of a Word-authored `m:d` comes back as explicit `mo` fence tokens inside an `mrow` rather than as an auto-growing `m:d` delimiter again, an `m:nary` comes back as a scripted operator followed by its operand rather than as an `m:nary`, and an `m:sPre` degrades outright on the way back out, since `mmultiscripts` is one of the constructs `src/omml/write.ts` has no OMML expression for. The mathematics survives every one of those hops; only the specific OMML construct that expressed it does not. Three further real, tracked read-side boundaries: an equation inside a TABLE CELL is not recovered (a cell's paragraphs are blocks of a `ContentTableCell`, not top-level blocks, so they neither participate in the `w:p`-ordinal correspondence nor have a top-level position to splice into — the same scope line `buildDocxPackage`'s own `appendCellBlock` draws on the write side); OMML records no geometry whatsoever, so a recovered block's `frame` is a stand-in whose only meaningful field is `heightPt`, taken from the equation's own `w:rPr/w:sz` when it states one and from Word's own 11pt body default otherwise, stated as the exact inverse of `src/layout/engine.ts`'s `frameHeightPt / 2` size estimate; and an `mtext` that carried an explicit `mathvariant` was written as an ordinary styled math run, which OMML gives no way to distinguish from a styled `mi`, so it reads back as `mi`/`mn`/`mo` rather than as `mtext`.
|
|
606
|
-
- **The OMML translator covers exactly the construct set `src/mathml/layout.ts` typesets, no more — the two are kept aligned deliberately, not by accident.** `mrow`/`mstyle`/`semantics` flatten (every OMML argument slot already holds a sequence, so OMML has no row element of its own); `mi`/`mn`/`mo`/`mtext` become `m:r`/`m:t` runs, with `mtext` written as OMML normal text (`m:nor`) and every `mathvariant` mapped onto the `m:scr` script + `m:sty` style pair — a mapping with no residue, since OMML's two axes span MathML's fourteen values exactly. The honest limits: a stretchy fence
|
|
615
|
+
- **The OMML translator covers exactly the construct set `src/mathml/layout.ts` typesets, no more — the two are kept aligned deliberately, not by accident.** `mrow`/`mstyle`/`semantics` flatten (every OMML argument slot already holds a sequence, so OMML has no row element of its own); `mi`/`mn`/`mo`/`mtext` become `m:r`/`m:t` runs, with `mtext` written as OMML normal text (`m:nor`) and every `mathvariant` mapped onto the `m:scr` script + `m:sty` style pair — a mapping with no residue, since OMML's two axes span MathML's fourteen values exactly. The honest limits: a stretchy fence is written as an ordinary operator run rather than as an auto-growing `m:d` delimiter — which now genuinely DIVERGES from the PDF path, where a fence does stretch to its content (see the stretchy-fence gotcha above): Word will render the docx fence at its base size where the PDF renders it assembled and full height. A tracked, bounded gap, not a silent one; closing it means emitting a real `m:d` with the fence characters as its `m:begChr`/`m:endChr`, which is a different write shape from the run-per-token one the rest of this translator uses. `munderover` becomes a nested `m:limUpp`/`m:limLow` pair rather than an `m:nary`, because `m:nary`'s own `m:e` slot is the *operand* being summed and MathML records no operand inside `munderover` at all (it sits outside as a following sibling, with nothing marking where it ends — choosing one would be guessing at operand scope), and `mspace` becomes a single literal space with an `approximated-element` diagnostic, since OMML has no width-parameterised spacer anywhere in its vocabulary. `mathvariant` is carried as markup only: the characters themselves stay in their base form rather than being rewritten into the Mathematical Alphanumeric Symbols block the way `applyMathVariant` does for glyph rendering, which would double-apply the style in Word. The `xmlns:m` declaration goes on the fragment's own root rather than on `w:document`, so an equation appended through `DocxParagraph.appendOfficeMath` stays valid inside a docx this package did not scaffold.
|
|
607
616
|
- **`sourcePath` traces a `LayoutItem` back to the `ContentDocument` node it came from, but only within one read+layout pass.** `ooxml.js`'s `readDocx`/`readPptx` stamp every `ContentRun`/`ContentImageBlock`/`ContentTable`/`ContentShape` with a positional path (`sections[0].blocks[2].runs[1]`, `slides[1].shapes[3].blocks[0]`); `convertWordprocessingToLayout`/`convertPresentationToLayout` copy that same string onto whichever `LayoutText`/`LayoutImage`/`LayoutLink`/`LayoutRect` item(s) it produces, so a positioned PDF-side item can be traced back to its semantic origin. When line-wrapping splits one run's word across a run boundary, every resulting fragment gets its own run's path (not a shared or merged one); when a single run is emergency-split across several lines or pages, every resulting fragment keeps that same one run's path unchanged. A table cell's background `LayoutRect` is attributed to its containing table's own `sourcePath`, since `ContentTableCell` carries none of its own. This is **not** an edit-tracking or incremental-relayout mechanism — the path is only valid against the exact `ContentDocument`/`Package` it was assigned from in that one read; editing the document, re-reading it, or reordering its blocks invalidates every previously-captured path, and nothing here recomputes or diffs paths across two versions of a document.
|
|
608
617
|
- **`readMarkdownContent` passes markdown-codec's `readMarkdown` return value straight through, unlike `readDocxContent`/`readOdtContent`/etc., which build a fresh `ContentDocument` envelope from a narrower, format-specific shape.** `markdown-codec`'s own `readMarkdown` already produces a full `document-schema.js` `ContentDocument` directly (`kind`/`formatVersion`/`metadata`/`sections`) — the identical `ContentDocument` type `documents.js` itself imports and re-exports from `document-schema.js`, with no local schema of its own to reconcile against — so, after narrowing to the `wordprocessing` variant, there is nothing left to rebuild.
|
|
609
618
|
- **Every construct-mapping gap either `readMarkdownContent` (read) or `buildMarkdownText` (write) cannot represent losslessly is markdown-codec's own documented, reachable `MarkdownDiagnosticCodes` entry, surfaced through whatever `sink` a caller passes to `readMarkdownContent`/`buildMarkdownText` directly (the `DocumentToPdfOptions`/`DocumentBridgeOptions` shapes `markdownToPdf`/`markdownToDocx`/`markdownToOdt` accept have no room for one — see those types' own doc comments) — not a silent approximation:**
|
|
@@ -630,7 +639,7 @@ To run a single test file: `pnpm vitest run src/path/to/file.test.ts`.
|
|
|
630
639
|
|
|
631
640
|
**docx/pptx/odt/odp/ods/odg → PDF** is a genuine layout render: the docx/odt flow/pagination engine and the pptx/odp direct-placement engine both produce real positioned text, images, tables, and (for docx/odt) numbered/bulleted lists, styled through the full cascade (theme fonts/colours, `basedOn` chains, placeholder inheritance for docx/pptx; `style:default-style`/`style:parent-style-name` chains for odt/odp). `odg` renders its vector primitives (rect/ellipse/line/path, the last emitted as real PDF `m`/`l`/`c`/`h` content-stream operators, not a polygon approximation of any curve) and reuses the pptx/odp direct-placement engine's own shape conversion for whatever text it also carries. It is a faithful **visual approximation**, not a pixel- or line-identical reproduction of what Word/PowerPoint/Writer/Impress/Draw would themselves render — how close depends on which typeface the document asks for and whether it embedded one, see the font-resolution gotcha above.
|
|
632
641
|
|
|
633
|
-
**odf → PDF (`odfToPdf`), and a formula embedded inside odt/odp,** render **faithful mathematical typesetting**, not a static image or a plain-text placeholder: real box-model layout (script/limit positioning, fraction/radical geometry with correct rule thickness, table column alignment, `mathvariant` → Mathematical Alphanumeric Symbols mapping) through the embedded STIX Two Math font, with genuine per-glyph metrics (advance width, italic correction, top-accent attachment) and font-wide layout constants (axis height, fraction/radical rule thickness and gaps, script shift amounts) parsed directly from that font's own `MATH` table — not approximated or hand-tuned.
|
|
642
|
+
**odf → PDF (`odfToPdf`), and a formula embedded inside odt/odp,** render **faithful mathematical typesetting**, not a static image or a plain-text placeholder: real box-model layout (script/limit positioning, fraction/radical geometry with correct rule thickness, table column alignment, `mathvariant` → Mathematical Alphanumeric Symbols mapping) through the embedded STIX Two Math font, with genuine per-glyph metrics (advance width, italic correction, top-accent attachment) and font-wide layout constants (axis height, fraction/radical rule thickness and gaps, script shift amounts) parsed directly from that font's own `MATH` table — not approximated or hand-tuned. A vertical fence around a tall construct genuinely stretches too, assembled from the font's own `MathVariants` pieces and sized to what it wraps, rather than drawn at a fixed base size. The honest limits: only vertical stretching is wired up, so an over/under-brace still renders at its base width, and `msqrt`/`mroot` still draw a hand-built radical sign rather than the font's own stretched one (both for structural reasons, not because the font data is unavailable — see the Gotchas entries above); `mover`/`munder` centre geometrically rather than at the font's own declared accent-attachment point; and the operator dictionary and Greek `mathvariant` mapping each cover a deliberately bounded, common-case set rather than the full specification. **`pdfToOdf` (PDF → structured MathML) is not attempted, on either direction** — recovering a semantic operator tree (is this pair of glyphs a fraction, or a coincidentally stacked pair of ordinary characters? is a raised glyph a superscript, or just a smaller font size used for emphasis?) from nothing but positioned glyphs and paths is a categorically different, OCR-adjacent problem, with no geometry-reconstruction analogue anywhere else in this package: `reconstructWordprocessing`/`reconstructPresentation` recover paragraph/shape *structure* from geometry, never semantic *meaning* the way recognising a fraction would require.
|
|
634
643
|
|
|
635
644
|
**PDF → docx/pptx/odt/odp** is necessarily a **best-effort reconstruction** from geometry: a PDF page is just positioned glyphs and images, with no semantic paragraph or shape structure to recover. Reading order, bold/italic/colour/font-size, and page/slide count are preserved; paragraph and text-block boundaries are inferred from baseline spacing and left-margin indentation, not recovered exactly. Two further kinds of content are recovered on top of that text, each on its own explicit terms: a real `ContentTable`, but **only** where a genuine drawn gridline lattice is detected, never from text alignment (which would be inventing structure, not recovering it); and a page's vector primitives, into a nested drawing document that currently reaches the `ContentDocument` pivot but not the output bytes. Both are covered in full by their own [Gotchas](#gotchas-and-quirks) entries.
|
|
636
645
|
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { COLOR_BLACK as COLOR_BLACK$1, Color as LayoutColor, ColorSchema as LayoutColorSchema, colorToRgbHex as colorToRgbHex$1, rgbHexToColor } from "document-schema.js";
|
|
2
|
+
export { rgbHexToColor as a, colorToRgbHex$1 as i, LayoutColor as n, LayoutColorSchema as r, COLOR_BLACK$1 as t };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_model_units = require("../../model/units.cjs");
|
|
3
3
|
const require_edit_docx_editor = require("./editor.cjs");
|
|
4
|
+
const require_model_embedded_drawing = require("../../model/embedded-drawing.cjs");
|
|
4
5
|
const require_model_formula = require("../../model/formula.cjs");
|
|
5
6
|
let ooxml_js = require("ooxml.js");
|
|
6
7
|
//#region src/edit/docx/content.ts
|
|
@@ -122,6 +123,10 @@ function appendBlock(body, block, options) {
|
|
|
122
123
|
else if (block.kind === "embeddedObject") appendEmbeddedObject(body, block, options);
|
|
123
124
|
}
|
|
124
125
|
function appendEmbeddedObject(body, block, options) {
|
|
126
|
+
if (require_model_embedded_drawing.drawingOfBlock(block) !== void 0) {
|
|
127
|
+
body.appendParagraph().appendVectorAnchors(require_model_embedded_drawing.embeddedDrawingVectors(block, require_model_embedded_drawing.FLOW_CONTAINER_ORIGIN));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
125
130
|
const formula = require_model_formula.formulaOfBlock(block);
|
|
126
131
|
if (formula === void 0) return;
|
|
127
132
|
const paragraph = body.appendParagraph();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ptToTwips } from "../../model/units.js";
|
|
2
2
|
import { createDocx } from "./editor.js";
|
|
3
|
+
import { FLOW_CONTAINER_ORIGIN, drawingOfBlock, embeddedDrawingVectors } from "../../model/embedded-drawing.js";
|
|
3
4
|
import { formulaOfBlock, formulaPlaceholderText } from "../../model/formula.js";
|
|
4
5
|
import { base64ToBytes } from "ooxml.js";
|
|
5
6
|
//#region src/edit/docx/content.ts
|
|
@@ -121,6 +122,10 @@ function appendBlock(body, block, options) {
|
|
|
121
122
|
else if (block.kind === "embeddedObject") appendEmbeddedObject(body, block, options);
|
|
122
123
|
}
|
|
123
124
|
function appendEmbeddedObject(body, block, options) {
|
|
125
|
+
if (drawingOfBlock(block) !== void 0) {
|
|
126
|
+
body.appendParagraph().appendVectorAnchors(embeddedDrawingVectors(block, FLOW_CONTAINER_ORIGIN));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
124
129
|
const formula = formulaOfBlock(block);
|
|
125
130
|
if (formula === void 0) return;
|
|
126
131
|
const paragraph = body.appendParagraph();
|
package/dist/edit/docx/image.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { i as insertImageMedia, n as MediaContext, r as buildInlineDrawing, t as ImageInit } from "../../image-
|
|
2
|
-
export { ImageInit, MediaContext, buildInlineDrawing, insertImageMedia };
|
|
1
|
+
import { a as nextDrawingId, i as insertImageMedia, n as MediaContext, r as buildInlineDrawing, t as ImageInit } from "../../image-DRqXQmEg.cjs";
|
|
2
|
+
export { ImageInit, MediaContext, buildInlineDrawing, insertImageMedia, nextDrawingId };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { i as insertImageMedia, n as MediaContext, r as buildInlineDrawing, t as ImageInit } from "../../image-
|
|
2
|
-
export { ImageInit, MediaContext, buildInlineDrawing, insertImageMedia };
|
|
1
|
+
import { a as nextDrawingId, i as insertImageMedia, n as MediaContext, r as buildInlineDrawing, t as ImageInit } from "../../image-DRqXQmEg.js";
|
|
2
|
+
export { ImageInit, MediaContext, buildInlineDrawing, insertImageMedia, nextDrawingId };
|
package/dist/edit/docx/image.js
CHANGED
|
@@ -69,4 +69,4 @@ function insertImageMedia(context, documentRoot, image) {
|
|
|
69
69
|
return buildInlineDrawing(relationshipId, image.widthPt, image.heightPt, id, image.altText);
|
|
70
70
|
}
|
|
71
71
|
//#endregion
|
|
72
|
-
export { buildInlineDrawing, insertImageMedia };
|
|
72
|
+
export { buildInlineDrawing, insertImageMedia, nextDrawingId };
|
|
@@ -5,6 +5,7 @@ const require_xml_edit = require("../../xml/edit.cjs");
|
|
|
5
5
|
const require_edit_docx_image = require("./image.cjs");
|
|
6
6
|
const require_edit_docx_props = require("./props.cjs");
|
|
7
7
|
const require_edit_docx_run = require("./run.cjs");
|
|
8
|
+
const require_edit_docx_vector = require("./vector.cjs");
|
|
8
9
|
let ooxml_js = require("ooxml.js");
|
|
9
10
|
//#region src/edit/docx/paragraph.ts
|
|
10
11
|
function directChild(parent, tag) {
|
|
@@ -29,7 +30,7 @@ var DocxParagraph = class {
|
|
|
29
30
|
return create ? require_edit_docx_props.ensureFirstChild(node, "w:pPr") : directChild(node, "w:pPr");
|
|
30
31
|
}
|
|
31
32
|
get text() {
|
|
32
|
-
return
|
|
33
|
+
return require_edit_docx_run.wordprocessingText(this.live());
|
|
33
34
|
}
|
|
34
35
|
runs() {
|
|
35
36
|
const node = this.live();
|
|
@@ -118,6 +119,14 @@ var DocxParagraph = class {
|
|
|
118
119
|
written: true
|
|
119
120
|
};
|
|
120
121
|
}
|
|
122
|
+
appendVectorAnchors(vectors) {
|
|
123
|
+
const node = this.live();
|
|
124
|
+
if (this.imageContext === void 0) throw new Error("appendVectorAnchors requires a paragraph opened through a DocxEditor");
|
|
125
|
+
const { documentRoot } = this.imageContext;
|
|
126
|
+
vectors.forEach((vector, index) => {
|
|
127
|
+
node.children.push(require_xml_fragment.el("w:r", {}, [require_edit_docx_vector.buildAnchoredVectorDrawing(vector, require_edit_docx_image.nextDrawingId(documentRoot), index)]));
|
|
128
|
+
});
|
|
129
|
+
}
|
|
121
130
|
insertImageAfter(image) {
|
|
122
131
|
const node = this.live();
|
|
123
132
|
if (this.imageContext === void 0) throw new Error("insertImageAfter requires a paragraph opened through a DocxEditor");
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { r as MathMlNode$1 } from "../../nodes-pArN9ilm.cjs";
|
|
2
2
|
import { t as OmmlWriteResult } from "../../write-BQ6SK8r8.cjs";
|
|
3
|
-
import { n as MediaContext, t as ImageInit } from "../../image-
|
|
4
|
-
import { n as RunInit, t as DocxRun } from "../../run-
|
|
5
|
-
import { ContentListMembership } from "document-schema.js";
|
|
3
|
+
import { n as MediaContext, t as ImageInit } from "../../image-DRqXQmEg.cjs";
|
|
4
|
+
import { n as RunInit, t as DocxRun } from "../../run-P0b2pDdX.cjs";
|
|
5
|
+
import { ContentListMembership, ContentVector } from "document-schema.js";
|
|
6
6
|
import { Package, XmlElement, XmlNode } from "ooxml.js";
|
|
7
7
|
//#region src/edit/docx/paragraph.d.ts
|
|
8
8
|
interface ParagraphInit {
|
|
@@ -37,6 +37,7 @@ declare class DocxParagraph {
|
|
|
37
37
|
appendOfficeMath(mathml: readonly MathMlNode$1[]): OmmlWriteResult & {
|
|
38
38
|
readonly written: boolean;
|
|
39
39
|
};
|
|
40
|
+
appendVectorAnchors(vectors: readonly ContentVector[]): void;
|
|
40
41
|
insertImageAfter(image: ImageInit): void;
|
|
41
42
|
remove(): void;
|
|
42
43
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { r as MathMlNode$1 } from "../../nodes-pArN9ilm.js";
|
|
2
2
|
import { t as OmmlWriteResult } from "../../write-THs7ipBq.js";
|
|
3
|
-
import { n as MediaContext, t as ImageInit } from "../../image-
|
|
4
|
-
import { n as RunInit, t as DocxRun } from "../../run-
|
|
3
|
+
import { n as MediaContext, t as ImageInit } from "../../image-DRqXQmEg.js";
|
|
4
|
+
import { n as RunInit, t as DocxRun } from "../../run-BhYv2wJf.js";
|
|
5
5
|
import { Package, XmlElement, XmlNode } from "ooxml.js";
|
|
6
|
-
import { ContentListMembership } from "document-schema.js";
|
|
6
|
+
import { ContentListMembership, ContentVector } from "document-schema.js";
|
|
7
7
|
//#region src/edit/docx/paragraph.d.ts
|
|
8
8
|
interface ParagraphInit {
|
|
9
9
|
readonly text?: string;
|
|
@@ -37,6 +37,7 @@ declare class DocxParagraph {
|
|
|
37
37
|
appendOfficeMath(mathml: readonly MathMlNode$1[]): OmmlWriteResult & {
|
|
38
38
|
readonly written: boolean;
|
|
39
39
|
};
|
|
40
|
+
appendVectorAnchors(vectors: readonly ContentVector[]): void;
|
|
40
41
|
insertImageAfter(image: ImageInit): void;
|
|
41
42
|
remove(): void;
|
|
42
43
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { el } from "../../xml/fragment.js";
|
|
2
2
|
import { buildOfficeMathParagraph } from "../../omml/write.js";
|
|
3
3
|
import { getOrCreateChildElement, removeChild } from "../../xml/edit.js";
|
|
4
|
-
import { insertImageMedia } from "./image.js";
|
|
4
|
+
import { insertImageMedia, nextDrawingId } from "./image.js";
|
|
5
5
|
import { PPR_ORDER, ensureFirstChild, getAlignment, getStyleId, setAlignment, setStyleId } from "./props.js";
|
|
6
|
-
import { DocxRun, buildRun } from "./run.js";
|
|
7
|
-
import {
|
|
6
|
+
import { DocxRun, buildRun, wordprocessingText } from "./run.js";
|
|
7
|
+
import { buildAnchoredVectorDrawing } from "./vector.js";
|
|
8
|
+
import { attr } from "ooxml.js";
|
|
8
9
|
//#region src/edit/docx/paragraph.ts
|
|
9
10
|
function directChild(parent, tag) {
|
|
10
11
|
for (const child of parent.children) if (child.type === "element" && child.tag === tag) return child;
|
|
@@ -28,7 +29,7 @@ var DocxParagraph = class {
|
|
|
28
29
|
return create ? ensureFirstChild(node, "w:pPr") : directChild(node, "w:pPr");
|
|
29
30
|
}
|
|
30
31
|
get text() {
|
|
31
|
-
return
|
|
32
|
+
return wordprocessingText(this.live());
|
|
32
33
|
}
|
|
33
34
|
runs() {
|
|
34
35
|
const node = this.live();
|
|
@@ -117,6 +118,14 @@ var DocxParagraph = class {
|
|
|
117
118
|
written: true
|
|
118
119
|
};
|
|
119
120
|
}
|
|
121
|
+
appendVectorAnchors(vectors) {
|
|
122
|
+
const node = this.live();
|
|
123
|
+
if (this.imageContext === void 0) throw new Error("appendVectorAnchors requires a paragraph opened through a DocxEditor");
|
|
124
|
+
const { documentRoot } = this.imageContext;
|
|
125
|
+
vectors.forEach((vector, index) => {
|
|
126
|
+
node.children.push(el("w:r", {}, [buildAnchoredVectorDrawing(vector, nextDrawingId(documentRoot), index)]));
|
|
127
|
+
});
|
|
128
|
+
}
|
|
120
129
|
insertImageAfter(image) {
|
|
121
130
|
const node = this.live();
|
|
122
131
|
if (this.imageContext === void 0) throw new Error("insertImageAfter requires a paragraph opened through a DocxEditor");
|
package/dist/edit/docx/run.cjs
CHANGED
|
@@ -3,9 +3,15 @@ const require_xml_fragment = require("../../xml/fragment.cjs");
|
|
|
3
3
|
const require_xml_entities = require("../../xml/entities.cjs");
|
|
4
4
|
const require_xml_edit = require("../../xml/edit.cjs");
|
|
5
5
|
const require_edit_docx_props = require("./props.cjs");
|
|
6
|
+
const require_xml_query = require("../../xml/query.cjs");
|
|
6
7
|
let ooxml_js = require("ooxml.js");
|
|
7
8
|
let document_schema_js = require("document-schema.js");
|
|
8
9
|
//#region src/edit/docx/run.ts
|
|
10
|
+
function wordprocessingText(element) {
|
|
11
|
+
let out = "";
|
|
12
|
+
for (const cursor of require_xml_query.walkElements([element])) if (cursor.node.tag === "w:t") out += (0, ooxml_js.textContent)(cursor.node);
|
|
13
|
+
return out;
|
|
14
|
+
}
|
|
9
15
|
function directChild(parent, tag) {
|
|
10
16
|
for (const child of parent.children) if (child.type === "element" && child.tag === tag) return child;
|
|
11
17
|
}
|
|
@@ -38,7 +44,7 @@ var DocxRun = class {
|
|
|
38
44
|
return create ? require_edit_docx_props.ensureFirstChild(node, "w:rPr") : directChild(node, "w:rPr");
|
|
39
45
|
}
|
|
40
46
|
get text() {
|
|
41
|
-
return (
|
|
47
|
+
return wordprocessingText(this.live());
|
|
42
48
|
}
|
|
43
49
|
set text(value) {
|
|
44
50
|
const tNode = findOrCreateT(this.live());
|
|
@@ -141,3 +147,4 @@ function buildRun(init = {}) {
|
|
|
141
147
|
//#endregion
|
|
142
148
|
exports.DocxRun = DocxRun;
|
|
143
149
|
exports.buildRun = buildRun;
|
|
150
|
+
exports.wordprocessingText = wordprocessingText;
|
package/dist/edit/docx/run.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as RunInit, r as buildRun, t as DocxRun } from "../../run-
|
|
2
|
-
export { DocxRun, RunInit, buildRun };
|
|
1
|
+
import { i as wordprocessingText, n as RunInit, r as buildRun, t as DocxRun } from "../../run-P0b2pDdX.cjs";
|
|
2
|
+
export { DocxRun, RunInit, buildRun, wordprocessingText };
|
package/dist/edit/docx/run.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as RunInit, r as buildRun, t as DocxRun } from "../../run-
|
|
2
|
-
export { DocxRun, RunInit, buildRun };
|
|
1
|
+
import { i as wordprocessingText, n as RunInit, r as buildRun, t as DocxRun } from "../../run-BhYv2wJf.js";
|
|
2
|
+
export { DocxRun, RunInit, buildRun, wordprocessingText };
|