documents.js 1.43.0 → 1.44.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 +35 -12
- package/dist/index.cjs +219 -9
- package/dist/index.d.cts +209 -3
- package/dist/index.d.ts +209 -3
- package/dist/index.js +183 -12
- package/package.json +2 -2
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
|
-
> Bidirectional docx/pptx/odt/odp ⇄ PDF conversion, a read-and-write live-view editor for docx/pptx/odt/odp content, and a fully hand-written PDF codec, built on [ooxml.js](https://github.com/ExaDev/ooxml.js) and [odf.js](https://github.com/ExaDev/odf.js).
|
|
5
|
+
> Bidirectional docx/pptx/odt/odp ⇄ PDF conversion, one-directional ods/odg → PDF conversion, a read-and-write live-view editor for docx/pptx/odt/odp/ods content, and a fully hand-written PDF codec, built on [ooxml.js](https://github.com/ExaDev/ooxml.js) and [odf.js](https://github.com/ExaDev/odf.js).
|
|
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 entirely hand-written: no external PDF library (`pdf-lib`, `pdfjs-dist`, `mupdf`, or any other) is a dependency. The one exception is [`fflate`](https://github.com/101arrowz/fflate) for raw DEFLATE/zlib compression underneath PDF's `FlateDecode` filter and PNG's `IDAT` chunks — the same dependency `ooxml.js` itself already relies on for ZIP handling.
|
|
8
8
|
|
|
@@ -30,10 +30,10 @@ npm install documents.js
|
|
|
30
30
|
|
|
31
31
|
## Usage
|
|
32
32
|
|
|
33
|
-
The eight round-trip ergonomic conversions (docx/pptx/odt/odp ⇄ PDF):
|
|
33
|
+
The eight round-trip ergonomic conversions (docx/pptx/odt/odp ⇄ PDF), plus `odsToPdf`/`odgToPdf`'s one-directional additions (there is no `pdfToOds` yet — PDF → spreadsheet reconstruction needs general vector-path tracking in the PDF reader that doesn't exist yet; there is no `pdfToOdg` either, for the equivalent reason on the drawing side — see [Gotchas](#gotchas-and-quirks)):
|
|
34
34
|
|
|
35
35
|
```ts
|
|
36
|
-
import { docxToPdf, odpToPdf, odtToPdf, pdfToDocx, pdfToOdp, pdfToOdt, pptxToPdf, pdfToPptx } from 'documents.js';
|
|
36
|
+
import { docxToPdf, odgToPdf, odpToPdf, odsToPdf, odtToPdf, pdfToDocx, pdfToOdp, pdfToOdt, pptxToPdf, pdfToPptx } from 'documents.js';
|
|
37
37
|
|
|
38
38
|
const pdfBytes = docxToPdf(docxBytes);
|
|
39
39
|
const docxBytes2 = pdfToDocx(pdfBytes);
|
|
@@ -46,6 +46,9 @@ const odtBytes2 = pdfToOdt(pdfFromOdt);
|
|
|
46
46
|
|
|
47
47
|
const pdfFromOdp = odpToPdf(odpBytes);
|
|
48
48
|
const odpBytes2 = pdfToOdp(pdfFromOdp);
|
|
49
|
+
|
|
50
|
+
const pdfFromOds = odsToPdf(odsBytes); // ods -> PDF only -- there is no pdfToOds yet
|
|
51
|
+
const pdfFromOdg = odgToPdf(odgBytes); // odg -> PDF only -- there is no pdfToOdg yet
|
|
49
52
|
```
|
|
50
53
|
|
|
51
54
|
Each accepts an optional `signal` (`AbortSignal`) and either a `onSubstitution` callback (docx/pptx/odt/odp → PDF, called once per character not representable in a standard-14 font) or a `sink` (PDF → docx/pptx/odt/odp, called once per recoverable parse diagnostic).
|
|
@@ -97,6 +100,19 @@ slide.notes = 'Speaker notes for this slide';
|
|
|
97
100
|
const bytes = editor.toBytes();
|
|
98
101
|
```
|
|
99
102
|
|
|
103
|
+
`createOds`/`openOds` and `OdsEditor`/`OdsSheet`/`OdsCell` are the spreadsheet equivalent — cell addressing has no docx/pptx analogue at all, so this is the one editor family built from scratch rather than reusing `OdtParagraph`/`OdtRun`. Setting a cell far from the origin does not materialise every cell in between: the underlying `table:number-columns-repeated`/`table:number-rows-repeated` runs are split in place at exactly the target position, the same repeat-compression convention `odf.js`'s own reader already reads.
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
import { createOds } from 'documents.js';
|
|
107
|
+
|
|
108
|
+
const editor = createOds();
|
|
109
|
+
const sheet = editor.addSheet('Sheet1');
|
|
110
|
+
sheet.cell(0, 0).value = { kind: 'string', value: 'Total' }; // 0-based (row, column) -- there is no A1-string overload
|
|
111
|
+
sheet.cell(0, 1).value = { kind: 'currency', value: 42.5, currency: 'USD' };
|
|
112
|
+
sheet.cell(500, 50).value = { kind: 'boolean', value: true }; // does not materialise 500x50 empty cells
|
|
113
|
+
const bytes = editor.toBytes();
|
|
114
|
+
```
|
|
115
|
+
|
|
100
116
|
Reading and writing PDF bytes directly, without going through docx/pptx:
|
|
101
117
|
|
|
102
118
|
```ts
|
|
@@ -119,24 +135,24 @@ const pdfFromDocx = z.decode(docxPdfCodec, docxBytes);
|
|
|
119
135
|
const docxBack = z.encode(docxPdfCodec, pdfFromDocx);
|
|
120
136
|
```
|
|
121
137
|
|
|
122
|
-
`readDocxContent`/`readPptxContent`/`readOdtContent`/`readOdpContent` (docx/pptx/odt/odp → `ContentDocument`), `convertWordprocessingToLayout`/`convertPresentationToLayout` (`ContentDocument` → `LayoutDocument`), and `reconstructWordprocessing`/`reconstructPresentation` (`LayoutDocument` → `ContentDocument`) are each exported individually too, for a caller that wants one stage of the pipeline without the rest. `readDocxContent` and `readOdtContent` both produce the identical `wordprocessing`-variant `ContentDocument` shape from two completely unrelated package formats (OOXML and ODF), which is what lets `odtToPdf` feed `convertWordprocessingToLayout` without a single line of that engine changing; `readPptxContent` and `readOdpContent` do the same for the `presentation` variant and `convertPresentationToLayout`.
|
|
138
|
+
`readDocxContent`/`readPptxContent`/`readOdtContent`/`readOdpContent`/`readOdsContent`/`readOdgContent` (docx/pptx/odt/odp/ods/odg → `ContentDocument`), `convertWordprocessingToLayout`/`convertPresentationToLayout`/`convertSpreadsheetToLayout`/`convertDrawingToLayout` (`ContentDocument` → `LayoutDocument`), and `reconstructWordprocessing`/`reconstructPresentation` (`LayoutDocument` → `ContentDocument`) are each exported individually too, for a caller that wants one stage of the pipeline without the rest. `readDocxContent` and `readOdtContent` both produce the identical `wordprocessing`-variant `ContentDocument` shape from two completely unrelated package formats (OOXML and ODF), which is what lets `odtToPdf` feed `convertWordprocessingToLayout` without a single line of that engine changing; `readPptxContent` and `readOdpContent` do the same for the `presentation` variant and `convertPresentationToLayout`. `readOdsContent`/`convertSpreadsheetToLayout` and `readOdgContent`/`convertDrawingToLayout` each have no OOXML-side counterpart at all (no `readXlsxContent`/xlsx layout, no drawing-equivalent OOXML format this package reads) — both `convertSpreadsheetToLayout` and `convertDrawingToLayout` are genuinely new layout algorithms, since a spreadsheet's addressed-grid-with-print-settings semantics and a drawing's vector-primitive vocabulary (rect/ellipse/line/path) have no flow/pagination or direct-placement analogue; `convertDrawingToLayout` does still reuse `convertPresentationToLayout`'s own shape-conversion logic (`convertShape`, exported from `src/layout/slides.ts`) verbatim for whatever text/image/table content a drawing page also carries. There is no `reconstructSpreadsheet` or `reconstructDrawing` yet (see [Gotchas](#gotchas-and-quirks)).
|
|
123
139
|
|
|
124
140
|
## Architecture
|
|
125
141
|
|
|
126
142
|
The package is layered from generic primitives outward to the two conversion directions:
|
|
127
143
|
|
|
128
|
-
- **`src/model/`** — thin, documents.js-specific additions on top of the sibling [`document-content-model`](https://github.com/ExaDev/document-content-model) package, which now owns the two pivot models themselves: `LayoutDocument` (the PDF-side pivot: pages of positioned text/image/rect/line/ellipse/link items, PDF-native coordinates and units) and `ContentDocument` (the semantic pivot: a discriminated union of `wordprocessing` and `
|
|
144
|
+
- **`src/model/`** — thin, documents.js-specific additions on top of the sibling [`document-content-model`](https://github.com/ExaDev/document-content-model) 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`, src/pdf/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`, and `drawing` variants 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`) are both imported, not defined here — `document-content-model` exists specifically so `ooxml.js`, `odf.js`, 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-content-model`'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-content-model` too (since `LayoutText`, part of the pivot, needs the field), leaving only the standard-14 font *resolution* logic that consumes it (`src/pdf/fonts.ts`/`font-read.ts`) as PDF-specific and local.
|
|
129
145
|
- **`src/bytes/`** and **`src/image/`** — generic byte and image-container primitives with zero PDF or OOXML knowledge: a chunked byte writer, a backtracking byte reader, CRC32, and a hand-written PNG decoder/encoder (palette/gray/RGB/alpha, multi-`IDAT` files, all five scanline filters) plus JPEG marker scanning for dimensions only — JPEG's compressed bytes pass through completely unchanged in both directions. `src/bytes/flate.ts` is the only file that imports `fflate`, mirroring how `ooxml.js`'s own `src/zip.ts` wraps it for ZIP handling.
|
|
130
146
|
- **`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.
|
|
131
|
-
- **`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`) wrapping the actual `XmlElement` objects inside a decoded `Package`, plus `buildDocxPackage`/`buildPptxPackage`/`buildOdtPackage`/`buildOdpPackage` bridging a `ContentDocument` to a fresh package built entirely through those same primitives. `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 — unlike `PptxShape`, which has no rotation setter yet (see Gotchas below).
|
|
147
|
+
- **`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`) wrapping the actual `XmlElement` objects inside a decoded `Package`, plus `buildDocxPackage`/`buildPptxPackage`/`buildOdtPackage`/`buildOdpPackage`/`buildOdsPackage` bridging a `ContentDocument` to a fresh package built entirely through those same primitives (there is no `pdfToOds` calling `buildOdsPackage` yet, though — see below). `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 — unlike `PptxShape`, which has no rotation setter yet (see Gotchas below). `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.
|
|
132
148
|
- **`src/pdf/`** — the hand-written PDF codec, importing only `model`/`bytes`/`image` (no OOXML knowledge at all):
|
|
133
149
|
- **Write**: `objects.ts` (the `PdfObject` discriminated union), `afm-widths.ts`/`encoding.ts`/`winansi.ts`/`fonts.ts` (standard-14 metrics, WinAnsi encoding, family resolution), `measure.ts`/`text-layout.ts` (greedy line-wrapping), `matrix.ts`, `content-write.ts` (`LayoutItem[]` → content-stream operators), `write.ts` (the full object graph, classic cross-reference table, trailer).
|
|
134
150
|
- **Read**: `lexer.ts`/`parse.ts` (byte tokenizer and tokens → `PdfObject`), `filters.ts`/`predictors.ts` (Flate/LZW/ASCII85/ASCIIHex/RunLength, TIFF/PNG predictors), `xref.ts`/`document.ts` (classic and cross-reference-stream resolution, object streams, `/Prev` chains, linear-scan recovery, the page tree with attribute inheritance), `content-read.ts`/`interpret.ts` (the content-stream tokenizer and graphics/text state machine, including form-XObject recursion), `cmap.ts`/`font-style.ts`/`font-read.ts` (`/ToUnicode` CMaps, font-dictionary resolution), `images-read.ts` (Image XObjects → PNG/JPEG bytes), `read.ts` (`readPdf`, assembling all of the above into a `LayoutDocument`).
|
|
135
151
|
- `codec.ts` — `pdfCodec`, a `z.codec()` pair over `readPdf`/`writePdf` (PDF bytes ⇄ `LayoutDocument`).
|
|
136
152
|
- **`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.
|
|
137
|
-
- **`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 `readOdp`, wrapping `{ metadata, slides }` into the identical `presentation` shape `readPptxContent` produces.
|
|
138
|
-
- **`src/layout/`** — the pure conversion algorithms, importing only `model` (no I/O): `engine.ts` (`ContentDocument` wordprocessing → `LayoutDocument`: flow, line-breaking, pagination — fed identically by docx- and odt-sourced content), `slides.ts` (`ContentDocument` presentation → `LayoutDocument`: direct EMU-to-point placement, no pagination needed — fed identically by pptx- and odp-sourced content), `reconstruct.ts` (`LayoutDocument` → `ContentDocument`,
|
|
139
|
-
- **`src/convert/`** — `convert.ts` (the eight round-trip ergonomic wrappers), `codec.ts` (`docxPdfCodec`/`pptxPdfCodec`/`odtPdfCodec`/`odpPdfCodec`, a `z.codec()` pair over each), `port.ts`/`local.ts` (the swappable `DocumentConverter` contract and its synchronous local implementation, covering `docx`/`pptx`/`odt`/`odp` → `pdf` and `pdf` → `docx`/`pptx`/`odt`/`odp`).
|
|
153
|
+
- **`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 `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 — neither has an OOXML-side sibling adapter (there is no `readXlsxContent`, and no drawing-equivalent OOXML format this package reads at all). Every direction now builds the reverse too, except odg's — `buildOdtPackage`/`buildOdpPackage`/`buildOdsPackage` (`src/edit/{odt,odp,ods}/content.ts`) — closing the PDF → odt/odp direction on the live-view editors above (ods's own `buildOdsPackage` exists and is exported, but nothing calls it yet — see the `pdfToOds` gotcha below; there is no `buildOdgPackage`/live-view odg editor at all yet, matching `pdfToOdg`'s own absence).
|
|
154
|
+
- **`src/layout/`** — the pure conversion algorithms, importing only `model` (no I/O): `engine.ts` (`ContentDocument` wordprocessing → `LayoutDocument`: flow, line-breaking, pagination — fed identically by docx- and odt-sourced content), `slides.ts` (`ContentDocument` presentation → `LayoutDocument`: direct EMU-to-point placement, no pagination needed — fed identically by pptx- and odp-sourced content; also exports `convertShape`, the single-`ContentShape`-to-`LayoutItem[]` conversion `drawing.ts` below reuses verbatim), `sheets.ts` (`ContentDocument` spreadsheet → `LayoutDocument`: resolve the print range, build cumulative column/row offsets skipping hidden ones, reserve header/repeat-row-column space, resolve an explicit or non-iterative fit-to-page scale, partition into column/row bands honouring manual breaks with the same "an oversized item gets its own band and overflows rather than looping" guarantee `engine.ts`'s `ensureRoom` documents, emit pages in `downThenOver`/`overThenDown` order, then per page paint backgrounds/gridlines/headers/cell text with default alignment by value kind and `###`/spill-then-truncate overflow handling — the first layout algorithm in this package that accepts an `AbortSignal`, since a 50k-cell sheet needs cancellation where a docx/pptx page count never did), `drawing.ts` (`ContentDocument` drawing → `LayoutDocument`: one `ContentDrawPage` per PDF page, direct placement like `slides.ts`, with one new emission path — a `ContentVector` `rect`/`ellipse`/`line` maps onto the pre-existing `LayoutRect`/`LayoutEllipse`/`LayoutLine` kinds, and a `path` vector's local, viewBox-relative subpath points are resolved through the vector's own frame offset then a single page-space flip into a `LayoutPath` value; vectors paint before shapes, a documented, bounded choice — see this module's own top-of-file note — since `ContentDrawPageSchema` keeps `shapes` and `vectors` as two independently paint-ordered arrays with no field recording their relative order when the two genuinely overlap), `reconstruct.ts` (`LayoutDocument` → `ContentDocument`, wordprocessing and presentation only — no `reconstructSpreadsheet`/`reconstructDrawing` yet, see Gotchas: baseline-proximity line clustering, then paragraph/text-block clustering from geometry — PDF has no semantic paragraph or shape structure to recover, only positioned glyphs).
|
|
155
|
+
- **`src/convert/`** — `convert.ts` (the eight round-trip ergonomic wrappers plus `odsToPdf`/`odgToPdf`'s one-directional ninth and tenth), `codec.ts` (`docxPdfCodec`/`pptxPdfCodec`/`odtPdfCodec`/`odpPdfCodec`, a `z.codec()` pair over each — deliberately no `odsPdfCodec`/`odgPdfCodec` yet, matching this package's own established rule that a codec needs both a genuine `decode` and `encode` half, and `odsToPdf`/`odgToPdf` alone each have no `pdfToOds`/`pdfToOdg` to encode with), `port.ts`/`local.ts` (the swappable `DocumentConverter` contract and its synchronous local implementation, covering `docx`/`pptx`/`odt`/`odp`/`ods`/`odg` → `pdf` and `pdf` → `docx`/`pptx`/`odt`/`odp`).
|
|
140
156
|
|
|
141
157
|
Dependency direction is strictly downward and checkable: `model`/`bytes` import nothing local; `image` imports `bytes` only; `pdf` imports `model`+`bytes`+`image` only; `ooxml/*` imports `xml`/`model` only (no PDF knowledge); `odf/*` imports `model` only (no PDF knowledge, no `xml/*` — `odf.js` already owns its own XML query helpers); `layout` imports `model` only; `convert` composes everything else. No `PdfObject`/`PdfDict`/`PdfStream` type appears outside `src/pdf/`.
|
|
142
158
|
|
|
@@ -148,7 +164,7 @@ pnpm typecheck # tsc --noEmit
|
|
|
148
164
|
pnpm lint # eslint . --max-warnings 0
|
|
149
165
|
pnpm test # vitest run --project unit
|
|
150
166
|
pnpm test:watch # vitest --project unit
|
|
151
|
-
pnpm test:smoke # rebuilds dist/, then verifies ESM/CJS parity, a real docxToPdf/pdfToDocx round trip, real odtToPdf/odpToPdf conversions, and a real createOdp/odpToPdf/pdfToOdp round trip, from the built CJS bundle
|
|
167
|
+
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), and a real createOdp/odpToPdf/pdfToOdp round trip, from the built CJS bundle
|
|
152
168
|
pnpm test:corpus # optional real-world PDF conformance checks against a local, gitignored test/corpus/ (see Fidelity)
|
|
153
169
|
```
|
|
154
170
|
|
|
@@ -170,6 +186,13 @@ To run a single test file: `pnpm vitest run src/path/to/file.test.ts`.
|
|
|
170
186
|
- **The docx⇄PDF and pptx⇄PDF conversions are explicitly not round-trip-lossless** — in deliberate contrast to `ooxml.js`'s own `packageCodec`, which is byte/part-faithful by design. See [Fidelity](#fidelity).
|
|
171
187
|
- **`odpToPdf`/`pdfToOdp` needed zero new layout code.** `readOdpContent` (`src/odf/odp/read.ts`) produces the identical `presentation` `ContentDocument` shape `readPptxContent` does, so it feeds `convertPresentationToLayout` unmodified — including the existing hidden-annotation speaker-notes mechanism below, which carries odp's `presentation:notes` through to the PDF with no new notes-handling code at all; `pdfToOdp` reuses `reconstructPresentation` unmodified too, the same architectural bet `pdfToOdt` already proved for `reconstructWordprocessing`. The genuinely new work for the reverse direction was the live-view editor itself (`src/edit/odp/*`) — see Architecture above.
|
|
172
188
|
- **`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. Unlike `PptxShape` (see the `colSpan`/`rowSpan` gotcha below, which pptx still has and odp does not), `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.
|
|
189
|
+
- **`odsToPdf` is one-directional — there is no `pdfToOds` yet, and no `reconstructSpreadsheet`.** Unlike odt/odp, going from PDF back to a spreadsheet needs general vector-path tracking in the PDF reader (`src/pdf/interpret.ts` currently only tracks the specific `re` rectangle operator, discarding general `m`/`l`/`c` path construction) so a reconstructed sheet's gridlines can be detected from the recovered geometry — that infrastructure doesn't exist yet. `buildOdsPackage` (`src/edit/ods/content.ts`) is built and exported, ready for `pdfToOds` to call the moment path tracking lands; nothing currently calls it.
|
|
190
|
+
- **`odgToPdf` is one-directional too — there is no `pdfToOdg`, no `reconstructDrawing`, and no live-view odg editor at all.** Reconstructing a `ContentDrawPage`'s own vector-primitive geometry (which recovered path is a rect vs. a genuine curve, where one shape ends and another begins) from PDF geometry alone is a fundamentally different, unstarted problem from `reconstructWordprocessing`/`reconstructPresentation`'s own paragraph/shape geometry clustering — the same `src/pdf/interpret.ts` gap `odsToPdf`'s own gotcha above describes (no general `m`/`l`/`c` path tracking) blocks this direction too, on top of needing its own reconstruction algorithm even once that infrastructure exists.
|
|
191
|
+
- **`LayoutPathSchema` (`document-content-model`) has no quadratic or elliptical-arc segment kind, deliberately — not a scope gap that happens to be unfilled.** `writePath` (`src/pdf/content-write.ts`) therefore has no quadratic-to-cubic elevation and no SVG-arc-to-cubic endpoint-to-centre parameterization anywhere in it: `odf.js`'s own real-LibreOffice-output-verified `svg:d` parser (`typed/shared/path.ts`) recognises `S`/`s`/`Q`/`q`/`T`/`t`/`A`/`a` as command letters (so its own token stream stays in sync) but produces no segment for any of them — real LibreOffice output for rectangles, ellipses, freeform curves, and basic custom-shape presets never emits a quadratic or an arc in the first place, only `M`/`L`/`H`/`V`/`C`/`Z`. Building unused quadratic/arc conversion code against a segment kind that can never occur would be speculative, not root-cause work.
|
|
192
|
+
- **A drawing page's `shapes` and `vectors` paint in two independently-ordered arrays, with no field recording their relative order.** `ContentDrawPageSchema` (`document-content-model`) keeps text/image/table content (`shapes`) and vector primitives (`vectors`) as two separate arrays, each correctly paint-ordered on its own by `odf.js`'s own reader (honouring a real `draw:z-index` when present, falling back to document order otherwise) — but there is no shared ordering field between the two arrays at all, a real, tracked gap in the shared schema, not something `convertDrawingToLayout` can reconstruct after the fact. `convertDrawingToLayout` resolves it with one fixed, documented choice: every vector paints before every shape (vectors are the common "diagram" content in a real `.odg`; shapes are far more often text labels layered on top of them than the reverse). A page that genuinely interleaves the two mid-stack will not paint in true document z-order until the schema itself grows a shared field.
|
|
193
|
+
- **A vector primitive's own rotation is never read at all.** None of `ContentVectorSchema`'s variants carry a rotation field, unlike `ContentShapeSchema` — `readOdgContent`'s underlying `odf.js` reader deliberately discards a `draw:rect`/`draw:ellipse`/`draw:custom-shape`'s own rotation, so it reads (and `convertDrawingToLayout` places) at its unrotated bounding frame. A real, tracked model limitation inherited from `odf.js`, not something this package's own layout code introduces.
|
|
194
|
+
- **`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.
|
|
195
|
+
- **`ContentSheetCellSchema` (`document-content-model`) models no per-cell border or background, and no per-cell alignment override** — unlike `ContentTableCellSchema.background`. `sheets.ts`'s cell-background and cell-border z-order steps are consequently skipped entirely (no dead placeholder code), and cell text alignment always falls back to the value-kind default (numeric right, boolean/error centre, string left) since there is nothing to override it with. A tracked, documented gap, not a silent one.
|
|
173
196
|
- **PDF output uses the standard 14 fonts only — no font embedding.** Helvetica/Times-Roman are genuinely metric-compatible substitutes for Arial/Times New Roman, but Word's actual current defaults (Calibri, Aptos) are not, so line wrapping and pagination will drift slightly from what Word itself would produce. Expect a faithful visual approximation, not a line-identical reproduction.
|
|
174
197
|
- **Reading arbitrary real-world PDFs is the single largest risk surface in this package**, and the parser is honest about its design target: cleanly-generated output from mainstream producers (Word, PowerPoint, Chrome, LibreOffice, Acrobat), recovering from the malformations those producers and their downstream tooling actually create, and failing loudly and specifically on anything else — not matching a mature library's robustness against adversarial input.
|
|
175
198
|
- **Encrypted PDFs are unsupported.** `/Encrypt` present in the trailer throws `PdfEncryptedError`, even for the common empty-user-password case.
|
|
@@ -182,7 +205,7 @@ To run a single test file: `pnpm vitest run src/path/to/file.test.ts`.
|
|
|
182
205
|
|
|
183
206
|
## Fidelity
|
|
184
207
|
|
|
185
|
-
**docx/pptx/odt/odp → 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). It is a faithful **visual approximation**, not a pixel- or line-identical reproduction of what Word/PowerPoint/Writer/Impress would themselves render — see the standard-14 font substitution gotcha above.
|
|
208
|
+
**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 standard-14 font substitution gotcha above.
|
|
186
209
|
|
|
187
210
|
**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.
|
|
188
211
|
|
|
@@ -204,7 +227,7 @@ Commits follow Conventional Commits (`feat:`, `fix:`, `test:`, `chore:`, …), e
|
|
|
204
227
|
|
|
205
228
|
- [ooxml.js](https://github.com/ExaDev/ooxml.js) — the sibling package this depends on for all docx/pptx/xlsx ⇄ JSON handling and cascade-resolved typed reading.
|
|
206
229
|
- [document-content-model](https://github.com/ExaDev/document-content-model) — the sibling package that owns `ContentDocument`/`LayoutDocument` themselves; both `ooxml.js` and `documents.js` import from it rather than each maintaining an independent copy.
|
|
207
|
-
- [odf.js](https://github.com/ExaDev/odf.js) — a sibling package doing the equivalent lossless-codec job for the OpenDocument Format (odt/ods/odp/odg/…), also built on `document-content-model`. A dependency of `documents.js` for: this package's `Odt`/`Ods`/`Odp`/`OdgBytesSchema` (`src/model/bytes.ts`), which validate against its `ODF_MEDIA_TYPES` table; `src/interop.test.ts`, a type-level guard that `ooxml.js`'s and `odf.js`'s raw `XmlElement`/`XmlNode`/`Attribute`/`Package` container types stay structurally compatible; `src/odf/odt/read.ts`'s `readOdtContent`, a thin adapter over `odf.js`'s own `readOdt`, feeding `odtToPdf`/`pdfToOdt` (`src/convert/convert.ts`); `src/odf/odp/read.ts`'s `readOdpContent`, the same adapter over `odf.js`'s `readOdp`, feeding `odpToPdf`/`pdfToOdp`; `src/edit/odt/*`'s `StyleRegistry`/`resolveStyle` (style interning) and `src/edit/odp/shape.ts`'s `applyOdfTransform`/`resolveOdfShapeGeometry` (rotation), both consumed directly rather than reimplemented. odt and odp → `ContentDocument` reading and PDF conversion are now integrated both ways; ods
|
|
230
|
+
- [odf.js](https://github.com/ExaDev/odf.js) — a sibling package doing the equivalent lossless-codec job for the OpenDocument Format (odt/ods/odp/odg/…), also built on `document-content-model`. A dependency of `documents.js` for: this package's `Odt`/`Ods`/`Odp`/`OdgBytesSchema` (`src/model/bytes.ts`), which validate against its `ODF_MEDIA_TYPES` table; `src/interop.test.ts`, a type-level guard that `ooxml.js`'s and `odf.js`'s raw `XmlElement`/`XmlNode`/`Attribute`/`Package` container types stay structurally compatible; `src/odf/odt/read.ts`'s `readOdtContent`, a thin adapter over `odf.js`'s own `readOdt`, feeding `odtToPdf`/`pdfToOdt` (`src/convert/convert.ts`); `src/odf/odp/read.ts`'s `readOdpContent`, the same adapter over `odf.js`'s `readOdp`, feeding `odpToPdf`/`pdfToOdp`; `src/odf/ods/read.ts`'s `readOdsContent`, the same adapter over `odf.js`'s `readOds`, feeding `odsToPdf`; `src/odf/odg/read.ts`'s `readOdgContent`, the same adapter over `odf.js`'s `readOdg` — including its own `typed/shared/path.ts`, the real-LibreOffice-output-verified `svg:d`/`draw:points` parser this package's `writePath` content is ultimately sourced from — feeding `odgToPdf`; `src/edit/odt/*`'s `StyleRegistry`/`resolveStyle` (style interning) and `src/edit/odp/shape.ts`'s `applyOdfTransform`/`resolveOdfShapeGeometry` (rotation), both consumed directly rather than reimplemented. odt and odp → `ContentDocument` reading and PDF conversion are now integrated both ways; ods and odg → `ContentDocument` reading and PDF conversion (the equivalent for spreadsheets and drawings) are integrated one-directionally (→ PDF only, no reverse direction yet).
|
|
208
231
|
|
|
209
232
|
## License
|
|
210
233
|
|
package/dist/index.cjs
CHANGED
|
@@ -24,6 +24,12 @@ const ContentDocumentSchema = zod.z.discriminatedUnion("kind", [
|
|
|
24
24
|
formatVersion: zod.z.literal(1),
|
|
25
25
|
metadata: document_content_model.LayoutMetadataSchema,
|
|
26
26
|
sheets: zod.z.array(document_content_model.ContentSheetSchema)
|
|
27
|
+
}),
|
|
28
|
+
zod.z.object({
|
|
29
|
+
kind: zod.z.literal("drawing"),
|
|
30
|
+
formatVersion: zod.z.literal(1),
|
|
31
|
+
metadata: document_content_model.LayoutMetadataSchema,
|
|
32
|
+
pages: zod.z.array(document_content_model.ContentDrawPageSchema)
|
|
27
33
|
})
|
|
28
34
|
]);
|
|
29
35
|
//#endregion
|
|
@@ -7604,11 +7610,15 @@ function writeText(writer, item, context, substitutions) {
|
|
|
7604
7610
|
writeUnderline(writer, item, item.widthPt ?? encoded.width1000 / 1e3 * item.sizePt * scale, context.measurer);
|
|
7605
7611
|
}
|
|
7606
7612
|
}
|
|
7607
|
-
function paintOperatorFor(fill, stroke) {
|
|
7608
|
-
|
|
7609
|
-
if (fill !== void 0) return "
|
|
7613
|
+
function paintOperatorFor(fill, stroke, fillRule) {
|
|
7614
|
+
const evenOdd = fillRule === "evenodd";
|
|
7615
|
+
if (fill !== void 0 && stroke !== void 0) return evenOdd ? "B*" : "B";
|
|
7616
|
+
if (fill !== void 0) return evenOdd ? "f*" : "f";
|
|
7610
7617
|
if (stroke !== void 0) return "S";
|
|
7611
7618
|
}
|
|
7619
|
+
function formatPoint(x, y) {
|
|
7620
|
+
return `${formatNumber(x)} ${formatNumber(y)}`;
|
|
7621
|
+
}
|
|
7612
7622
|
function writeFillAndStroke(writer, fill, stroke) {
|
|
7613
7623
|
if (fill !== void 0) writeRgbOperator(writer, fill, "rg");
|
|
7614
7624
|
if (stroke !== void 0) {
|
|
@@ -7639,12 +7649,24 @@ function writeEllipse(writer, item) {
|
|
|
7639
7649
|
const ry = item.heightPt / 2;
|
|
7640
7650
|
const kx = rx * BEZIER_KAPPA;
|
|
7641
7651
|
const ky = ry * BEZIER_KAPPA;
|
|
7642
|
-
|
|
7643
|
-
writer.writeAscii(`${
|
|
7644
|
-
writer.writeAscii(`${
|
|
7645
|
-
writer.writeAscii(`${
|
|
7646
|
-
writer.writeAscii(`${
|
|
7647
|
-
writer.writeAscii(`${
|
|
7652
|
+
writer.writeAscii(`${formatPoint(cx + rx, cy)} m\n`);
|
|
7653
|
+
writer.writeAscii(`${formatPoint(cx + rx, cy + ky)} ${formatPoint(cx + kx, cy + ry)} ${formatPoint(cx, cy + ry)} c\n`);
|
|
7654
|
+
writer.writeAscii(`${formatPoint(cx - kx, cy + ry)} ${formatPoint(cx - rx, cy + ky)} ${formatPoint(cx - rx, cy)} c\n`);
|
|
7655
|
+
writer.writeAscii(`${formatPoint(cx - rx, cy - ky)} ${formatPoint(cx - kx, cy - ry)} ${formatPoint(cx, cy - ry)} c\n`);
|
|
7656
|
+
writer.writeAscii(`${formatPoint(cx + kx, cy - ry)} ${formatPoint(cx + rx, cy - ky)} ${formatPoint(cx + rx, cy)} c\n`);
|
|
7657
|
+
writer.writeAscii(`${paint}\n`);
|
|
7658
|
+
}
|
|
7659
|
+
function writeSubpath(writer, subpath) {
|
|
7660
|
+
writer.writeAscii(`${formatPoint(subpath.startXPt, subpath.startYPt)} m\n`);
|
|
7661
|
+
for (const segment of subpath.segments) if (segment.kind === "line") writer.writeAscii(`${formatPoint(segment.xPt, segment.yPt)} l\n`);
|
|
7662
|
+
else writer.writeAscii(`${formatPoint(segment.c1xPt, segment.c1yPt)} ${formatPoint(segment.c2xPt, segment.c2yPt)} ${formatPoint(segment.xPt, segment.yPt)} c\n`);
|
|
7663
|
+
if (subpath.closed) writer.writeAscii("h\n");
|
|
7664
|
+
}
|
|
7665
|
+
function writePath(writer, item) {
|
|
7666
|
+
const paint = paintOperatorFor(item.fill, item.stroke, item.fillRule);
|
|
7667
|
+
if (paint === void 0) return;
|
|
7668
|
+
writeFillAndStroke(writer, item.fill, item.stroke);
|
|
7669
|
+
for (const subpath of item.subpaths) writeSubpath(writer, subpath);
|
|
7648
7670
|
writer.writeAscii(`${paint}\n`);
|
|
7649
7671
|
}
|
|
7650
7672
|
function writeImage(writer, item, context) {
|
|
@@ -7663,6 +7685,7 @@ function writeContentStream(items, context) {
|
|
|
7663
7685
|
else if (item.kind === "rect") writeRect(writer, item);
|
|
7664
7686
|
else if (item.kind === "line") writeLine(writer, item);
|
|
7665
7687
|
else if (item.kind === "ellipse") writeEllipse(writer, item);
|
|
7688
|
+
else if (item.kind === "path") writePath(writer, item);
|
|
7666
7689
|
return {
|
|
7667
7690
|
bytes: writer.toBytes(),
|
|
7668
7691
|
substitutions
|
|
@@ -8469,6 +8492,17 @@ function readOdsContent(pkg) {
|
|
|
8469
8492
|
};
|
|
8470
8493
|
}
|
|
8471
8494
|
//#endregion
|
|
8495
|
+
//#region src/odf/odg/read.ts
|
|
8496
|
+
function readOdgContent(pkg) {
|
|
8497
|
+
const odgDoc = (0, odf_js.readOdg)(pkg);
|
|
8498
|
+
return {
|
|
8499
|
+
kind: "drawing",
|
|
8500
|
+
formatVersion: 1,
|
|
8501
|
+
metadata: { ...odgDoc.metadata },
|
|
8502
|
+
pages: odgDoc.pages
|
|
8503
|
+
};
|
|
8504
|
+
}
|
|
8505
|
+
//#endregion
|
|
8472
8506
|
//#region src/pdf/text-layout.ts
|
|
8473
8507
|
const WORD_OR_WHITESPACE_PATTERN = /\n|\s+|\S+/g;
|
|
8474
8508
|
function atomizeRuns(runs, measurer) {
|
|
@@ -9500,6 +9534,118 @@ function convertSpreadsheetToLayout(doc, options) {
|
|
|
9500
9534
|
};
|
|
9501
9535
|
}
|
|
9502
9536
|
//#endregion
|
|
9537
|
+
//#region src/layout/drawing.ts
|
|
9538
|
+
function convertRectVector(vector, pageHeightPt, out) {
|
|
9539
|
+
const flipped = flipY(vector.frame, pageHeightPt);
|
|
9540
|
+
out.push({
|
|
9541
|
+
kind: "rect",
|
|
9542
|
+
xPt: flipped.xPt,
|
|
9543
|
+
yPt: flipped.yPt,
|
|
9544
|
+
widthPt: flipped.widthPt,
|
|
9545
|
+
heightPt: flipped.heightPt,
|
|
9546
|
+
fill: vector.fill,
|
|
9547
|
+
stroke: vector.stroke,
|
|
9548
|
+
sourcePath: vector.sourcePath
|
|
9549
|
+
});
|
|
9550
|
+
}
|
|
9551
|
+
function convertEllipseVector(vector, pageHeightPt, out) {
|
|
9552
|
+
const flipped = flipY(vector.frame, pageHeightPt);
|
|
9553
|
+
out.push({
|
|
9554
|
+
kind: "ellipse",
|
|
9555
|
+
xPt: flipped.xPt,
|
|
9556
|
+
yPt: flipped.yPt,
|
|
9557
|
+
widthPt: flipped.widthPt,
|
|
9558
|
+
heightPt: flipped.heightPt,
|
|
9559
|
+
fill: vector.fill,
|
|
9560
|
+
stroke: vector.stroke,
|
|
9561
|
+
sourcePath: vector.sourcePath
|
|
9562
|
+
});
|
|
9563
|
+
}
|
|
9564
|
+
function convertLineVector(vector, pageHeightPt, out) {
|
|
9565
|
+
out.push({
|
|
9566
|
+
kind: "line",
|
|
9567
|
+
x1Pt: vector.from.xPt,
|
|
9568
|
+
y1Pt: pageHeightPt - vector.from.yPt,
|
|
9569
|
+
x2Pt: vector.to.xPt,
|
|
9570
|
+
y2Pt: pageHeightPt - vector.to.yPt,
|
|
9571
|
+
color: vector.stroke.color,
|
|
9572
|
+
widthPt: vector.stroke.widthPt,
|
|
9573
|
+
sourcePath: vector.sourcePath
|
|
9574
|
+
});
|
|
9575
|
+
}
|
|
9576
|
+
function placePathPoint(frame, point, pageHeightPt) {
|
|
9577
|
+
return {
|
|
9578
|
+
xPt: frame.xPt + point.xPt,
|
|
9579
|
+
yPt: pageHeightPt - frame.yPt - point.yPt
|
|
9580
|
+
};
|
|
9581
|
+
}
|
|
9582
|
+
function convertPathVector(vector, pageHeightPt, out) {
|
|
9583
|
+
const subpaths = vector.subpaths.map((subpath) => {
|
|
9584
|
+
const start = placePathPoint(vector.frame, subpath.start, pageHeightPt);
|
|
9585
|
+
return {
|
|
9586
|
+
startXPt: start.xPt,
|
|
9587
|
+
startYPt: start.yPt,
|
|
9588
|
+
closed: subpath.closed,
|
|
9589
|
+
segments: subpath.segments.map((segment) => {
|
|
9590
|
+
if (segment.kind === "line") {
|
|
9591
|
+
const to = placePathPoint(vector.frame, segment.to, pageHeightPt);
|
|
9592
|
+
return {
|
|
9593
|
+
kind: "line",
|
|
9594
|
+
xPt: to.xPt,
|
|
9595
|
+
yPt: to.yPt
|
|
9596
|
+
};
|
|
9597
|
+
}
|
|
9598
|
+
const control1 = placePathPoint(vector.frame, segment.control1, pageHeightPt);
|
|
9599
|
+
const control2 = placePathPoint(vector.frame, segment.control2, pageHeightPt);
|
|
9600
|
+
const to = placePathPoint(vector.frame, segment.to, pageHeightPt);
|
|
9601
|
+
return {
|
|
9602
|
+
kind: "cubic",
|
|
9603
|
+
c1xPt: control1.xPt,
|
|
9604
|
+
c1yPt: control1.yPt,
|
|
9605
|
+
c2xPt: control2.xPt,
|
|
9606
|
+
c2yPt: control2.yPt,
|
|
9607
|
+
xPt: to.xPt,
|
|
9608
|
+
yPt: to.yPt
|
|
9609
|
+
};
|
|
9610
|
+
})
|
|
9611
|
+
};
|
|
9612
|
+
});
|
|
9613
|
+
out.push({
|
|
9614
|
+
kind: "path",
|
|
9615
|
+
subpaths,
|
|
9616
|
+
fill: vector.fill,
|
|
9617
|
+
fillRule: vector.fillRule,
|
|
9618
|
+
stroke: vector.stroke,
|
|
9619
|
+
sourcePath: vector.sourcePath
|
|
9620
|
+
});
|
|
9621
|
+
}
|
|
9622
|
+
function convertVector(vector, pageHeightPt, out) {
|
|
9623
|
+
if (vector.kind === "rect") convertRectVector(vector, pageHeightPt, out);
|
|
9624
|
+
else if (vector.kind === "ellipse") convertEllipseVector(vector, pageHeightPt, out);
|
|
9625
|
+
else if (vector.kind === "line") convertLineVector(vector, pageHeightPt, out);
|
|
9626
|
+
else convertPathVector(vector, pageHeightPt, out);
|
|
9627
|
+
}
|
|
9628
|
+
function convertPage(page, measurer, images) {
|
|
9629
|
+
const items = [];
|
|
9630
|
+
for (const vector of page.vectors) convertVector(vector, page.size.heightPt, items);
|
|
9631
|
+
for (const shape of page.shapes) convertShape(shape, page.size.heightPt, measurer, images, items);
|
|
9632
|
+
return {
|
|
9633
|
+
widthPt: page.size.widthPt,
|
|
9634
|
+
heightPt: page.size.heightPt,
|
|
9635
|
+
items
|
|
9636
|
+
};
|
|
9637
|
+
}
|
|
9638
|
+
function convertDrawingToLayout(doc, options) {
|
|
9639
|
+
const images = {};
|
|
9640
|
+
const pages = doc.pages.map((page) => convertPage(page, options.measurer, images));
|
|
9641
|
+
return {
|
|
9642
|
+
formatVersion: document_content_model.LAYOUT_FORMAT_VERSION,
|
|
9643
|
+
metadata: doc.metadata,
|
|
9644
|
+
pages,
|
|
9645
|
+
images
|
|
9646
|
+
};
|
|
9647
|
+
}
|
|
9648
|
+
//#endregion
|
|
9503
9649
|
//#region src/layout/reconstruct.ts
|
|
9504
9650
|
const ZERO_MARGINS = {
|
|
9505
9651
|
topPt: 0,
|
|
@@ -9879,6 +10025,14 @@ function odsToPdf(bytes, options) {
|
|
|
9879
10025
|
onSubstitution: options?.onSubstitution
|
|
9880
10026
|
});
|
|
9881
10027
|
}
|
|
10028
|
+
function odgToPdf(bytes, options) {
|
|
10029
|
+
const content = readOdgContent((0, odf_js.decodePackage)(bytes));
|
|
10030
|
+
if (content.kind !== "drawing") throw new Error("readOdgContent returned a non-drawing ContentDocument");
|
|
10031
|
+
return writePdf(convertDrawingToLayout(content, { measurer: createStandardFontMeasurer() }), {
|
|
10032
|
+
signal: options?.signal,
|
|
10033
|
+
onSubstitution: options?.onSubstitution
|
|
10034
|
+
});
|
|
10035
|
+
}
|
|
9882
10036
|
function pdfToDocx(bytes, options) {
|
|
9883
10037
|
const content = reconstructWordprocessing(readPdf(bytes, {
|
|
9884
10038
|
signal: options?.signal,
|
|
@@ -9948,6 +10102,10 @@ const SUPPORTED_CONVERSIONS = [
|
|
|
9948
10102
|
source: "ods",
|
|
9949
10103
|
target: "pdf"
|
|
9950
10104
|
},
|
|
10105
|
+
{
|
|
10106
|
+
source: "odg",
|
|
10107
|
+
target: "pdf"
|
|
10108
|
+
},
|
|
9951
10109
|
{
|
|
9952
10110
|
source: "pdf",
|
|
9953
10111
|
target: "docx"
|
|
@@ -10053,6 +10211,19 @@ function createLocalDocumentConverter() {
|
|
|
10053
10211
|
diagnostics
|
|
10054
10212
|
});
|
|
10055
10213
|
}
|
|
10214
|
+
if (source.format === "odg" && targetFormat === "pdf") {
|
|
10215
|
+
const bytes = odgToPdf(source.bytes, {
|
|
10216
|
+
signal: options.signal,
|
|
10217
|
+
onSubstitution: (s, c) => diagnostics.push(substitutionDiagnostic(s, c))
|
|
10218
|
+
});
|
|
10219
|
+
return Promise.resolve({
|
|
10220
|
+
document: {
|
|
10221
|
+
format: "pdf",
|
|
10222
|
+
bytes
|
|
10223
|
+
},
|
|
10224
|
+
diagnostics
|
|
10225
|
+
});
|
|
10226
|
+
}
|
|
10056
10227
|
if (source.format === "pdf" && targetFormat === "docx") {
|
|
10057
10228
|
const bytes = pdfToDocx(source.bytes, {
|
|
10058
10229
|
signal: options.signal,
|
|
@@ -10172,6 +10343,12 @@ Object.defineProperty(exports, "ContentCellValueSchema", {
|
|
|
10172
10343
|
}
|
|
10173
10344
|
});
|
|
10174
10345
|
exports.ContentDocumentSchema = ContentDocumentSchema;
|
|
10346
|
+
Object.defineProperty(exports, "ContentDrawPageSchema", {
|
|
10347
|
+
enumerable: true,
|
|
10348
|
+
get: function() {
|
|
10349
|
+
return document_content_model.ContentDrawPageSchema;
|
|
10350
|
+
}
|
|
10351
|
+
});
|
|
10175
10352
|
Object.defineProperty(exports, "ContentImageBlockSchema", {
|
|
10176
10353
|
enumerable: true,
|
|
10177
10354
|
get: function() {
|
|
@@ -10190,6 +10367,18 @@ Object.defineProperty(exports, "ContentParagraphSchema", {
|
|
|
10190
10367
|
return document_content_model.ContentParagraphSchema;
|
|
10191
10368
|
}
|
|
10192
10369
|
});
|
|
10370
|
+
Object.defineProperty(exports, "ContentPathPointSchema", {
|
|
10371
|
+
enumerable: true,
|
|
10372
|
+
get: function() {
|
|
10373
|
+
return document_content_model.ContentPathPointSchema;
|
|
10374
|
+
}
|
|
10375
|
+
});
|
|
10376
|
+
Object.defineProperty(exports, "ContentPathSegmentSchema", {
|
|
10377
|
+
enumerable: true,
|
|
10378
|
+
get: function() {
|
|
10379
|
+
return document_content_model.ContentPathSegmentSchema;
|
|
10380
|
+
}
|
|
10381
|
+
});
|
|
10193
10382
|
Object.defineProperty(exports, "ContentRunSchema", {
|
|
10194
10383
|
enumerable: true,
|
|
10195
10384
|
get: function() {
|
|
@@ -10256,6 +10445,18 @@ Object.defineProperty(exports, "ContentSlideSchema", {
|
|
|
10256
10445
|
return document_content_model.ContentSlideSchema;
|
|
10257
10446
|
}
|
|
10258
10447
|
});
|
|
10448
|
+
Object.defineProperty(exports, "ContentStrokeSchema", {
|
|
10449
|
+
enumerable: true,
|
|
10450
|
+
get: function() {
|
|
10451
|
+
return document_content_model.ContentStrokeSchema;
|
|
10452
|
+
}
|
|
10453
|
+
});
|
|
10454
|
+
Object.defineProperty(exports, "ContentSubpathSchema", {
|
|
10455
|
+
enumerable: true,
|
|
10456
|
+
get: function() {
|
|
10457
|
+
return document_content_model.ContentSubpathSchema;
|
|
10458
|
+
}
|
|
10459
|
+
});
|
|
10259
10460
|
Object.defineProperty(exports, "ContentTableCellSchema", {
|
|
10260
10461
|
enumerable: true,
|
|
10261
10462
|
get: function() {
|
|
@@ -10274,6 +10475,12 @@ Object.defineProperty(exports, "ContentTableSchema", {
|
|
|
10274
10475
|
return document_content_model.ContentTableSchema;
|
|
10275
10476
|
}
|
|
10276
10477
|
});
|
|
10478
|
+
Object.defineProperty(exports, "ContentVectorSchema", {
|
|
10479
|
+
enumerable: true,
|
|
10480
|
+
get: function() {
|
|
10481
|
+
return document_content_model.ContentVectorSchema;
|
|
10482
|
+
}
|
|
10483
|
+
});
|
|
10277
10484
|
Object.defineProperty(exports, "DEFAULT_LAYOUT_FONT", {
|
|
10278
10485
|
enumerable: true,
|
|
10279
10486
|
get: function() {
|
|
@@ -10456,6 +10663,7 @@ Object.defineProperty(exports, "compactPackageCodec", {
|
|
|
10456
10663
|
return ooxml_js.compactPackageCodec;
|
|
10457
10664
|
}
|
|
10458
10665
|
});
|
|
10666
|
+
exports.convertDrawingToLayout = convertDrawingToLayout;
|
|
10459
10667
|
exports.convertPresentationToLayout = convertPresentationToLayout;
|
|
10460
10668
|
exports.convertSpreadsheetToLayout = convertSpreadsheetToLayout;
|
|
10461
10669
|
exports.convertWordprocessingToLayout = convertWordprocessingToLayout;
|
|
@@ -10529,6 +10737,7 @@ Object.defineProperty(exports, "isXmlNode", {
|
|
|
10529
10737
|
return ooxml_js.isXmlNode;
|
|
10530
10738
|
}
|
|
10531
10739
|
});
|
|
10740
|
+
exports.odgToPdf = odgToPdf;
|
|
10532
10741
|
exports.odpPdfCodec = odpPdfCodec;
|
|
10533
10742
|
exports.odpToPdf = odpToPdf;
|
|
10534
10743
|
exports.odsToPdf = odsToPdf;
|
|
@@ -10565,6 +10774,7 @@ exports.pdfToPptx = pdfToPptx;
|
|
|
10565
10774
|
exports.pptxPdfCodec = pptxPdfCodec;
|
|
10566
10775
|
exports.pptxToPdf = pptxToPdf;
|
|
10567
10776
|
exports.readDocxContent = readDocxContent;
|
|
10777
|
+
exports.readOdgContent = readOdgContent;
|
|
10568
10778
|
exports.readOdpContent = readOdpContent;
|
|
10569
10779
|
exports.readOdsContent = readOdsContent;
|
|
10570
10780
|
exports.readOdtContent = readOdtContent;
|