js.documents 1.62.0 → 1.63.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 +39 -6
- package/dist/convert/convert.cjs +69 -17
- package/dist/convert/convert.d.cts +2 -1
- package/dist/convert/convert.d.ts +2 -1
- package/dist/convert/convert.js +70 -18
- package/dist/convert/local.cjs +15 -2
- package/dist/convert/local.js +15 -2
- package/dist/convert/port.d.cts +8 -4
- package/dist/convert/port.d.ts +8 -4
- package/dist/fonts/obfuscation.cjs +50 -0
- package/dist/fonts/obfuscation.d.cts +9 -0
- package/dist/fonts/obfuscation.d.ts +9 -0
- package/dist/fonts/obfuscation.js +46 -0
- package/dist/fonts/odf.cjs +86 -0
- package/dist/fonts/odf.d.cts +9 -0
- package/dist/fonts/odf.d.ts +9 -0
- package/dist/fonts/odf.js +84 -0
- package/dist/fonts/ooxml.cjs +129 -0
- package/dist/fonts/ooxml.d.cts +9 -0
- package/dist/fonts/ooxml.d.ts +9 -0
- package/dist/fonts/ooxml.js +127 -0
- package/dist/fonts/registry.cjs +18 -0
- package/dist/fonts/registry.d.cts +2 -0
- package/dist/fonts/registry.d.ts +2 -0
- package/dist/fonts/registry.js +16 -0
- package/dist/index.cjs +32 -0
- package/dist/index.d.cts +7 -3
- package/dist/index.d.ts +7 -3
- package/dist/index.js +6 -2
- package/dist/registry-CrSqLIcn.d.ts +19 -0
- package/dist/registry-CtPhMD0f.d.cts +19 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/ExaDev/documents.js) [](https://www.npmjs.com/package/documents.js) [](https://github.com/ExaDev/documents.js/releases/latest) [](https://github.com/ExaDev/documents.js/actions)
|
|
4
4
|
|
|
5
|
-
> Converts between any two compatible document formats through a shared content/layout pivot — docx, pptx, odt, odp, ods, odg, xlsx, and markdown all read into and build from the same `ContentDocument`/`LayoutDocument` model, with PDF simply the one format every variant can reach (docx/pptx/odt/odp/ods/odg/xlsx/markdown ⇄ PDF, fourteen pairs, all round-tripping both ways), plus ten further cross-format bridges, five pairs (odt⇄docx, odp⇄pptx, ods⇄xlsx, markdown⇄docx, markdown⇄odt) that bypass PDF entirely for pairs already sharing a pivot variant directly. Also included: a resolver-driven odm (ODF master document) → PDF conversion for multi-chapter documents, `.odb` (ODF database front-end) table extraction to xlsx/CSV from an embedded HSQLDB TEXT script (Tier 1), HSQLDB's own binary CACHED-table row-store format (Tier 2), and an embedded Firebird database's own gbak logical-backup format (Tier 3), plus static Form/Report *structure* reading (bound controls, bands/groups/functions — not live-data rendering), a read-and-write live-view editor for docx/pptx/odt/odp/ods/odg content, a hand-written MathML presentation-layer typesetting engine with embedded-font PDF rendering (odf → PDF, plus formulas embedded inside odt/odp) and a matching MathML → OMML translator so an embedded formula reaches a docx as real, editable Word math, and a fully hand-written PDF codec, built on [ooxml.js](https://github.com/ExaDev/ooxml.js), [odf.js](https://github.com/ExaDev/odf.js), and [markdown-codec](https://github.com/ExaDev/markdown-codec).
|
|
5
|
+
> Converts between any two compatible document formats through a shared content/layout pivot — docx, pptx, odt, odp, ods, odg, xlsx, and markdown all read into and build from the same `ContentDocument`/`LayoutDocument` model, with PDF simply the one format every variant can reach (docx/pptx/odt/odp/ods/odg/xlsx/markdown ⇄ PDF, fourteen pairs, all round-tripping both ways), plus ten further cross-format bridges, five pairs (odt⇄docx, odp⇄pptx, ods⇄xlsx, markdown⇄docx, markdown⇄odt) that bypass PDF entirely for pairs already sharing a pivot variant directly. Also included: a resolver-driven odm (ODF master document) → PDF conversion for multi-chapter documents, `.odb` (ODF database front-end) table extraction to xlsx/CSV from an embedded HSQLDB TEXT script (Tier 1), HSQLDB's own binary CACHED-table row-store format (Tier 2), and an embedded Firebird database's own gbak logical-backup format (Tier 3), plus static Form/Report *structure* reading (bound controls, bands/groups/functions — not live-data rendering), a read-and-write live-view editor for docx/pptx/odt/odp/ods/odg content, real font resolution for ordinary text (a source document's own embedded faces extracted and rendered through, ahead of caller-supplied faces, metric-compatible vendored substitutes, and finally the standard 14), a hand-written MathML presentation-layer typesetting engine with embedded-font PDF rendering (odf → PDF, plus formulas embedded inside odt/odp) and a matching MathML → OMML translator so an embedded formula reaches a docx as real, editable Word math, and a fully hand-written PDF codec, built on [ooxml.js](https://github.com/ExaDev/ooxml.js), [odf.js](https://github.com/ExaDev/odf.js), and [markdown-codec](https://github.com/ExaDev/markdown-codec).
|
|
6
6
|
|
|
7
7
|
`documents.js` depends on `ooxml.js` for lossless docx/pptx/xlsx ⇄ JSON handling and extends it in two directions `ooxml.js` deliberately does not cover: full PDF support (parsing arbitrary real-world PDFs and generating new ones), and a read-**and-write** manipulation API for docx/pptx content — `ooxml.js`'s own typed readers (`readDocx`/`readPptx`) are one-way and explicitly forbid write-back. PDF reading, writing, and the docx⇄PDF/pptx⇄PDF conversion pipeline are provided by [`pdf-codec`](https://github.com/ExaDev/pdf-codec), a sibling package extracted from this one: a hand-written, dependency-minimal PDF codec with no external PDF library (`pdf-lib`, `pdfjs-dist`, `mupdf`, or any other) as a dependency — see pdf-codec's own README for how it's built and what it embeds (including the vendored STIX Two Math font this package renders formulas through). `src/mathml/` (the MathML typesetting engine) stays in this package and is hand-written too, for the same "no supply-chain surface beyond what's already declared" reason, but consumes pdf-codec's embedded math font through a structurally-typed port rather than any font-parsing code of its own — see [Architecture](#architecture). CommonMark+GFM markdown reading/writing is provided by [`markdown-codec`](https://github.com/ExaDev/markdown-codec), the same "hand-write the format instead of wrapping a third-party library" bet applied to markdown: no `micromark`/`remark`/`marked`/`markdown-it`/`commonmark`/`mdast`/`unified`/`turndown`/`showdown` dependency anywhere in that package.
|
|
8
8
|
|
|
@@ -95,6 +95,8 @@ const markdownBytes2 = pdfToMarkdown(pdfFromMarkdown); // the lossiest conversio
|
|
|
95
95
|
|
|
96
96
|
Each accepts an optional `signal` (`AbortSignal`) and either a `onSubstitution` callback (docx/pptx/odt/odp/ods/odg/xlsx/markdown → PDF, called once per character not representable in a standard-14 font) or a `sink` (PDF → docx/pptx/odt/odp/ods/odg/xlsx/markdown, called once per recoverable parse diagnostic).
|
|
97
97
|
|
|
98
|
+
Every X → PDF conversion additionally accepts `fonts` (extra `ProvidedFont` faces to make available) and `onFontSubstitution` (called once per requested family+weight+style that resolved to something else). Neither is needed for the common case: the conversion already extracts the **source document's own embedded fonts** and renders through them, so a docx or odt saved with font embedding turned on comes out in its real typeface at its real metrics with no caller involvement at all — see [Fonts](#fonts) below for the full resolution order.
|
|
99
|
+
|
|
98
100
|
Ten further conversions, five pairs, bypass PDF entirely: `odtToDocx`/`docxToOdt`, `odpToPptx`/`pptxToOdp`, `odsToXlsx`/`xlsxToOds`, and `markdownToDocx`/`docxToMarkdown`, `markdownToOdt`/`odtToMarkdown` each compose a direct `readXContent` → `buildYPackage` pivot copy, since both sides of each pair already read into and build from the identical `ContentDocument` variant — no layout engine, no font measurement, and no geometry-based reconstruction in between. See [Fidelity](#fidelity) for what that means in practice, and for markdown specifically, why "no layout/reconstruction lossiness" is not the same claim as "no lossiness at all".
|
|
99
101
|
|
|
100
102
|
```ts
|
|
@@ -380,6 +382,36 @@ import { buildOdtPackage } from 'documents.js/edit/odt/content';
|
|
|
380
382
|
|
|
381
383
|
This works via a `"./*"` wildcard entry in `package.json`'s `exports` map, resolving any subpath to the correspondingly-named file under `dist/` — the same directory structure `src/` has, one output file per source module, so `src/edit/odt/content.ts` becomes `dist/edit/odt/content.js`/`.cjs`/`.d.ts`/`.d.cts`.
|
|
382
384
|
|
|
385
|
+
## Fonts
|
|
386
|
+
|
|
387
|
+
Every X → PDF conversion (`docxToPdf`, `pptxToPdf`, `odtToPdf`, `odpToPdf`, `odsToPdf`, `odgToPdf`, plus `markdownToPdf`/`xlsxToPdf`/`odmToPdf`) resolves each requested typeface through a real [`FontRegistry`](https://github.com/ExaDev/pdf-codec), in this order:
|
|
388
|
+
|
|
389
|
+
1. **The source document's own embedded faces.** A docx that was saved with font embedding on carries the exact bytes it was authored against, in `word/fontTable.xml`'s `w:embed*` parts (obfuscated per ECMA-376 Part 4, 2.8.1 — the first 32 bytes XORed against a key derived from the accompanying `w:fontKey` GUID); a pptx carries them in `p:embeddedFontLst` (unobfuscated `.fntdata` parts); an ODF package carries them under `Fonts/`, declared by `office:font-face-decls`'s `svg:font-face-uri` (also unobfuscated). All three are extracted automatically — the caller does nothing.
|
|
390
|
+
2. **Faces the caller supplied** through `options.fonts`, for a family the source document did not embed.
|
|
391
|
+
3. **pdf-codec's vendored Carlito and Caladea faces**, genuinely metric-compatible with Calibri and Cambria, embedded as real subsetted TrueType font programs.
|
|
392
|
+
4. **The standard 14**, for everything else — where Helvetica/Times-Roman remain metric-compatible with Arial/Times New Roman and a width-correction factor approximates the rest.
|
|
393
|
+
|
|
394
|
+
The same registry drives both halves of a conversion: the `TextMeasurer` that decides where lines break and the writer that emits the glyphs. That is load-bearing rather than tidy — measuring against Helvetica's metrics and then drawing through a real Carlito face would wrap text at positions that do not match what was painted.
|
|
395
|
+
|
|
396
|
+
```ts
|
|
397
|
+
import { docxToPdf } from 'documents.js';
|
|
398
|
+
|
|
399
|
+
// Nothing to configure: a docx that embedded its fonts renders in its real typeface.
|
|
400
|
+
const pdfBytes = docxToPdf(docxBytes);
|
|
401
|
+
|
|
402
|
+
// A face for a family the document didn't embed, plus a report of anything that still fell back.
|
|
403
|
+
const withFallbackFace = docxToPdf(docxBytes, {
|
|
404
|
+
fonts: [{ family: 'Brand Sans', bold: false, italic: false, bytes: brandSansTtfBytes }],
|
|
405
|
+
onFontSubstitution: (substitution) => console.warn(substitution.requestedFamily, '->', substitution.resolvedFamily),
|
|
406
|
+
});
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
A document that embeds nothing and asks for no family a vendored substitute covers writes **byte-identical** output to the standard-14-only pipeline this package had before font resolution existed — proven by a real before/after byte comparison across all six conversions in `src/convert/convert-fonts.test.ts`, against a reference that reproduces the old pipeline exactly.
|
|
410
|
+
|
|
411
|
+
Two honest limits, both structural rather than provisional. An embedded face is normally **subsetted** by the application that saved it, so it can legitimately lack a character this package synthesises rather than reads (a list bullet, `sheets.ts`'s `###` column-overflow marker); pdf-codec reports that per character through `onMissingGlyph` and falls back for that one character, never for the run or the document. And `odfToPdf` accepts both font options and consults neither — a standalone formula document emits no positioned text at all, only the embedded STIX Two Math font's own glyphs, which are not registry-resolvable.
|
|
412
|
+
|
|
413
|
+
`extractOoxmlEmbeddedFonts`/`extractOdfEmbeddedFonts`, `extractSourceFonts`, and `createDocumentFontRegistry` are exported for a caller composing `readXContent` → `convertXToLayout` → `writePdf` themselves rather than going through an ergonomic conversion.
|
|
414
|
+
|
|
383
415
|
## Architecture
|
|
384
416
|
|
|
385
417
|
The package is layered from generic primitives outward to the two conversion directions:
|
|
@@ -390,6 +422,7 @@ The package is layered from generic primitives outward to the two conversion dir
|
|
|
390
422
|
- **`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()`.
|
|
391
423
|
- **`src/odf-package/`** — the ODF-side counterpart to `src/opc/`: `manifest.ts` is a pure re-export of `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 `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) and re-syncs the manifest via that same `syncManifest` re-export — 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. `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; `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.
|
|
392
424
|
- **`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` is the one primitive here with no ODF counterpart: it appends a real OMML display equation (`m:oMathPara` > `m:oMath`) built by `src/omml/write.ts`, and is what `buildDocxPackage` uses to write an embedded formula as genuine Word math 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).
|
|
425
|
+
- **`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.
|
|
393
426
|
- **`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 strokes, 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.
|
|
394
427
|
- **`src/omml/`** — the MathML → OMML (Office Math Markup Language, ECMA-376 Part 1 §22.1's own `m:` vocabulary) structural translator: the write-side 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. `write.ts`'s `buildOfficeMath`/`buildOfficeMathParagraph` map each MathML construct 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). It emits no geometry, measures nothing, and loads no font — this is a vocabulary translation, not a rendering. It lives outside `src/mathml/` for that directory's own isolation rule: its whole output type is `ooxml.js`'s `XmlElement`, and `src/mathml/` imports no package at all. `buildDocxPackage` is its real caller; a construct with no OMML counterpart degrades to a literal-text run carrying its own text content, with a diagnostic, exactly as `src/mathml/layout.ts`'s own `unsupported` fallback does for the PDF path.
|
|
395
428
|
- **`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.
|
|
@@ -399,9 +432,9 @@ The package is layered from generic primitives outward to the two conversion dir
|
|
|
399
432
|
- **`src/hsqldb/`** — the `.odb` decoders, in two tiers over two genuinely different on-disk storage shapes a HSQLDB table can use. `script.ts` (Tier 1): a small, bounded HSQLDB TEXT-script-format (`hsqldb.script_format=0`) DDL/DML text parser, not a database engine — `parseHsqldbScript(bytes)` extracts `CREATE TABLE`'s own column names/types and `INSERT INTO`'s own row values into `HsqldbTable[]`, tolerating (skipping) every other statement kind real HSQLDB output emits that this package has no use for (users, grants, sequences, indexes, views), and throwing `HsqldbScriptParseError` for anything matching neither list. `rowformat.ts`/`cache.ts` (Tier 2): a CACHED table's own binary row-store format — LibreOffice's embedded-HSQLDB default (`database.isStoredFileAccess()` switches `hsqldb.default_table_type` to `cached` specifically for storage-backed access, confirmed against the decompiled engine source) — a CACHED table's DDL still lives in `database/script` as ordinary TEXT (Tier 1 parses it unmodified) but its row *data* lives in a separate binary page-cache file, `database/data`. `rowformat.ts` decodes one column's own binary field at a time (`HsqldbDataCursor`, a big-endian `DataView` cursor; `readHsqldbColumnValue`, one branch per SQL type code); `cache.ts` walks a table's own AVL row-position tree (`readHsqldbCachedTableRows`, following each row's persisted `iLeft`/`iRight` child positions recursively, needing no key-comparison or free-list logic at all — a deleted row is already unlinked from the tree before its space can be reused, so a traversal rooted at the tree's current root only ever reaches live rows), rooted at the position `parseHsqldbIndexRoots` recovers from each table's own `SET TABLE ... INDEX'...'` script line, using `parseHsqldbProperties`'s reading of `database/properties` (cache-file scale, engine version) to resolve byte offsets; `decodeHsqldbCachedTables` is the orchestration `src/odb/read.ts` calls, splicing real rows into every table with an index-root line and leaving every other table (MEMORY/TEXT, or a genuinely empty CACHED table — HSQLDB never writes an index-root line for one) exactly as Tier 1 already produced it. `binary-script.ts` (Tier 4): HSQLDB's own whole-script BINARY (`hsqldb.script_format=1`) and COMPRESSED (`=3`) serialisations of `database/script` itself — `parseHsqldbBinaryScript` reads the leading `org.hsqldb.Result` record carrying the database's DDL, rejoins its statements into exactly the TEXT-format script text the same database would have written at `script_format=0`, feeds that to Tier 1, and then decodes the per-table row sections that follow through `rowformat.ts`'s existing per-column decoder; `inflateHsqldbCompressedScript` is the zlib unwrap `=3` needs first, `fflate`'s `unzlibSync`, the one place in `src/hsqldb/` with a dependency beyond `document-schema.js`. All tiers mirror pdf-codec's own isolation discipline: `script.ts` imports only `document-schema.js`'s `ContentCellValue` type; `rowformat.ts` imports the same plus nothing else; `cache.ts` imports only those two and `script.ts`'s own types — no odf.js `Package`/`XmlElement` knowledge anywhere in `src/hsqldb/` — the caller is responsible for handing every function its raw bytes/text already extracted from a real `.odb` package. `HsqldbTable`/`HsqldbColumn` are also the shared pivot shape `src/firebird/`'s own Tier 3 decoder below produces. See Gotchas for Tier 2's own version scope and verification account.
|
|
400
433
|
- **`src/firebird/`** — the Tier 3 `.odb` decoder: a reader for Firebird's own gbak logical-backup format (`database/firebird.fbk`), the artifact a real Firebird-embedded `.odb` actually contains — see the README's own Gotchas entry below for the empirical finding that this is NOT a raw on-disk ODS page dump, the single largest correction this subsystem's own design went through. `reader.ts` holds the two distinct byte-level primitives the format mixes (`FirebirdBackupReader`, the generic little-endian tag+length+value attribute framing every `rec_*`/`att_*` record uses, plus its own RLE/"PackBits"-style decompression for `att_data_data` when the backup is compressed; `XdrReader`, the big-endian, 4-byte-aligned RFC 1832 XDR decoding a row's own field values use once compression is peeled off). `blr-types.ts` maps a field's own BLR type opcode (`att_field_type`) onto its physical storage representation, sourced directly from Firebird's own `blr.h`/`align.h`. `date.ts` restates Firebird's own MJD-epoch DATE and 1/10000-second-tick TIME encoding, taken from `NoThrowTimeStamp.cpp`. `schema.ts` walks `rec_relation`/`rec_field` (column definitions gbak has ALREADY resolved from the live engine's system tables at backup time — see the Gotchas entry). `data.ts` walks `rec_relation_data`/`rec_data` (a relation's own rows, addressed by name), decoding each row's XDR-and-possibly-RLE-compressed field-value sequence into `ContentCellValue[]`. `backup.ts`'s `readFirebirdBackup` is the top-level entry point, producing the identical `HsqldbTable[]` shape `parseHsqldbScript` does.
|
|
401
434
|
- **`src/odb/`** — the decoder-selection and pivot-mapping layer sitting between odf.js's `.odb` support and `src/hsqldb/`/`src/firebird/`: `read.ts`'s `readOdbTables(pkg)` calls odf.js's own `readOdbInventory` to classify the package's connection (throwing `OdbNoEmbeddedDataSourceError` for an external-only datasource) and its embedded engine, then routes a genuine HSQLDB TEXT script to `parseHsqldbScript` and a BINARY/COMPRESSED one to `src/hsqldb/binary-script.ts`'s `parseHsqldbBinaryScript` (which recovers the identical TEXT-format DDL either way), then — whenever a `database/data` part is present — hands that result to `src/hsqldb/cache.ts`'s `decodeHsqldbCachedTables` to splice in every CACHED table's real rows (a `.odb` with no CACHED table at all, the common case, never even looks for `database/data`, leaving the script-derived result untouched), or routes a Firebird `database/firebird.fbk` part to `readFirebirdBackup` — throwing `OdbUnsupportedFormatError` for an embedded engine, or an engine storage shape, it has no reader for at all. `spreadsheet.ts`'s `odbTablesToSpreadsheetDocument` maps `HsqldbTable[]` onto the same `ContentSheet`-based `ContentDocument` spreadsheet variant `readOdsContent`/`buildOdsPackage` already produce and consume, feeding `odbToXlsx`'s call into `buildXlsxPackage` directly. `csv.ts`'s `buildOdbTableCsv` writes exactly one named table as CSV bytes, with no `ContentSheet`/xlsx machinery involved at all, throwing `OdbTableNotSpecifiedError`/`OdbTableNotFoundError` (naming every available table) when the caller's own `table` option doesn't resolve to exactly one table.
|
|
402
|
-
- **`src/convert/`** — `convert.ts` (the fourteen PDF-pivot round-trip ergonomic wrappers — docx/pptx/odt/odp/ods/odg each with a genuine layout-engine edge, `xlsxToPdf`/`pdfToXlsx` composing the ods⇄xlsx bridge with the ods⇄pdf layout pair internally, and `markdownToPdf`/`pdfToMarkdown` reusing the wordprocessing layout engine directly — plus a dedicated "cross-format bridges" section, ten functions across five pairs: `odtToDocx`/`docxToOdt`, `odpToPptx`/`pptxToOdp`, `odsToXlsx`/`xlsxToOds`, and `markdownToDocx`/`docxToMarkdown`, `markdownToOdt`/`odtToMarkdown`, each a direct `readXContent` → `buildYPackage` composition bypassing PDF entirely — see [Fidelity](#fidelity) — `odmToPdf`, the one further conversion shaped around a caller-supplied `resolveSubDocument` callback rather than being purely bytes-in/bytes-out, since a `.odm` master document's own chapters are external references odf.js's `readOdm` never inlines — see Gotchas — `odbToXlsx`/`odbToCsv`, thin compositions over `readOdbTables` and `src/odb/`'s own pivot/CSV mapping, and `odfToPdf`, a standalone `.odf` formula document → PDF via `readOdfFormulaContent` → `src/mathml`'s `layoutFormula` → `writePdf`'s own formula-aware option, with no reverse `pdfToOdf` at all), `codec.ts` (`docxPdfCodec`/`pptxPdfCodec`/`odtPdfCodec`/`odpPdfCodec`/`odsPdfCodec`/`odgPdfCodec`/`xlsxPdfCodec`/`markdownPdfCodec` plus `odtDocxCodec`/`odpPptxCodec`/`odsXlsxCodec`/`markdownDocxCodec`/`markdownOdtCodec`, a `z.codec()` pair over each — `odmToPdf`/`odbToXlsx`/`odbToCsv`/`odfToPdf` have no codec of their own, for the same fixed-signature/one-directional reasons each has no port entry, or a one-way port entry, below), `port.ts`/`local.ts` (the swappable `DocumentConverter` contract and its synchronous local implementation, covering `docx`/`pptx`/`odt`/`odp`/`ods`/`odg`/`odf`/`xlsx`/`markdown` → `pdf`, `pdf` → `docx`/`pptx`/`odt`/`odp`/`ods`/`odg`/`xlsx`/`markdown`, and the ten bridge functions — `DocumentFormat` includes `xlsx` even though xlsx has no PDF conversion of its own (the port composes one, see `xlsxToPdf`); `odm` and `odb` are deliberately not `DocumentFormat` members, since neither `odmToPdf` nor `odbToXlsx`/`odbToCsv` is wired into this port at all; `odf` IS a member, but with only the one `odf → pdf` entry — no `pdf → odf`). Every conversion function that builds a `ContentDocument`/`LayoutDocument` internally (the fourteen PDF-pivot conversions and the ten bridges; `odfToPdf` accepts but never invokes it) also accepts an `onDocument` callback, and `ConversionResult` carries the same value through the port as an optional `package` field — the full `DocumentPackage` (content + layout, from `document-schema.js`) that conversion built, not just its target bytes.
|
|
435
|
+
- **`src/convert/`** — `convert.ts` (the fourteen PDF-pivot round-trip ergonomic wrappers — docx/pptx/odt/odp/ods/odg each with a genuine layout-engine edge, `xlsxToPdf`/`pdfToXlsx` composing the ods⇄xlsx bridge with the ods⇄pdf layout pair internally, and `markdownToPdf`/`pdfToMarkdown` reusing the wordprocessing layout engine directly — plus a dedicated "cross-format bridges" section, ten functions across five pairs: `odtToDocx`/`docxToOdt`, `odpToPptx`/`pptxToOdp`, `odsToXlsx`/`xlsxToOds`, and `markdownToDocx`/`docxToMarkdown`, `markdownToOdt`/`odtToMarkdown`, each a direct `readXContent` → `buildYPackage` composition bypassing PDF entirely — see [Fidelity](#fidelity) — `odmToPdf`, the one further conversion shaped around a caller-supplied `resolveSubDocument` callback rather than being purely bytes-in/bytes-out, since a `.odm` master document's own chapters are external references odf.js's `readOdm` never inlines — see Gotchas — `odbToXlsx`/`odbToCsv`, thin compositions over `readOdbTables` and `src/odb/`'s own pivot/CSV mapping, and `odfToPdf`, a standalone `.odf` formula document → PDF via `readOdfFormulaContent` → `src/mathml`'s `layoutFormula` → `writePdf`'s own formula-aware option, with no reverse `pdfToOdf` at all), `codec.ts` (`docxPdfCodec`/`pptxPdfCodec`/`odtPdfCodec`/`odpPdfCodec`/`odsPdfCodec`/`odgPdfCodec`/`xlsxPdfCodec`/`markdownPdfCodec` plus `odtDocxCodec`/`odpPptxCodec`/`odsXlsxCodec`/`markdownDocxCodec`/`markdownOdtCodec`, a `z.codec()` pair over each — `odmToPdf`/`odbToXlsx`/`odbToCsv`/`odfToPdf` have no codec of their own, for the same fixed-signature/one-directional reasons each has no port entry, or a one-way port entry, below), `port.ts`/`local.ts` (the swappable `DocumentConverter` contract and its synchronous local implementation, covering `docx`/`pptx`/`odt`/`odp`/`ods`/`odg`/`odf`/`xlsx`/`markdown` → `pdf`, `pdf` → `docx`/`pptx`/`odt`/`odp`/`ods`/`odg`/`xlsx`/`markdown`, and the ten bridge functions — `DocumentFormat` includes `xlsx` even though xlsx has no PDF conversion of its own (the port composes one, see `xlsxToPdf`); `odm` and `odb` are deliberately not `DocumentFormat` members, since neither `odmToPdf` nor `odbToXlsx`/`odbToCsv` is wired into this port at all; `odf` IS a member, but with only the one `odf → pdf` entry — no `pdf → odf`). Every conversion function that builds a `ContentDocument`/`LayoutDocument` internally (the fourteen PDF-pivot conversions and the ten bridges; `odfToPdf` accepts but never invokes it) also accepts an `onDocument` callback, and `ConversionResult` carries the same value through the port as an optional `package` field — the full `DocumentPackage` (content + layout, from `document-schema.js`) that conversion built, not just its target bytes. `ConversionOptions` carries `fonts`/`onFontSubstitution` alongside `signal` for the same reason `DocumentToPdfOptions` does (see [Fonts](#fonts)), reaching only the `toPdf` edges — a PDF-to-X reconstruction reads a page's already-positioned glyphs and a bridge runs no layout engine, so neither resolves a face at all — and the local implementation reports every substitution as a `font/substituted` diagnostic as well as through the caller's own callback.
|
|
403
436
|
|
|
404
|
-
Dependency direction among this package's own local modules is downward and checkable, with one deliberate exception (`layout`, noted below): `mathml`/`ports` import nothing local (`mathml` is fully self-contained — no dependency on `model`, `document-schema.js`, or any ODF package, since it consumes only its own locally-mirrored `MathMlNode` input and its own injected `MathFontMetrics` port); `model` imports nothing local at all any more — `formula.ts`'s former type-only `MathMlNode` import from `mathml` is gone with the local `EmbeddedFormula` type it served, since document-schema.js now owns a fully-specified `MathMlNode` of its own; `ooxml/*` imports no local module at all (now a thin adapter over `ooxml.js`'s own `readDocx`/`readPptx` — see the `src/ooxml/` entry above — with no `model`/`xml/*` dependency of its own left, since `ContentDocument`/`CONTENT_FORMAT_VERSION` now come straight from `document-schema.js`; no PDF knowledge either); `odf/*` imports `model` only, and only for `formula.ts`'s block/document builders and `geometry.ts`'s `Box`/`PAGE_SIZE_A4` (its own `ContentDocument`/`CONTENT_FORMAT_VERSION` usage is `document-schema.js`-direct too now — no PDF knowledge, no `xml/*` — `odf.js` already owns its own XML query helpers); `markdown` imports `model` only, and only for `formula.ts`'s stand-in text on the write side (`write.ts` flattens a formula block markdown cannot represent), plus the external `markdown-codec` dependency directly (no PDF knowledge, no odf.js/ooxml.js knowledge at all — the one adapter package in this family whose source format is not a zip archive); `omml` imports `mathml` (its node helpers, operator dictionary, `mathvariant` type, and length parser) and `xml/*` (`fragment.ts`'s `el`/`txt`, `entities.ts`'s `encodeXmlText`) only, plus `ooxml.js` for its own `XmlElement` output type — never `model`, `layout`, or any ODF package, and never in the other direction: `mathml` still imports nothing local at all, which is exactly why this translator is a sibling of it rather than a file inside it; `hsqldb` imports `document-schema.js` only (no odf.js knowledge); `firebird` imports `document-schema.js` (its own row/schema decoding, `ContentCellValue` only) and `hsqldb` (`HsqldbTable`/`HsqldbColumn`, a type-only import for its own output shape — the deliberate pivot-sharing point between Tier 1 and Tier 3) but no odf.js knowledge at all; `layout` imports `model`+`mathml`+`ports`, plus, genuinely upward and outward, several text-measurement/font-metric/matrix primitives from the external `pdf-codec` dependency (`measure.ts`/`text-layout.ts`/`math-font.ts`/`matrix.ts`/`afm-widths.ts`/`fonts.ts` — see the `src/layout/` entry above for exactly which); `odf-package` imports odf.js only (no local dependency, mirroring `opc`'s relationship to `ooxml.js`); `odb` imports `hsqldb`+`firebird`+`model`+`odf-package`+odf.js only; `convert` composes everything else, including `pdf-codec` directly for `readPdf`/`writePdf`/`loadMathFont` and `markdown-codec` indirectly via `markdown/read.ts`/`markdown/write.ts`/`markdown/text.ts`. Beyond this package's own local modules, five external dependencies each own a distinct concern with no overlap: `ooxml.js` (docx/pptx/xlsx ⇄ JSON), `odf.js` (odt/ods/odp/odg ⇄ JSON), `document-schema.js` (the shared `ContentDocument`/`LayoutDocument` schemas), `pdf-codec` (the PDF codec itself, plus the text-layout/font-resolution/byte/image primitives built on it), and `markdown-codec` (CommonMark+GFM ⇄ `ContentDocument`). No `PdfObject`/`PdfDict`/`PdfStream` type appears anywhere in this package at all — that type is pdf-codec's own internal concern now, never exposed across the package boundary.
|
|
437
|
+
Dependency direction among this package's own local modules is downward and checkable, with one deliberate exception (`layout`, noted below): `mathml`/`ports` import nothing local (`mathml` is fully self-contained — no dependency on `model`, `document-schema.js`, or any ODF package, since it consumes only its own locally-mirrored `MathMlNode` input and its own injected `MathFontMetrics` port); `model` imports nothing local at all any more — `formula.ts`'s former type-only `MathMlNode` import from `mathml` is gone with the local `EmbeddedFormula` type it served, since document-schema.js now owns a fully-specified `MathMlNode` of its own; `ooxml/*` imports no local module at all (now a thin adapter over `ooxml.js`'s own `readDocx`/`readPptx` — see the `src/ooxml/` entry above — with no `model`/`xml/*` dependency of its own left, since `ContentDocument`/`CONTENT_FORMAT_VERSION` now come straight from `document-schema.js`; no PDF knowledge either); `odf/*` imports `model` only, and only for `formula.ts`'s block/document builders and `geometry.ts`'s `Box`/`PAGE_SIZE_A4` (its own `ContentDocument`/`CONTENT_FORMAT_VERSION` usage is `document-schema.js`-direct too now — no PDF knowledge, no `xml/*` — `odf.js` already owns its own XML query helpers); `markdown` imports `model` only, and only for `formula.ts`'s stand-in text on the write side (`write.ts` flattens a formula block markdown cannot represent), plus the external `markdown-codec` dependency directly (no PDF knowledge, no odf.js/ooxml.js knowledge at all — the one adapter package in this family whose source format is not a zip archive); `omml` imports `mathml` (its node helpers, operator dictionary, `mathvariant` type, and length parser) and `xml/*` (`fragment.ts`'s `el`/`txt`, `entities.ts`'s `encodeXmlText`) only, plus `ooxml.js` for its own `XmlElement` output type — never `model`, `layout`, or any ODF package, and never in the other direction: `mathml` still imports nothing local at all, which is exactly why this translator is a sibling of it rather than a file inside it; `hsqldb` imports `document-schema.js` only (no odf.js knowledge); `firebird` imports `document-schema.js` (its own row/schema decoding, `ContentCellValue` only) and `hsqldb` (`HsqldbTable`/`HsqldbColumn`, a type-only import for its own output shape — the deliberate pivot-sharing point between Tier 1 and Tier 3) but no odf.js knowledge at all; `layout` imports `model`+`mathml`+`ports`, plus, genuinely upward and outward, several text-measurement/font-metric/matrix primitives from the external `pdf-codec` dependency (`measure.ts`/`text-layout.ts`/`math-font.ts`/`matrix.ts`/`afm-widths.ts`/`fonts.ts` — see the `src/layout/` entry above for exactly which); `odf-package` imports odf.js only (no local dependency, mirroring `opc`'s relationship to `ooxml.js`); `fonts` imports no local module at all either — only `ooxml.js`/`odf.js` for the two package shapes it reads and `pdf-codec` for the `ProvidedFont`/`FontRegistry` shapes it produces, so it sits beside `layout` rather than under it despite both feeding the same conversion; `odb` imports `hsqldb`+`firebird`+`model`+`odf-package`+odf.js only; `convert` composes everything else, including `fonts` and `pdf-codec` directly for `readPdf`/`writePdf`/`loadMathFont`/`createFontMeasurer`/`createFontRegistry` and `markdown-codec` indirectly via `markdown/read.ts`/`markdown/write.ts`/`markdown/text.ts`. Beyond this package's own local modules, five external dependencies each own a distinct concern with no overlap: `ooxml.js` (docx/pptx/xlsx ⇄ JSON), `odf.js` (odt/ods/odp/odg ⇄ JSON), `document-schema.js` (the shared `ContentDocument`/`LayoutDocument` schemas), `pdf-codec` (the PDF codec itself, plus the text-layout/font-resolution/byte/image primitives built on it), and `markdown-codec` (CommonMark+GFM ⇄ `ContentDocument`). No `PdfObject`/`PdfDict`/`PdfStream` type appears anywhere in this package at all — that type is pdf-codec's own internal concern now, never exposed across the package boundary.
|
|
405
438
|
|
|
406
439
|
## Build, test, and lint
|
|
407
440
|
|
|
@@ -411,7 +444,7 @@ pnpm typecheck # tsc --noEmit
|
|
|
411
444
|
pnpm lint # eslint . --max-warnings 0
|
|
412
445
|
pnpm test # vitest run --project unit
|
|
413
446
|
pnpm test:watch # vitest --project unit
|
|
414
|
-
pnpm test:smoke # rebuilds dist/, then verifies ESM/CJS parity, a real docxToPdf/pdfToDocx round trip, real odtToPdf/odpToPdf/odsToPdf/odgToPdf conversions (odgToPdf's own fixture carries a real curved path, proving writePath reaches the built dist/ bundle), a real createOdp/odpToPdf/pdfToOdp round trip, a real odsToPdf/pdfToOds round trip plus a separate createOds/printSettings/buildOdsPackage exercise, a real createOdg/odgToPdf/pdfToOdg round trip (a curved path, a filled rect, and text, built entirely through the odg live-view editor, converted to PDF and reconstructed back to odg via reconstructDrawing), a real odfToPdf conversion (a fraction, rendered via the embedded STIX Two Math font -- checked by confirming the built PDF contains a real /Type0/Identity-H/CIDFontType0C font resource, proving the base64-embedded font asset itself survived the tsdown build),
|
|
447
|
+
pnpm test:smoke # rebuilds dist/, then verifies ESM/CJS parity, a real docxToPdf/pdfToDocx round trip, real odtToPdf/odpToPdf/odsToPdf/odgToPdf conversions (odgToPdf's own fixture carries a real curved path, proving writePath reaches the built dist/ bundle), a real createOdp/odpToPdf/pdfToOdp round trip, a real odsToPdf/pdfToOds round trip plus a separate createOds/printSettings/buildOdsPackage exercise, a real createOdg/odgToPdf/pdfToOdg round trip (a curved path, a filled rect, and text, built entirely through the odg live-view editor, converted to PDF and reconstructed back to odg via reconstructDrawing), a real odfToPdf conversion (a fraction, rendered via the embedded STIX Two Math font -- checked by confirming the built PDF contains a real /Type0/Identity-H/CIDFontType0C font resource, proving the base64-embedded font asset itself survived the tsdown build), a real markdownToPdf/pdfToMarkdown round trip plus a markdownToDocx bridge exercise, and real font resolution in docxToPdf (a Calibri run producing a genuine /Type0/Identity-H/CIDFontType2/FontFile2 Carlito font program, alongside an Arial control run that embeds nothing at all), from the built CJS bundle
|
|
415
448
|
```
|
|
416
449
|
|
|
417
450
|
The optional real-world PDF conformance corpus (`test:corpus` in the family's earlier layout) now lives in `pdf-codec`'s own repository, since it exercises the PDF codec directly rather than anything docx/pptx/odt/odp/ods/odg-specific — see that package's own README.
|
|
@@ -455,7 +488,7 @@ To run a single test file: `pnpm vitest run src/path/to/file.test.ts`.
|
|
|
455
488
|
- **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.
|
|
456
489
|
- **`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.
|
|
457
490
|
- **A cell's declared border renders solid whatever its `style` says, because the layout schema has nowhere to carry a dash pattern.** `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. What does not survive is `ContentBorder.style` (`solid`/`dashed`/`dotted`/`double`) — `LayoutLineSchema` is kind/x1/y1/x2/y2/color/widthPt and `LayoutPathSchema`'s own stroke is color/widthPt, so there is no dash array to write and nothing in pdf-codec to read one from. Rendering `double` as two hand-offset parallel lines was considered and rejected: the offset distance is nowhere in the model, so it would be an invented constant standing in for information the source never carried.
|
|
458
|
-
- **Ordinary text in PDF output
|
|
491
|
+
- **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).
|
|
459
492
|
- **Justified docx/odt paragraphs now stretch real inter-word gaps — but only in the flow engine (`engine.ts`), not in the direct-placement one (`slides.ts`) or the spreadsheet one (`sheets.ts`).** `justifyLineGapsPt` (`src/layout/shared.ts`) recovers each wrapped line's own word-gap positions from `WrappedLine`'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` and `layoutParagraphInCell` (both `engine.ts`, so this covers docx/odt paragraphs, docx/odt tables, and odm-assembled chapters, since all three route through this one engine) 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's own convention. `alignmentOffsetPt` itself still returns `0` for `'justify'`, 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 the caller applies on top, not a new branch inside it. `slides.ts` (pptx/odp shape text and slide-table cells) and `sheets.ts` (spreadsheet cell text) still call only `alignmentOffsetPt`, with no `justifyLineGapsPt` call anywhere in either file — a justified paragraph inside a pptx/odp shape, or a `'justify'`-aligned spreadsheet cell, still renders left-aligned. A real, tracked, bounded gap, not a silent one: closing it needs the identical per-fragment stretch wired into two more call sites, not new algorithmic work.
|
|
460
493
|
- **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.
|
|
461
494
|
- **PDF → docx/pptx/odt/odp reconstruction recovers a table only from a real drawn gridline lattice, and never from text alignment.** `reconstructWordprocessing`/`reconstructPresentation` run the identical detector, thresholds, and span-consistency check `reconstructSpreadsheet` gates its own cell boundaries on (`src/layout/lattice.ts`), and synthesize a real `ContentTable` when — and only when — one fires. Aligned columns of text with wide gaps are deliberately **not** accepted as evidence: several left-aligned lines separated by a tab-sized gap are indistinguishable, from geometry alone, from a genuinely tabbed paragraph, an indented code sample, or a two-column page layout, so building a table out of one would be inventing structure the source never had rather than recovering structure it did. A wide horizontal gap on a line still becomes a tab character, exactly as before. A lattice with no text inside it is rejected too (a grid of empty boxes is far more likely a decorative frame, a chart's plot area, or a form's field outlines than a table). Where a table IS recovered it reaches the output bytes for real — `buildDocxPackage`/`buildOdtPackage` write a real table, `buildPptxPackage`/`buildOdpPackage` a real slide table — with column widths and row heights measured directly from the drawn boundaries, and the lattice's own strokes reported once, as the table's structure, rather than also as loose vectors alongside it. Gradients and shadings are still not recovered at all.
|
|
@@ -512,7 +545,7 @@ To run a single test file: `pnpm vitest run src/path/to/file.test.ts`.
|
|
|
512
545
|
|
|
513
546
|
## Fidelity
|
|
514
547
|
|
|
515
|
-
**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 — see the
|
|
548
|
+
**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.
|
|
516
549
|
|
|
517
550
|
**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. The honest limits: stretchy delimiters render at a fixed size rather than dynamically assembling to their content's own height (the `MathVariants` subtable isn't parsed), a token's own box height comes from the font's nominal ascent/descent rather than a tight per-glyph ink bound, `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 — see the Gotchas entries above for the exact boundary of each. **`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.
|
|
518
551
|
|
package/dist/convert/convert.cjs
CHANGED
|
@@ -8,6 +8,7 @@ const require_edit_odt_content = require("../edit/odt/content.cjs");
|
|
|
8
8
|
const require_edit_odp_content = require("../edit/odp/content.cjs");
|
|
9
9
|
const require_edit_ods_content = require("../edit/ods/content.cjs");
|
|
10
10
|
const require_edit_odg_content = require("../edit/odg/content.cjs");
|
|
11
|
+
const require_fonts_registry = require("../fonts/registry.cjs");
|
|
11
12
|
const require_mathml_layout = require("../mathml/layout.cjs");
|
|
12
13
|
const require_ooxml_docx_read = require("../ooxml/docx/read.cjs");
|
|
13
14
|
const require_ooxml_pptx_read = require("../ooxml/pptx/read.cjs");
|
|
@@ -37,7 +38,11 @@ function docxToPdf(bytes, options) {
|
|
|
37
38
|
const pkg = require_edit_docx_editor.openDocx(bytes).toPackage();
|
|
38
39
|
const content = require_ooxml_docx_read.readDocxContent(pkg);
|
|
39
40
|
if (content.kind !== "wordprocessing") throw new Error("readDocxContent returned a non-wordprocessing ContentDocument");
|
|
40
|
-
const
|
|
41
|
+
const fonts = require_fonts_registry.createDocumentFontRegistry({
|
|
42
|
+
kind: "docx",
|
|
43
|
+
package: pkg
|
|
44
|
+
}, options);
|
|
45
|
+
const { document: layout, formulas } = require_layout_engine.convertWordprocessingToLayout(content, { measurer: (0, pdf_codec.createFontMeasurer)(fonts) });
|
|
41
46
|
options?.onDocument?.({
|
|
42
47
|
formatVersion: document_schema_js.DOCUMENT_PACKAGE_FORMAT_VERSION,
|
|
43
48
|
content,
|
|
@@ -46,14 +51,19 @@ function docxToPdf(bytes, options) {
|
|
|
46
51
|
return (0, pdf_codec.writePdf)(layout, {
|
|
47
52
|
signal: options?.signal,
|
|
48
53
|
onSubstitution: options?.onSubstitution,
|
|
49
|
-
formulas
|
|
54
|
+
formulas,
|
|
55
|
+
fonts
|
|
50
56
|
});
|
|
51
57
|
}
|
|
52
58
|
function odtToPdf(bytes, options) {
|
|
53
59
|
const pkg = (0, odf_js.decodePackage)(bytes);
|
|
54
60
|
const content = require_odf_odt_read.readOdtContent(pkg);
|
|
55
61
|
if (content.kind !== "wordprocessing") throw new Error("readOdtContent returned a non-wordprocessing ContentDocument");
|
|
56
|
-
const
|
|
62
|
+
const fonts = require_fonts_registry.createDocumentFontRegistry({
|
|
63
|
+
kind: "odf",
|
|
64
|
+
package: pkg
|
|
65
|
+
}, options);
|
|
66
|
+
const { document: layout, formulas } = require_layout_engine.convertWordprocessingToLayout(content, { measurer: (0, pdf_codec.createFontMeasurer)(fonts) });
|
|
57
67
|
options?.onDocument?.({
|
|
58
68
|
formatVersion: document_schema_js.DOCUMENT_PACKAGE_FORMAT_VERSION,
|
|
59
69
|
content,
|
|
@@ -62,14 +72,19 @@ function odtToPdf(bytes, options) {
|
|
|
62
72
|
return (0, pdf_codec.writePdf)(layout, {
|
|
63
73
|
signal: options?.signal,
|
|
64
74
|
onSubstitution: options?.onSubstitution,
|
|
65
|
-
formulas
|
|
75
|
+
formulas,
|
|
76
|
+
fonts
|
|
66
77
|
});
|
|
67
78
|
}
|
|
68
79
|
function pptxToPdf(bytes, options) {
|
|
69
80
|
const pkg = require_edit_pptx_editor.openPptx(bytes).toPackage();
|
|
70
81
|
const content = require_ooxml_pptx_read.readPptxContent(pkg);
|
|
71
82
|
if (content.kind !== "presentation") throw new Error("readPptxContent returned a non-presentation ContentDocument");
|
|
72
|
-
const
|
|
83
|
+
const fonts = require_fonts_registry.createDocumentFontRegistry({
|
|
84
|
+
kind: "pptx",
|
|
85
|
+
package: pkg
|
|
86
|
+
}, options);
|
|
87
|
+
const { document: layout, formulas } = require_layout_slides.convertPresentationToLayout(content, { measurer: (0, pdf_codec.createFontMeasurer)(fonts) });
|
|
73
88
|
options?.onDocument?.({
|
|
74
89
|
formatVersion: document_schema_js.DOCUMENT_PACKAGE_FORMAT_VERSION,
|
|
75
90
|
content,
|
|
@@ -78,14 +93,19 @@ function pptxToPdf(bytes, options) {
|
|
|
78
93
|
return (0, pdf_codec.writePdf)(layout, {
|
|
79
94
|
signal: options?.signal,
|
|
80
95
|
onSubstitution: options?.onSubstitution,
|
|
81
|
-
formulas
|
|
96
|
+
formulas,
|
|
97
|
+
fonts
|
|
82
98
|
});
|
|
83
99
|
}
|
|
84
100
|
function odpToPdf(bytes, options) {
|
|
85
101
|
const pkg = (0, odf_js.decodePackage)(bytes);
|
|
86
102
|
const content = require_odf_odp_read.readOdpContent(pkg);
|
|
87
103
|
if (content.kind !== "presentation") throw new Error("readOdpContent returned a non-presentation ContentDocument");
|
|
88
|
-
const
|
|
104
|
+
const fonts = require_fonts_registry.createDocumentFontRegistry({
|
|
105
|
+
kind: "odf",
|
|
106
|
+
package: pkg
|
|
107
|
+
}, options);
|
|
108
|
+
const { document: layout, formulas } = require_layout_slides.convertPresentationToLayout(content, { measurer: (0, pdf_codec.createFontMeasurer)(fonts) });
|
|
89
109
|
options?.onDocument?.({
|
|
90
110
|
formatVersion: document_schema_js.DOCUMENT_PACKAGE_FORMAT_VERSION,
|
|
91
111
|
content,
|
|
@@ -94,15 +114,20 @@ function odpToPdf(bytes, options) {
|
|
|
94
114
|
return (0, pdf_codec.writePdf)(layout, {
|
|
95
115
|
signal: options?.signal,
|
|
96
116
|
onSubstitution: options?.onSubstitution,
|
|
97
|
-
formulas
|
|
117
|
+
formulas,
|
|
118
|
+
fonts
|
|
98
119
|
});
|
|
99
120
|
}
|
|
100
121
|
function odsToPdf(bytes, options) {
|
|
101
122
|
const pkg = (0, odf_js.decodePackage)(bytes);
|
|
102
123
|
const content = require_odf_ods_read.readOdsContent(pkg);
|
|
103
124
|
if (content.kind !== "spreadsheet") throw new Error("readOdsContent returned a non-spreadsheet ContentDocument");
|
|
125
|
+
const fonts = require_fonts_registry.createDocumentFontRegistry({
|
|
126
|
+
kind: "odf",
|
|
127
|
+
package: pkg
|
|
128
|
+
}, options);
|
|
104
129
|
const layout = require_layout_sheets.convertSpreadsheetToLayout(content, {
|
|
105
|
-
measurer: (0, pdf_codec.
|
|
130
|
+
measurer: (0, pdf_codec.createFontMeasurer)(fonts),
|
|
106
131
|
signal: options?.signal
|
|
107
132
|
});
|
|
108
133
|
options?.onDocument?.({
|
|
@@ -112,14 +137,19 @@ function odsToPdf(bytes, options) {
|
|
|
112
137
|
});
|
|
113
138
|
return (0, pdf_codec.writePdf)(layout, {
|
|
114
139
|
signal: options?.signal,
|
|
115
|
-
onSubstitution: options?.onSubstitution
|
|
140
|
+
onSubstitution: options?.onSubstitution,
|
|
141
|
+
fonts
|
|
116
142
|
});
|
|
117
143
|
}
|
|
118
144
|
function odgToPdf(bytes, options) {
|
|
119
145
|
const pkg = (0, odf_js.decodePackage)(bytes);
|
|
120
146
|
const content = require_odf_odg_read.readOdgContent(pkg);
|
|
121
147
|
if (content.kind !== "drawing") throw new Error("readOdgContent returned a non-drawing ContentDocument");
|
|
122
|
-
const
|
|
148
|
+
const fonts = require_fonts_registry.createDocumentFontRegistry({
|
|
149
|
+
kind: "odf",
|
|
150
|
+
package: pkg
|
|
151
|
+
}, options);
|
|
152
|
+
const layout = require_layout_drawing.convertDrawingToLayout(content, { measurer: (0, pdf_codec.createFontMeasurer)(fonts) });
|
|
123
153
|
options?.onDocument?.({
|
|
124
154
|
formatVersion: document_schema_js.DOCUMENT_PACKAGE_FORMAT_VERSION,
|
|
125
155
|
content,
|
|
@@ -127,7 +157,8 @@ function odgToPdf(bytes, options) {
|
|
|
127
157
|
});
|
|
128
158
|
return (0, pdf_codec.writePdf)(layout, {
|
|
129
159
|
signal: options?.signal,
|
|
130
|
-
onSubstitution: options?.onSubstitution
|
|
160
|
+
onSubstitution: options?.onSubstitution,
|
|
161
|
+
fonts
|
|
131
162
|
});
|
|
132
163
|
}
|
|
133
164
|
function markdownToPdf(bytes, options) {
|
|
@@ -135,7 +166,11 @@ function markdownToPdf(bytes, options) {
|
|
|
135
166
|
const text = require_markdown_text.decodeMarkdownText(bytes);
|
|
136
167
|
const content = require_markdown_read.readMarkdownContent(text, { signal: options?.signal });
|
|
137
168
|
if (content.kind !== "wordprocessing") throw new Error("readMarkdownContent returned a non-wordprocessing ContentDocument");
|
|
138
|
-
const
|
|
169
|
+
const fonts = (0, pdf_codec.createFontRegistry)({
|
|
170
|
+
fonts: options?.fonts,
|
|
171
|
+
onSubstitution: options?.onFontSubstitution
|
|
172
|
+
});
|
|
173
|
+
const { document: layout, formulas } = require_layout_engine.convertWordprocessingToLayout(content, { measurer: (0, pdf_codec.createFontMeasurer)(fonts) });
|
|
139
174
|
options?.onDocument?.({
|
|
140
175
|
formatVersion: document_schema_js.DOCUMENT_PACKAGE_FORMAT_VERSION,
|
|
141
176
|
content,
|
|
@@ -144,7 +179,8 @@ function markdownToPdf(bytes, options) {
|
|
|
144
179
|
return (0, pdf_codec.writePdf)(layout, {
|
|
145
180
|
signal: options?.signal,
|
|
146
181
|
onSubstitution: options?.onSubstitution,
|
|
147
|
-
formulas
|
|
182
|
+
formulas,
|
|
183
|
+
fonts
|
|
148
184
|
});
|
|
149
185
|
}
|
|
150
186
|
const STANDALONE_FORMULA_SIZE_PT = 18;
|
|
@@ -413,7 +449,9 @@ function xlsxToPdf(bytes, options) {
|
|
|
413
449
|
return odsToPdf(odsBytes, {
|
|
414
450
|
signal: options?.signal,
|
|
415
451
|
onSubstitution: options?.onSubstitution,
|
|
416
|
-
onDocument: options?.onDocument
|
|
452
|
+
onDocument: options?.onDocument,
|
|
453
|
+
fonts: options?.fonts,
|
|
454
|
+
onFontSubstitution: options?.onFontSubstitution
|
|
417
455
|
});
|
|
418
456
|
}
|
|
419
457
|
function pdfToXlsx(bytes, options) {
|
|
@@ -469,6 +507,10 @@ function odmToPdf(bytes, options) {
|
|
|
469
507
|
const odm = (0, odf_js.readOdm)(pkg);
|
|
470
508
|
const unresolvedHrefs = [];
|
|
471
509
|
const chapterSections = [];
|
|
510
|
+
const sourceFonts = require_fonts_registry.extractSourceFonts({
|
|
511
|
+
kind: "odf",
|
|
512
|
+
package: pkg
|
|
513
|
+
});
|
|
472
514
|
for (const section of odm.sections) {
|
|
473
515
|
require_ports_abort.throwIfAborted(options?.signal);
|
|
474
516
|
if (section.inlineContent !== void 0) {
|
|
@@ -481,6 +523,10 @@ function odmToPdf(bytes, options) {
|
|
|
481
523
|
continue;
|
|
482
524
|
}
|
|
483
525
|
const chapterPkg = (0, odf_js.decodePackage)(chapterBytes);
|
|
526
|
+
sourceFonts.push(...require_fonts_registry.extractSourceFonts({
|
|
527
|
+
kind: "odf",
|
|
528
|
+
package: chapterPkg
|
|
529
|
+
}));
|
|
484
530
|
const chapterContent = require_odf_odt_read.readOdtContent(chapterPkg);
|
|
485
531
|
if (chapterContent.kind !== "wordprocessing") throw new Error("readOdtContent returned a non-wordprocessing ContentDocument");
|
|
486
532
|
chapterSections.push(chapterContent.sections);
|
|
@@ -501,11 +547,17 @@ function odmToPdf(bytes, options) {
|
|
|
501
547
|
metadata: (0, odf_js.readOdfMetadata)(pkg),
|
|
502
548
|
sections: combinedSections
|
|
503
549
|
};
|
|
504
|
-
const
|
|
550
|
+
const fonts = (0, pdf_codec.createFontRegistry)({
|
|
551
|
+
sourceFonts,
|
|
552
|
+
fonts: options?.fonts,
|
|
553
|
+
onSubstitution: options?.onFontSubstitution
|
|
554
|
+
});
|
|
555
|
+
const { document: layout, formulas } = require_layout_engine.convertWordprocessingToLayout(content, { measurer: (0, pdf_codec.createFontMeasurer)(fonts) });
|
|
505
556
|
return (0, pdf_codec.writePdf)(layout, {
|
|
506
557
|
signal: options?.signal,
|
|
507
558
|
onSubstitution: options?.onSubstitution,
|
|
508
|
-
formulas
|
|
559
|
+
formulas,
|
|
560
|
+
fonts
|
|
509
561
|
});
|
|
510
562
|
}
|
|
511
563
|
function odbToXlsx(bytes, options) {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { n as HsqldbDecodeOptions } from "../rowformat-Cl2exlEh.cjs";
|
|
2
2
|
import { t as OmmlDiagnostic } from "../write-DuyfnbL_.cjs";
|
|
3
|
+
import { t as DocumentFontRegistryOptions } from "../registry-CtPhMD0f.cjs";
|
|
3
4
|
import { ContentSection, DocumentPackage } from "document-schema.js";
|
|
4
5
|
import { OdmSection, Package } from "odf.js";
|
|
5
6
|
import { PdfDiagnosticSink, WinAnsiSubstitution } from "pdf-codec";
|
|
6
7
|
//#region src/convert/convert.d.ts
|
|
7
|
-
interface DocumentToPdfOptions {
|
|
8
|
+
interface DocumentToPdfOptions extends DocumentFontRegistryOptions {
|
|
8
9
|
readonly signal?: AbortSignal;
|
|
9
10
|
readonly onSubstitution?: (substitution: WinAnsiSubstitution, context: {
|
|
10
11
|
readonly pageIndex: number;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { n as HsqldbDecodeOptions } from "../rowformat-Cl2exlEh.js";
|
|
2
2
|
import { t as OmmlDiagnostic } from "../write-Zqwx6pka.js";
|
|
3
|
+
import { t as DocumentFontRegistryOptions } from "../registry-CrSqLIcn.js";
|
|
3
4
|
import { ContentSection, DocumentPackage } from "document-schema.js";
|
|
4
5
|
import { OdmSection, Package } from "odf.js";
|
|
5
6
|
import { PdfDiagnosticSink, WinAnsiSubstitution } from "pdf-codec";
|
|
6
7
|
//#region src/convert/convert.d.ts
|
|
7
|
-
interface DocumentToPdfOptions {
|
|
8
|
+
interface DocumentToPdfOptions extends DocumentFontRegistryOptions {
|
|
8
9
|
readonly signal?: AbortSignal;
|
|
9
10
|
readonly onSubstitution?: (substitution: WinAnsiSubstitution, context: {
|
|
10
11
|
readonly pageIndex: number;
|