odf.js 1.3.0 → 1.4.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 +116 -0
- package/dist/index.cjs +412 -4
- package/dist/index.d.cts +49 -2
- package/dist/index.d.ts +49 -2
- package/dist/index.js +401 -6
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# odf.js
|
|
2
|
+
|
|
3
|
+
[](https://github.com/ExaDev/odf.js) [](https://www.npmjs.com/package/odf.js) [](https://github.com/ExaDev/odf.js/releases/latest) [](https://github.com/ExaDev/odf.js/actions)
|
|
4
|
+
|
|
5
|
+
> A hand-written, dependency-minimal codec for the OpenDocument Format (ODF — OASIS/ISO 26300): `.odt`/`.ods`/`.odp`/`.odg`/`.odf`/`.odb`/`.odm` and their template variants, built on [Zod 4](https://zod.dev) codecs.
|
|
6
|
+
|
|
7
|
+
`odf.js` is the ODF sibling of [`ooxml.js`](https://github.com/ExaDev/ooxml.js), mirroring its architecture as closely as the two, structurally unrelated formats allow: a lossless ZIP-of-XML core that round-trips any package byte-for-content-faithful, with ergonomic typed readers layered on top for convenient access. Unlike OOXML — a ZIP of parts with a relationship-file (`.rels`) mechanism and an extension-defaults-plus-overrides `[Content_Types].xml` — ODF has no relationships at all (inter-part references are direct paths/IRIs) and an exhaustive `META-INF/manifest.xml` that enumerates every part explicitly. Where OOXML runs carry formatting directly as attributes, ODF has no inline/direct formatting whatsoever: every formatting difference, however small, must be a named "automatic style" — so `odf.js` owns a style-interning subsystem (`src/styles/`) with no equivalent anywhere in `ooxml.js`.
|
|
8
|
+
|
|
9
|
+
**This package does not depend on `ooxml.js`**, even though the two do near-identical jobs for their respective formats: `ooxml.js` is a package signed, SBOM-attested, and branded exclusively around ECMA-376/OOXML, so depending on it here would be a permanently wrong signal for an OASIS-standard codec, and would force a breaking `ooxml.js` release every time an ODF-only fix needed the shared primitive layer. Instead, `odf.js` duplicates the small (~400-line) generic ZIP/XML/`Package` layer as its own code — kept deliberately structurally identical (plain, unmarked shapes, no branding) so TypeScript's structural typing makes the two packages' `Package`/`XmlNode`/`XmlElement` values freely interchangeable wherever a shared consumer (like `documents.js`) needs to treat them uniformly, without either package formally depending on the other.
|
|
10
|
+
|
|
11
|
+
Both packages **do** depend on [`document-content-model`](https://github.com/ExaDev/document-content-model), the genuinely shared canonical schema for `ContentDocument`/`LayoutDocument` — the semantic content model (paragraphs, runs, tables, shapes, slides) both an ODF and an OOXML reader ultimately produce. `odf.js`'s typed readers return the real, imported `ContentSection`/`ContentSlide`/etc. types from that package, not a structurally-similar lookalike, so a downstream consumer (`documents.js`) can run an `.odt` through the exact same layout/pagination engine it already uses for `.docx`, unmodified.
|
|
12
|
+
|
|
13
|
+
## Status
|
|
14
|
+
|
|
15
|
+
This package is under active development. What's built and shipped:
|
|
16
|
+
|
|
17
|
+
- **Lossless core** — generic ZIP-of-XML primitives (`Package`/`XmlNode`/`XmlElement`, XML parse/build, zip/unzip, base64, the `packageCodec`/`xmlCodec` `z.codec()` pairs) with zero ODF-specific knowledge.
|
|
18
|
+
- **Namespaces, media types, mimetype, manifest** (`src/ns.ts`, `src/media-type.ts`, `src/mimetype.ts`, `src/manifest.ts`) — full read *and* write, including `META-INF/manifest.xml`'s exhaustive per-part enumeration and the mimetype part's mandatory first-entry/stored/uncompressed byte layout, verified against real LibreOffice-produced output.
|
|
19
|
+
- **Style interning** (`src/styles/`) — `StyleRegistry`: adopts a part's existing automatic styles on construction, finds-or-mints on `intern()`, fingerprints on canonical serialized properties plus parent style name (never `JSON.stringify`), and is collision-checked across all four style containers a document can have.
|
|
20
|
+
- **Shared typed primitives** (`src/typed/shared/`) — ODF length-unit parsing (`cm`/`mm`/`in`/`pt`/`pc`/`px`), A1-style spreadsheet cell-reference computation with repeat-count cursor advancement, colour/geometry parsing into `document-content-model`'s own types, ODF's `text:s`/`text:tab`/`text:line-break` whitespace-run decoding, the read-side style cascade (`style:default-style` → parent chain → the referenced style — one layer shorter than OOXML's, since ODF has no separate direct-formatting layer on top), and `meta.xml` reading.
|
|
21
|
+
|
|
22
|
+
Not yet built: the per-format typed readers (`readOdt`, `readOds`, `readOdp`, `readOdg`, `readOdfFormula`, `readOdbInventory`), live-view editors, and the `.odm` master-document/`.odb` database-table-export subsystems. This section will be replaced with real usage examples once those land — see the [Architecture](#architecture) section below for the intended shape, and this repository's own commit history/releases for current progress.
|
|
23
|
+
|
|
24
|
+
## Getting started
|
|
25
|
+
|
|
26
|
+
Requires Node.js `>=20` and pnpm `11.6.0` (pinned via `packageManager` in `package.json`).
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
pnpm install
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Install as a dependency in another project:
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
pnpm add odf.js
|
|
36
|
+
# or
|
|
37
|
+
npm install odf.js
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
The lossless core — the only public surface stable enough to document with real examples right now:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import { decodePackage, encodePackage } from 'odf.js';
|
|
46
|
+
|
|
47
|
+
// .odt / .ods / .odp bytes -> faithful JSON Package
|
|
48
|
+
const pkg = decodePackage(new Uint8Array(await file.arrayBuffer()));
|
|
49
|
+
|
|
50
|
+
// ...inspect pkg.parts...
|
|
51
|
+
|
|
52
|
+
// Package -> bytes (content-identical, mimetype-first/stored, manifest untouched)
|
|
53
|
+
const bytes = encodePackage(pkg);
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Manifest and mimetype, ODF's own package-identity mechanism (no relationships, unlike OOXML):
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { readManifest, syncManifest, setDocumentMediaType, readMimetype } from 'odf.js';
|
|
60
|
+
|
|
61
|
+
const manifest = readManifest(pkg); // { entries: [{ fullPath, mediaType }, ...] }
|
|
62
|
+
setDocumentMediaType(pkg, 'application/vnd.oasis.opendocument.text'); // updates mimetype + manifest root entry atomically
|
|
63
|
+
syncManifest(pkg); // rebuilds manifest.xml to exactly match pkg's current parts
|
|
64
|
+
readMimetype(pkg); // 'application/vnd.oasis.opendocument.text'
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Architecture
|
|
68
|
+
|
|
69
|
+
Layered from a lossless core outward, mirroring `ooxml.js`'s own structure:
|
|
70
|
+
|
|
71
|
+
- **`src/model/`** — `Package`/`XmlNode`/`XmlElement` and friends: a duplicate-by-design copy of `ooxml.js`'s equivalent, kept structurally identical (see [Why no `ooxml.js` dependency](#why-no-ooxmljs-dependency) above).
|
|
72
|
+
- **`src/xml/`** — `parse.ts`/`build.ts` (XML string ⇄ `XmlNode[]` forest via `fast-xml-parser`), `fragment.ts`/`entities.ts` (production element/text-node construction and entity encoding — `odf.js` writes `manifest.xml` itself, unlike `ooxml.js`'s read-only stance on OPC relationships, so this needs to be real writing code, not test-only scaffolding), `query.ts` (shared tree-query helpers).
|
|
73
|
+
- **`src/zip.ts`** — takes *ordered* `[path, entry]` tuples, not a `Record`, specifically so ODF's mimetype-first/stored/uncompressed requirement doesn't depend on `Record`/`Object.keys` insertion order surviving a Zod round trip.
|
|
74
|
+
- **`src/package-io/`** — `write.ts` hoists a `mimetype` part first (stored) and `META-INF/manifest.xml` second, if present, before everything else in existing order — the one deliberate behavioural difference from `ooxml.js`'s own writer, and never fabricates either part as a side effect.
|
|
75
|
+
- **`src/manifest.ts`** — unlike `ooxml.js` (which only ever *reads* OPC relationships, leaving writing to `documents.js`), `odf.js` owns manifest read **and** write, since the manifest is ODF's one mandatory part and its correctness is exhaustive.
|
|
76
|
+
- **`src/styles/`** — `properties.ts` (the property-bag shape + real ODF attribute parsing), `serialize.ts` (canonical, deterministic property-bag → XML attributes), `registry.ts` (`StyleRegistry`, ODF's mandatory style-interning layer, no OOXML equivalent), `span.ts` (character-range wrapping into a formattable `text:span`, correctly splitting `text:s`/`text:tab` elements that straddle a boundary).
|
|
77
|
+
- **`src/typed/shared/`** — the ODF-specific typed primitives every future format reader builds on: `units.ts`, `a1.ts`, `color.ts`/`geometry.ts` (parsing into `document-content-model`'s own types, never redefining them), `style.ts` (a thin re-export — ODF's style-properties concern is fully covered by `styles/properties.ts` and the cascade below), `text.ts` (whitespace-run decoding), `cascade.ts` (the read-side style-resolution walk), `metadata.ts` (`meta.xml` reading).
|
|
78
|
+
- **`src/typed/odt/`, `src/typed/ods.ts`, `src/typed/odp/`, `src/typed/odg/`, `src/typed/draw/`, `src/typed/formula/`** — per-format typed readers, in progress (see [Status](#status)).
|
|
79
|
+
|
|
80
|
+
## Why no `ooxml.js` dependency
|
|
81
|
+
|
|
82
|
+
See the top of this README — the short version: `ooxml.js`'s branding and signed SBOM make it the wrong dependency for an OASIS-standard package regardless of how much low-level code the two could share; `document-content-model` is the neutral package both actually depend on for the parts that are genuinely, permanently identical (the semantic content vocabulary), while the ZIP-of-XML primitive layer stays duplicated on purpose.
|
|
83
|
+
|
|
84
|
+
## Conventions
|
|
85
|
+
|
|
86
|
+
- **Zod-first schema/type/guard**, matching `ooxml.js`/`document-content-model`: every model type is inferred from its Zod schema, never hand-written.
|
|
87
|
+
- **Recursive types use a hand-written structural guard, not `z.lazy`** — the same `z.lazy`-collapses-to-`unknown` issue `ooxml.js`'s `XmlNode` and `document-content-model`'s `ContentBlock` already work around.
|
|
88
|
+
- **No type assertions anywhere** — `assertionStyle: 'never'`, `noInlineConfig: true`, matching both sibling packages exactly.
|
|
89
|
+
- **Ground truth over memory for every ODF spec fact.** Namespace URIs, media types, style-property attribute names, and `meta.xml` element names are all verified against either the live OASIS ODF specification or real files produced by an installed LibreOffice, never assumed from pattern-matching an OOXML analogue or a remembered convention — several confirmed traps exist specifically because the "obvious" guess is wrong (see [Gotchas](#gotchas-and-quirks)).
|
|
90
|
+
|
|
91
|
+
## Gotchas and quirks
|
|
92
|
+
|
|
93
|
+
- **Several ODF namespace URIs are not what you'd guess from the prefix.** `draw:` is `...xmlns:drawing:1.0`, not `...draw:1.0`; `number:` is `...xmlns:datastyle:1.0`, not `...number:1.0`; `fo:`/`svg:`/`smil:` are OASIS's own `*-compatible:1.0` URIs, not the real W3C namespaces those prefixes suggest. See `src/ns.ts`'s inline comments for the full, verified table.
|
|
94
|
+
- **`.odb`'s real media type is `application/vnd.oasis.opendocument.base`**, not `...database` — a common stale/wrong value found in some third-party documentation.
|
|
95
|
+
- **ODF's `dc:creator` is not "the author."** It records whoever most recently *saved* the document (Dublin Core's own definition); the original author is `meta:initial-creator`. `typed/shared/metadata.ts` maps `LayoutMetadata.author` to `meta:initial-creator`, matching the byline role `ooxml.js`'s own `DocumentMetadata.author` plays for OOXML.
|
|
96
|
+
- **`meta:keyword` appears once per keyword**, unlike OOXML's single comma-separated `cp:keywords` element.
|
|
97
|
+
- **`table:number-columns-repeated`/`table:number-rows-repeated` must be cursor-advanced, never materialized.** A real spreadsheet has trailing cells/rows with repeat counts over a million; `typed/shared/a1.ts`'s cursor advances in O(1) without allocating that many objects — tested against a real repeat count taken from a genuine LibreOffice template.
|
|
98
|
+
- **ODF cells carry no explicit cell-reference attribute at all** (unlike xlsx's `r="B7"`) — `typed/shared/a1.ts` computes A1-style references from a running column/row cursor as a reader walks cells in document order.
|
|
99
|
+
|
|
100
|
+
## Release and publishing
|
|
101
|
+
|
|
102
|
+
`.github/workflows/ci.yml` runs commitlint, lint, typecheck, the unit suite, and the smoke test on every push and pull request. On a push to `main` where those all pass, `release.config.ts` drives [semantic-release](https://semantic-release.gitbook.io/semantic-release): commit history since the last tag decides the version bump, `CHANGELOG.md` and `package.json` are committed back to `main`, a GitHub Release is cut, and the package publishes to [npmjs.org](https://www.npmjs.com/package/odf.js) — via npm's OIDC trusted publishing, so no `NPM_TOKEN` exists anywhere in the pipeline. A further job republishes the same build under the scoped `@exadev/odf.js` alias to GitHub Packages, and another signs an SPDX SBOM and build-provenance attestation against the exact release tarball.
|
|
103
|
+
|
|
104
|
+
## Contributing
|
|
105
|
+
|
|
106
|
+
Commits follow Conventional Commits (`feat:`, `fix:`, `test:`, `chore:`, …), enforced by commitlint via a husky `commit-msg` hook and a CI `commitlint` job. A husky `pre-commit` hook runs `lint-staged` (`eslint --fix` on staged `*.ts` files) and `pre-push` runs the test suite. There is a single `main` branch and no open pull request workflow established so far.
|
|
107
|
+
|
|
108
|
+
## References
|
|
109
|
+
|
|
110
|
+
- [ooxml.js](https://github.com/ExaDev/ooxml.js) — the sibling package doing the equivalent lossless-codec job for OOXML (docx/pptx/xlsx). Architecturally mirrored, deliberately not depended on — see [Why no `ooxml.js` dependency](#why-no-ooxmljs-dependency).
|
|
111
|
+
- [document-content-model](https://github.com/ExaDev/document-content-model) — the canonical `ContentDocument`/`LayoutDocument` schema pivot both this package and `ooxml.js` depend on.
|
|
112
|
+
- [documents.js](https://github.com/ExaDev/documents.js) — the intended downstream consumer, adding ODF ⇄ PDF conversion and a live-view ODF editor once this package's typed readers land.
|
|
113
|
+
|
|
114
|
+
## License
|
|
115
|
+
|
|
116
|
+
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -1243,6 +1243,17 @@ function childrenWithTag(element, tag) {
|
|
|
1243
1243
|
for (const child of element.children) if (child.type === "element" && child.tag === tag) out.push(child);
|
|
1244
1244
|
return out;
|
|
1245
1245
|
}
|
|
1246
|
+
function* walk(nodes) {
|
|
1247
|
+
for (const node of nodes) {
|
|
1248
|
+
yield node;
|
|
1249
|
+
if (node.type === "element") yield* walk(node.children);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
function elementsWithTag(nodes, tag) {
|
|
1253
|
+
const out = [];
|
|
1254
|
+
for (const node of walk(nodes)) if (node.type === "element" && node.tag === tag) out.push(node);
|
|
1255
|
+
return out;
|
|
1256
|
+
}
|
|
1246
1257
|
function attrValue(element, name) {
|
|
1247
1258
|
return element.attributes.find((attribute) => attribute.name === name)?.value;
|
|
1248
1259
|
}
|
|
@@ -1522,13 +1533,14 @@ function collectStyles(pkg) {
|
|
|
1522
1533
|
defaultByFamily
|
|
1523
1534
|
};
|
|
1524
1535
|
}
|
|
1525
|
-
function
|
|
1536
|
+
function resolveStyleElementChain(styleName, family, pkg) {
|
|
1526
1537
|
const { byName, defaultByFamily } = collectStyles(pkg);
|
|
1527
1538
|
const diagnostics = [];
|
|
1539
|
+
const elements = [];
|
|
1528
1540
|
const defaultElement = defaultByFamily.get(family);
|
|
1529
|
-
|
|
1541
|
+
if (defaultElement !== void 0) elements.push(defaultElement);
|
|
1530
1542
|
if (styleName === void 0) return {
|
|
1531
|
-
|
|
1543
|
+
elements,
|
|
1532
1544
|
diagnostics
|
|
1533
1545
|
};
|
|
1534
1546
|
const chain = [];
|
|
@@ -1556,7 +1568,16 @@ function resolveStyle(styleName, family, pkg) {
|
|
|
1556
1568
|
currentName = attrValue(element, "style:parent-style-name");
|
|
1557
1569
|
}
|
|
1558
1570
|
chain.reverse();
|
|
1559
|
-
|
|
1571
|
+
elements.push(...chain);
|
|
1572
|
+
return {
|
|
1573
|
+
elements,
|
|
1574
|
+
diagnostics
|
|
1575
|
+
};
|
|
1576
|
+
}
|
|
1577
|
+
function resolveStyle(styleName, family, pkg) {
|
|
1578
|
+
const { elements, diagnostics } = resolveStyleElementChain(styleName, family, pkg);
|
|
1579
|
+
let properties = {};
|
|
1580
|
+
for (const element of elements) properties = {
|
|
1560
1581
|
...properties,
|
|
1561
1582
|
...parseStyleElementProperties(element).properties
|
|
1562
1583
|
};
|
|
@@ -1565,6 +1586,10 @@ function resolveStyle(styleName, family, pkg) {
|
|
|
1565
1586
|
diagnostics
|
|
1566
1587
|
};
|
|
1567
1588
|
}
|
|
1589
|
+
function findStyleElement(styleName, family, pkg) {
|
|
1590
|
+
const { byName } = collectStyles(pkg);
|
|
1591
|
+
return byName.get(nameKey(family, styleName));
|
|
1592
|
+
}
|
|
1568
1593
|
//#endregion
|
|
1569
1594
|
//#region src/typed/shared/metadata.ts
|
|
1570
1595
|
const META_PART = "meta.xml";
|
|
@@ -1603,6 +1628,376 @@ function readOdfMetadata(pkg) {
|
|
|
1603
1628
|
return metadata;
|
|
1604
1629
|
}
|
|
1605
1630
|
//#endregion
|
|
1631
|
+
//#region src/typed/shared/paragraph.ts
|
|
1632
|
+
function collectRuns(nodes, baseProperties, pkg, out) {
|
|
1633
|
+
for (const node of nodes) {
|
|
1634
|
+
if (node.type === "text") {
|
|
1635
|
+
if (node.value.length > 0) out.push(runFromText(decodeXmlText(node.value), baseProperties));
|
|
1636
|
+
continue;
|
|
1637
|
+
}
|
|
1638
|
+
if (node.type !== "element") continue;
|
|
1639
|
+
if (node.tag === "text:s") out.push(runFromText(" ".repeat(getOdfSpaceCount(node)), baseProperties));
|
|
1640
|
+
else if (node.tag === "text:tab") out.push(runFromText(" ", baseProperties));
|
|
1641
|
+
else if (node.tag === "text:line-break") out.push(runFromText("\n", baseProperties));
|
|
1642
|
+
else if (node.tag === "text:span") {
|
|
1643
|
+
const styleName = attrValue(node, "text:style-name");
|
|
1644
|
+
const spanProperties = {
|
|
1645
|
+
...baseProperties,
|
|
1646
|
+
...resolveStyle(styleName, "text", pkg).properties
|
|
1647
|
+
};
|
|
1648
|
+
collectRuns(node.children, spanProperties, pkg, out);
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
function runFromText(text, properties) {
|
|
1653
|
+
return {
|
|
1654
|
+
text,
|
|
1655
|
+
bold: properties.bold,
|
|
1656
|
+
italic: properties.italic,
|
|
1657
|
+
underline: properties.underline,
|
|
1658
|
+
strike: properties.strike,
|
|
1659
|
+
fontFamily: properties.fontFamily,
|
|
1660
|
+
sizePt: properties.sizePt,
|
|
1661
|
+
color: properties.color
|
|
1662
|
+
};
|
|
1663
|
+
}
|
|
1664
|
+
function readOdfParagraph(pElement, pkg) {
|
|
1665
|
+
const styleName = attrValue(pElement, "text:style-name");
|
|
1666
|
+
const paragraphProperties = resolveStyle(styleName, "paragraph", pkg).properties;
|
|
1667
|
+
const runs = [];
|
|
1668
|
+
collectRuns(pElement.children, paragraphProperties, pkg, runs);
|
|
1669
|
+
return {
|
|
1670
|
+
kind: "paragraph",
|
|
1671
|
+
runs,
|
|
1672
|
+
styleId: styleName,
|
|
1673
|
+
alignment: paragraphProperties.alignment,
|
|
1674
|
+
spacingBeforePt: paragraphProperties.spacingBeforePt,
|
|
1675
|
+
spacingAfterPt: paragraphProperties.spacingAfterPt,
|
|
1676
|
+
lineSpacing: paragraphProperties.lineSpacing,
|
|
1677
|
+
indentLeftPt: paragraphProperties.indentLeftPt,
|
|
1678
|
+
indentFirstLinePt: paragraphProperties.indentFirstLinePt
|
|
1679
|
+
};
|
|
1680
|
+
}
|
|
1681
|
+
//#endregion
|
|
1682
|
+
//#region src/typed/shared/table.ts
|
|
1683
|
+
function readRepeatCount(element, attrName) {
|
|
1684
|
+
const raw = attrValue(element, attrName);
|
|
1685
|
+
if (raw === void 0) return 1;
|
|
1686
|
+
const parsed = Number.parseInt(raw, 10);
|
|
1687
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : 1;
|
|
1688
|
+
}
|
|
1689
|
+
function resolveColumnWidthPt(columnElement, pkg) {
|
|
1690
|
+
const styleName = attrValue(columnElement, "table:style-name");
|
|
1691
|
+
const styleElement = styleName === void 0 ? void 0 : findStyleElement(styleName, "table-column", pkg);
|
|
1692
|
+
const props = styleElement === void 0 ? void 0 : childrenWithTag(styleElement, "style:table-column-properties")[0];
|
|
1693
|
+
const widthValue = props === void 0 ? void 0 : attrValue(props, "style:column-width");
|
|
1694
|
+
return widthValue === void 0 ? 0 : parseOdfLength(widthValue) ?? 0;
|
|
1695
|
+
}
|
|
1696
|
+
function resolveRowHeightPt(rowElement, pkg) {
|
|
1697
|
+
const styleName = attrValue(rowElement, "table:style-name");
|
|
1698
|
+
const styleElement = styleName === void 0 ? void 0 : findStyleElement(styleName, "table-row", pkg);
|
|
1699
|
+
const props = styleElement === void 0 ? void 0 : childrenWithTag(styleElement, "style:table-row-properties")[0];
|
|
1700
|
+
const heightValue = props === void 0 ? void 0 : attrValue(props, "style:row-height");
|
|
1701
|
+
return heightValue === void 0 ? void 0 : parseOdfLength(heightValue);
|
|
1702
|
+
}
|
|
1703
|
+
function readTableCellBackground(cellElement, pkg) {
|
|
1704
|
+
const styleName = attrValue(cellElement, "table:style-name");
|
|
1705
|
+
const styleElement = styleName === void 0 ? void 0 : findStyleElement(styleName, "table-cell", pkg);
|
|
1706
|
+
const props = styleElement === void 0 ? void 0 : childrenWithTag(styleElement, "style:table-cell-properties")[0];
|
|
1707
|
+
const value = props === void 0 ? void 0 : attrValue(props, "fo:background-color");
|
|
1708
|
+
return value === void 0 ? void 0 : parseOdfColor(value);
|
|
1709
|
+
}
|
|
1710
|
+
function readTableCell(cellElement, pkg) {
|
|
1711
|
+
const blocks = childrenWithTag(cellElement, "text:p").map((p) => readOdfParagraph(p, pkg));
|
|
1712
|
+
const colSpanRaw = attrValue(cellElement, "table:number-columns-spanned");
|
|
1713
|
+
const rowSpanRaw = attrValue(cellElement, "table:number-rows-spanned");
|
|
1714
|
+
return {
|
|
1715
|
+
blocks,
|
|
1716
|
+
colSpan: colSpanRaw === void 0 ? void 0 : Number.parseInt(colSpanRaw, 10),
|
|
1717
|
+
rowSpan: rowSpanRaw === void 0 ? void 0 : Number.parseInt(rowSpanRaw, 10),
|
|
1718
|
+
background: readTableCellBackground(cellElement, pkg)
|
|
1719
|
+
};
|
|
1720
|
+
}
|
|
1721
|
+
function readTableRow(rowElement, pkg) {
|
|
1722
|
+
const cells = [];
|
|
1723
|
+
for (const child of rowElement.children) {
|
|
1724
|
+
if (child.type !== "element") continue;
|
|
1725
|
+
if (child.tag === "table:covered-table-cell") {
|
|
1726
|
+
const repeat = readRepeatCount(child, "table:number-columns-repeated");
|
|
1727
|
+
for (let i = 0; i < repeat; i++) cells.push({ blocks: [] });
|
|
1728
|
+
} else if (child.tag === "table:table-cell") {
|
|
1729
|
+
const cell = readTableCell(child, pkg);
|
|
1730
|
+
const repeat = readRepeatCount(child, "table:number-columns-repeated");
|
|
1731
|
+
for (let i = 0; i < repeat; i++) cells.push(cell);
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
return {
|
|
1735
|
+
cells,
|
|
1736
|
+
heightPt: resolveRowHeightPt(rowElement, pkg)
|
|
1737
|
+
};
|
|
1738
|
+
}
|
|
1739
|
+
function readOdfTable(tableElement, pkg) {
|
|
1740
|
+
const columnWidthsPt = [];
|
|
1741
|
+
for (const column of childrenWithTag(tableElement, "table:table-column")) {
|
|
1742
|
+
const widthPt = resolveColumnWidthPt(column, pkg);
|
|
1743
|
+
const repeat = readRepeatCount(column, "table:number-columns-repeated");
|
|
1744
|
+
for (let i = 0; i < repeat; i++) columnWidthsPt.push(widthPt);
|
|
1745
|
+
}
|
|
1746
|
+
const rows = [];
|
|
1747
|
+
for (const rowElement of childrenWithTag(tableElement, "table:table-row")) {
|
|
1748
|
+
const row = readTableRow(rowElement, pkg);
|
|
1749
|
+
const repeat = readRepeatCount(rowElement, "table:number-rows-repeated");
|
|
1750
|
+
for (let i = 0; i < repeat; i++) rows.push(row);
|
|
1751
|
+
}
|
|
1752
|
+
return {
|
|
1753
|
+
kind: "table",
|
|
1754
|
+
rows,
|
|
1755
|
+
columnWidthsPt
|
|
1756
|
+
};
|
|
1757
|
+
}
|
|
1758
|
+
//#endregion
|
|
1759
|
+
//#region src/typed/shared/transform.ts
|
|
1760
|
+
const FUNCTION_PATTERN = /([a-zA-Z]+)\s*\(\s*([^)]*?)\s*\)/g;
|
|
1761
|
+
function parseOdfTransform(value) {
|
|
1762
|
+
const functions = [];
|
|
1763
|
+
for (const match of value.matchAll(FUNCTION_PATTERN)) {
|
|
1764
|
+
const name = match[1];
|
|
1765
|
+
const argsRaw = match[2];
|
|
1766
|
+
if (name === void 0 || argsRaw === void 0) continue;
|
|
1767
|
+
const args = argsRaw.split(/\s+/).filter((arg) => arg.length > 0);
|
|
1768
|
+
if (name === "rotate") {
|
|
1769
|
+
const angleArg = args[0];
|
|
1770
|
+
if (angleArg === void 0) continue;
|
|
1771
|
+
const angleRad = Number(angleArg);
|
|
1772
|
+
if (!Number.isFinite(angleRad)) continue;
|
|
1773
|
+
functions.push({
|
|
1774
|
+
kind: "rotate",
|
|
1775
|
+
angleRad
|
|
1776
|
+
});
|
|
1777
|
+
} else if (name === "translate") {
|
|
1778
|
+
const xArg = args[0];
|
|
1779
|
+
if (xArg === void 0) continue;
|
|
1780
|
+
const yArg = args[1];
|
|
1781
|
+
const xPt = parseOdfLength(xArg);
|
|
1782
|
+
const yPt = yArg === void 0 ? 0 : parseOdfLength(yArg);
|
|
1783
|
+
if (xPt === void 0 || yPt === void 0) continue;
|
|
1784
|
+
functions.push({
|
|
1785
|
+
kind: "translate",
|
|
1786
|
+
xPt,
|
|
1787
|
+
yPt
|
|
1788
|
+
});
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
return functions;
|
|
1792
|
+
}
|
|
1793
|
+
function applyOdfTransform(functions, point) {
|
|
1794
|
+
let current = point;
|
|
1795
|
+
for (const fn of functions) if (fn.kind === "rotate") {
|
|
1796
|
+
const cos = Math.cos(fn.angleRad);
|
|
1797
|
+
const sin = Math.sin(fn.angleRad);
|
|
1798
|
+
current = {
|
|
1799
|
+
xPt: current.xPt * cos + current.yPt * sin,
|
|
1800
|
+
yPt: current.yPt * cos - current.xPt * sin
|
|
1801
|
+
};
|
|
1802
|
+
} else current = {
|
|
1803
|
+
xPt: current.xPt + fn.xPt,
|
|
1804
|
+
yPt: current.yPt + fn.yPt
|
|
1805
|
+
};
|
|
1806
|
+
return current;
|
|
1807
|
+
}
|
|
1808
|
+
function netRotationDeg(functions) {
|
|
1809
|
+
let totalRad = 0;
|
|
1810
|
+
for (const fn of functions) if (fn.kind === "rotate") totalRad += fn.angleRad;
|
|
1811
|
+
return -totalRad * 180 / Math.PI;
|
|
1812
|
+
}
|
|
1813
|
+
function resolveOdfShapeGeometry(element) {
|
|
1814
|
+
const transformValue = attrValue(element, "draw:transform");
|
|
1815
|
+
if (transformValue === void 0) {
|
|
1816
|
+
const box = parseBox(element);
|
|
1817
|
+
return box === void 0 ? void 0 : {
|
|
1818
|
+
frame: box,
|
|
1819
|
+
rotationDeg: void 0
|
|
1820
|
+
};
|
|
1821
|
+
}
|
|
1822
|
+
const widthValue = attrValue(element, "svg:width");
|
|
1823
|
+
const heightValue = attrValue(element, "svg:height");
|
|
1824
|
+
if (widthValue === void 0 || heightValue === void 0) return;
|
|
1825
|
+
const widthPt = parseOdfLength(widthValue);
|
|
1826
|
+
const heightPt = parseOdfLength(heightValue);
|
|
1827
|
+
if (widthPt === void 0 || heightPt === void 0) return;
|
|
1828
|
+
const functions = parseOdfTransform(transformValue);
|
|
1829
|
+
const center = applyOdfTransform(functions, {
|
|
1830
|
+
xPt: widthPt / 2,
|
|
1831
|
+
yPt: heightPt / 2
|
|
1832
|
+
});
|
|
1833
|
+
const rotationDeg = netRotationDeg(functions);
|
|
1834
|
+
return {
|
|
1835
|
+
frame: {
|
|
1836
|
+
xPt: center.xPt - widthPt / 2,
|
|
1837
|
+
yPt: center.yPt - heightPt / 2,
|
|
1838
|
+
widthPt,
|
|
1839
|
+
heightPt
|
|
1840
|
+
},
|
|
1841
|
+
rotationDeg: rotationDeg === 0 ? void 0 : rotationDeg
|
|
1842
|
+
};
|
|
1843
|
+
}
|
|
1844
|
+
function composeOdfGroupTransform(groupFunctions, child) {
|
|
1845
|
+
if (groupFunctions.length === 0) return child;
|
|
1846
|
+
const newCenter = applyOdfTransform(groupFunctions, {
|
|
1847
|
+
xPt: child.frame.xPt + child.frame.widthPt / 2,
|
|
1848
|
+
yPt: child.frame.yPt + child.frame.heightPt / 2
|
|
1849
|
+
});
|
|
1850
|
+
const newRotationDeg = (child.rotationDeg ?? 0) + netRotationDeg(groupFunctions);
|
|
1851
|
+
return {
|
|
1852
|
+
frame: {
|
|
1853
|
+
xPt: newCenter.xPt - child.frame.widthPt / 2,
|
|
1854
|
+
yPt: newCenter.yPt - child.frame.heightPt / 2,
|
|
1855
|
+
widthPt: child.frame.widthPt,
|
|
1856
|
+
heightPt: child.frame.heightPt
|
|
1857
|
+
},
|
|
1858
|
+
rotationDeg: newRotationDeg === 0 ? void 0 : newRotationDeg
|
|
1859
|
+
};
|
|
1860
|
+
}
|
|
1861
|
+
//#endregion
|
|
1862
|
+
//#region src/typed/draw/shapes.ts
|
|
1863
|
+
const ZERO_INSETS = {
|
|
1864
|
+
insetLeftPt: 0,
|
|
1865
|
+
insetTopPt: 0,
|
|
1866
|
+
insetRightPt: 0,
|
|
1867
|
+
insetBottomPt: 0
|
|
1868
|
+
};
|
|
1869
|
+
function readPaddingPt(props, attrName) {
|
|
1870
|
+
const value = attrValue(props, attrName);
|
|
1871
|
+
return value === void 0 ? void 0 : parseOdfLength(value);
|
|
1872
|
+
}
|
|
1873
|
+
function readFrameInsets(frame, pkg) {
|
|
1874
|
+
const { elements } = resolveStyleElementChain(attrValue(frame, "draw:style-name"), "graphic", pkg);
|
|
1875
|
+
let insets = ZERO_INSETS;
|
|
1876
|
+
for (const element of elements) {
|
|
1877
|
+
const props = childrenWithTag(element, "style:graphic-properties")[0];
|
|
1878
|
+
if (props === void 0) continue;
|
|
1879
|
+
insets = {
|
|
1880
|
+
insetLeftPt: readPaddingPt(props, "fo:padding-left") ?? insets.insetLeftPt,
|
|
1881
|
+
insetTopPt: readPaddingPt(props, "fo:padding-top") ?? insets.insetTopPt,
|
|
1882
|
+
insetRightPt: readPaddingPt(props, "fo:padding-right") ?? insets.insetRightPt,
|
|
1883
|
+
insetBottomPt: readPaddingPt(props, "fo:padding-bottom") ?? insets.insetBottomPt
|
|
1884
|
+
};
|
|
1885
|
+
}
|
|
1886
|
+
return insets;
|
|
1887
|
+
}
|
|
1888
|
+
function readDrawImageBlock(image, frameBox, pkg) {
|
|
1889
|
+
const href = attrValue(image, "xlink:href");
|
|
1890
|
+
const part = href === void 0 ? void 0 : pkg.parts[href];
|
|
1891
|
+
if (part?.kind !== "binary") return;
|
|
1892
|
+
const format = sniffImageFormat(base64ToBytes(part.base64));
|
|
1893
|
+
if (format === void 0) return;
|
|
1894
|
+
return {
|
|
1895
|
+
kind: "image",
|
|
1896
|
+
format,
|
|
1897
|
+
base64: part.base64,
|
|
1898
|
+
widthPt: frameBox.widthPt,
|
|
1899
|
+
heightPt: frameBox.heightPt
|
|
1900
|
+
};
|
|
1901
|
+
}
|
|
1902
|
+
function readDrawFrameContent(frame, frameBox, pkg) {
|
|
1903
|
+
const table = childrenWithTag(frame, "table:table")[0];
|
|
1904
|
+
if (table !== void 0) return [readOdfTable(table, pkg)];
|
|
1905
|
+
const textBox = childrenWithTag(frame, "draw:text-box")[0];
|
|
1906
|
+
if (textBox !== void 0) return elementsWithTag(textBox.children, "text:p").map((p) => readOdfParagraph(p, pkg));
|
|
1907
|
+
const image = childrenWithTag(frame, "draw:image")[0];
|
|
1908
|
+
if (image !== void 0) {
|
|
1909
|
+
const block = readDrawImageBlock(image, frameBox, pkg);
|
|
1910
|
+
return block === void 0 ? [] : [block];
|
|
1911
|
+
}
|
|
1912
|
+
return [];
|
|
1913
|
+
}
|
|
1914
|
+
function readDrawFrame(frame, groupFunctions, pkg) {
|
|
1915
|
+
const ownGeometry = resolveOdfShapeGeometry(frame);
|
|
1916
|
+
if (ownGeometry === void 0) return;
|
|
1917
|
+
const geometry = composeOdfGroupTransform(groupFunctions, ownGeometry);
|
|
1918
|
+
return {
|
|
1919
|
+
name: attrValue(frame, "draw:name"),
|
|
1920
|
+
frame: geometry.frame,
|
|
1921
|
+
rotationDeg: geometry.rotationDeg,
|
|
1922
|
+
...readFrameInsets(frame, pkg),
|
|
1923
|
+
blocks: readDrawFrameContent(frame, geometry.frame, pkg)
|
|
1924
|
+
};
|
|
1925
|
+
}
|
|
1926
|
+
function readOwnTransformFunctions(element) {
|
|
1927
|
+
const value = attrValue(element, "draw:transform");
|
|
1928
|
+
return value === void 0 ? [] : parseOdfTransform(value);
|
|
1929
|
+
}
|
|
1930
|
+
function walkDrawShapes(children, groupFunctions, pkg, out) {
|
|
1931
|
+
for (const node of children) {
|
|
1932
|
+
if (node.type !== "element") continue;
|
|
1933
|
+
if (node.tag === "draw:frame") {
|
|
1934
|
+
const shape = readDrawFrame(node, groupFunctions, pkg);
|
|
1935
|
+
if (shape !== void 0) out.push(shape);
|
|
1936
|
+
} else if (node.tag === "draw:g") {
|
|
1937
|
+
const ownFunctions = readOwnTransformFunctions(node);
|
|
1938
|
+
const nested = ownFunctions.length === 0 ? groupFunctions : [...ownFunctions, ...groupFunctions];
|
|
1939
|
+
walkDrawShapes(node.children, nested, pkg, out);
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
//#endregion
|
|
1944
|
+
//#region src/typed/odp/read.ts
|
|
1945
|
+
const CONTENT_PART = "content.xml";
|
|
1946
|
+
const STYLES_PART = "styles.xml";
|
|
1947
|
+
const AUTOMATIC_STYLE_PARTS = [CONTENT_PART, STYLES_PART];
|
|
1948
|
+
function findMasterPageElement(pkg, masterPageName) {
|
|
1949
|
+
if (masterPageName === void 0) return;
|
|
1950
|
+
const stylesPart = pkg.parts[STYLES_PART];
|
|
1951
|
+
if (stylesPart?.kind !== "xml") return;
|
|
1952
|
+
const root = rootElement(stylesPart.nodes);
|
|
1953
|
+
const masterStyles = root === void 0 ? void 0 : findChildElement(root.children, "office:master-styles");
|
|
1954
|
+
if (masterStyles === void 0) return;
|
|
1955
|
+
return childrenWithTag(masterStyles, "style:master-page").find((element) => attrValue(element, "style:name") === masterPageName);
|
|
1956
|
+
}
|
|
1957
|
+
function findPageLayoutElement(pkg, pageLayoutName) {
|
|
1958
|
+
if (pageLayoutName === void 0) return;
|
|
1959
|
+
for (const partPath of AUTOMATIC_STYLE_PARTS) {
|
|
1960
|
+
const part = pkg.parts[partPath];
|
|
1961
|
+
if (part?.kind !== "xml") continue;
|
|
1962
|
+
const root = rootElement(part.nodes);
|
|
1963
|
+
const automaticStyles = root === void 0 ? void 0 : findChildElement(root.children, "office:automatic-styles");
|
|
1964
|
+
if (automaticStyles === void 0) continue;
|
|
1965
|
+
const found = childrenWithTag(automaticStyles, "style:page-layout").find((element) => attrValue(element, "style:name") === pageLayoutName);
|
|
1966
|
+
if (found !== void 0) return found;
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
function readSlideSize(page, pkg) {
|
|
1970
|
+
const masterPage = findMasterPageElement(pkg, attrValue(page, "draw:master-page-name"));
|
|
1971
|
+
const pageLayout = findPageLayoutElement(pkg, masterPage === void 0 ? void 0 : attrValue(masterPage, "style:page-layout-name"));
|
|
1972
|
+
const properties = pageLayout === void 0 ? void 0 : childrenWithTag(pageLayout, "style:page-layout-properties")[0];
|
|
1973
|
+
return (properties === void 0 ? void 0 : parsePageSize(properties)) ?? document_content_model.SLIDE_SIZE_WIDESCREEN;
|
|
1974
|
+
}
|
|
1975
|
+
function readSlideNotes(page) {
|
|
1976
|
+
const notes = childrenWithTag(page, "presentation:notes")[0];
|
|
1977
|
+
if (notes === void 0) return "";
|
|
1978
|
+
return elementsWithTag(notes.children, "text:p").map(decodeOdfText).join("\n");
|
|
1979
|
+
}
|
|
1980
|
+
function readSlide(page, pkg) {
|
|
1981
|
+
const shapes = [];
|
|
1982
|
+
walkDrawShapes(page.children, [], pkg, shapes);
|
|
1983
|
+
return {
|
|
1984
|
+
size: readSlideSize(page, pkg),
|
|
1985
|
+
shapes,
|
|
1986
|
+
notes: readSlideNotes(page)
|
|
1987
|
+
};
|
|
1988
|
+
}
|
|
1989
|
+
function readOdp(pkg) {
|
|
1990
|
+
const contentPart = pkg.parts[CONTENT_PART];
|
|
1991
|
+
const root = contentPart?.kind === "xml" ? rootElement(contentPart.nodes) : void 0;
|
|
1992
|
+
const body = root === void 0 ? void 0 : findChildElement(root.children, "office:body");
|
|
1993
|
+
const presentation = body === void 0 ? void 0 : findChildElement(body.children, "office:presentation");
|
|
1994
|
+
const pages = presentation === void 0 ? [] : childrenWithTag(presentation, "draw:page");
|
|
1995
|
+
return {
|
|
1996
|
+
metadata: readOdfMetadata(pkg),
|
|
1997
|
+
slides: pages.map((page) => readSlide(page, pkg))
|
|
1998
|
+
};
|
|
1999
|
+
}
|
|
2000
|
+
//#endregion
|
|
1606
2001
|
Object.defineProperty(exports, "AlignmentSchema", {
|
|
1607
2002
|
enumerable: true,
|
|
1608
2003
|
get: function() {
|
|
@@ -1633,6 +2028,7 @@ exports.XmlNodeSchema = XmlNodeSchema;
|
|
|
1633
2028
|
exports.XmlPartSchema = XmlPartSchema;
|
|
1634
2029
|
exports.XmlPiSchema = XmlPiSchema;
|
|
1635
2030
|
exports.XmlTextSchema = XmlTextSchema;
|
|
2031
|
+
exports.applyOdfTransform = applyOdfTransform;
|
|
1636
2032
|
exports.attrValue = attrValue;
|
|
1637
2033
|
exports.base64ToBytes = base64ToBytes;
|
|
1638
2034
|
exports.buildManifest = buildManifest;
|
|
@@ -1643,14 +2039,17 @@ exports.canonicalPropertiesString = canonicalPropertiesString;
|
|
|
1643
2039
|
exports.cellReference = cellReference;
|
|
1644
2040
|
exports.childrenWithTag = childrenWithTag;
|
|
1645
2041
|
exports.columnIndexToLetters = columnIndexToLetters;
|
|
2042
|
+
exports.composeOdfGroupTransform = composeOdfGroupTransform;
|
|
1646
2043
|
exports.decodeOdfText = decodeOdfText;
|
|
1647
2044
|
exports.decodePackage = decodePackage;
|
|
1648
2045
|
exports.decodeXmlText = decodeXmlText;
|
|
1649
2046
|
exports.el = el;
|
|
2047
|
+
exports.elementsWithTag = elementsWithTag;
|
|
1650
2048
|
exports.encodePackage = encodePackage;
|
|
1651
2049
|
exports.encodeXmlText = encodeXmlText;
|
|
1652
2050
|
exports.ensureSpan = ensureSpan;
|
|
1653
2051
|
exports.findChildElement = findChildElement;
|
|
2052
|
+
exports.findStyleElement = findStyleElement;
|
|
1654
2053
|
exports.formatOdfColor = formatOdfColor;
|
|
1655
2054
|
exports.formatOdfLength = formatOdfLength;
|
|
1656
2055
|
exports.formatPercentageMultiplier = formatPercentageMultiplier;
|
|
@@ -1660,6 +2059,7 @@ exports.isStyleFamily = isStyleFamily;
|
|
|
1660
2059
|
exports.isXmlNode = isXmlNode;
|
|
1661
2060
|
exports.measureOdfNodeLength = measureOdfNodeLength;
|
|
1662
2061
|
exports.mediaTypeForExtension = mediaTypeForExtension;
|
|
2062
|
+
exports.netRotationDeg = netRotationDeg;
|
|
1663
2063
|
exports.packageCodec = packageCodec;
|
|
1664
2064
|
exports.paragraphPropertiesToAttributes = paragraphPropertiesToAttributes;
|
|
1665
2065
|
exports.parseBox = parseBox;
|
|
@@ -1667,16 +2067,23 @@ exports.parseLength = parseLength;
|
|
|
1667
2067
|
exports.parseMargins = parseMargins;
|
|
1668
2068
|
exports.parseOdfColor = parseOdfColor;
|
|
1669
2069
|
exports.parseOdfLength = parseOdfLength;
|
|
2070
|
+
exports.parseOdfTransform = parseOdfTransform;
|
|
1670
2071
|
exports.parsePackage = parsePackage;
|
|
1671
2072
|
exports.parsePageSize = parsePageSize;
|
|
1672
2073
|
exports.parseParagraphProperties = parseParagraphProperties;
|
|
1673
2074
|
exports.parseStyleElementProperties = parseStyleElementProperties;
|
|
1674
2075
|
exports.parseTextProperties = parseTextProperties;
|
|
1675
2076
|
exports.parseXml = parseXml;
|
|
2077
|
+
exports.readDrawFrame = readDrawFrame;
|
|
1676
2078
|
exports.readManifest = readManifest;
|
|
1677
2079
|
exports.readMimetype = readMimetype;
|
|
1678
2080
|
exports.readOdfMetadata = readOdfMetadata;
|
|
2081
|
+
exports.readOdfParagraph = readOdfParagraph;
|
|
2082
|
+
exports.readOdfTable = readOdfTable;
|
|
2083
|
+
exports.readOdp = readOdp;
|
|
2084
|
+
exports.resolveOdfShapeGeometry = resolveOdfShapeGeometry;
|
|
1679
2085
|
exports.resolveStyle = resolveStyle;
|
|
2086
|
+
exports.resolveStyleElementChain = resolveStyleElementChain;
|
|
1680
2087
|
exports.rootElement = rootElement;
|
|
1681
2088
|
exports.serializePackage = serializePackage;
|
|
1682
2089
|
exports.setDocumentMediaType = setDocumentMediaType;
|
|
@@ -1687,6 +2094,7 @@ exports.textPropertiesToAttributes = textPropertiesToAttributes;
|
|
|
1687
2094
|
exports.txt = txt;
|
|
1688
2095
|
exports.unzipPackage = unzipPackage;
|
|
1689
2096
|
exports.validateManifest = validateManifest;
|
|
2097
|
+
exports.walkDrawShapes = walkDrawShapes;
|
|
1690
2098
|
exports.writeManifest = writeManifest;
|
|
1691
2099
|
exports.writeMimetype = writeMimetype;
|
|
1692
2100
|
exports.xmlCodec = xmlCodec;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { Alignment, AlignmentSchema, Box, Color, LayoutMetadata, Margins, PageSize } from "document-content-model";
|
|
2
|
+
import { Alignment, AlignmentSchema, Box, Color, ContentParagraph, ContentShape, ContentSlide, ContentTable, LayoutMetadata, Margins, PageSize } from "document-content-model";
|
|
3
3
|
//#region src/model/node.d.ts
|
|
4
4
|
declare const AttributeSchema: z.ZodObject<{
|
|
5
5
|
name: z.ZodString;
|
|
@@ -480,6 +480,7 @@ declare function ensureSpan(paragraph: XmlElement, start: number, end: number, s
|
|
|
480
480
|
declare function rootElement(nodes: readonly XmlNode[]): XmlElement | undefined;
|
|
481
481
|
declare function findChildElement(nodes: readonly XmlNode[], tag: string): XmlElement | undefined;
|
|
482
482
|
declare function childrenWithTag(element: XmlElement, tag: string): XmlElement[];
|
|
483
|
+
declare function elementsWithTag(nodes: readonly XmlNode[], tag: string): XmlElement[];
|
|
483
484
|
declare function attrValue(element: XmlElement, name: string): string | undefined;
|
|
484
485
|
//#endregion
|
|
485
486
|
//#region src/typed/shared/a1.d.ts
|
|
@@ -518,10 +519,56 @@ interface StyleCascadeResult {
|
|
|
518
519
|
properties: StyleProperties;
|
|
519
520
|
diagnostics: CascadeDiagnostic[];
|
|
520
521
|
}
|
|
522
|
+
interface StyleElementChainResult {
|
|
523
|
+
elements: XmlElement[];
|
|
524
|
+
diagnostics: CascadeDiagnostic[];
|
|
525
|
+
}
|
|
526
|
+
declare function resolveStyleElementChain(styleName: string | undefined, family: StyleFamily, pkg: Package): StyleElementChainResult;
|
|
521
527
|
declare function resolveStyle(styleName: string | undefined, family: StyleFamily, pkg: Package): StyleCascadeResult;
|
|
528
|
+
declare function findStyleElement(styleName: string, family: StyleFamily, pkg: Package): XmlElement | undefined;
|
|
522
529
|
//#endregion
|
|
523
530
|
//#region src/typed/shared/metadata.d.ts
|
|
524
531
|
declare const META_PART = "meta.xml";
|
|
525
532
|
declare function readOdfMetadata(pkg: Package): LayoutMetadata;
|
|
526
533
|
//#endregion
|
|
527
|
-
|
|
534
|
+
//#region src/typed/shared/paragraph.d.ts
|
|
535
|
+
declare function readOdfParagraph(pElement: XmlElement, pkg: Package): ContentParagraph;
|
|
536
|
+
//#endregion
|
|
537
|
+
//#region src/typed/shared/table.d.ts
|
|
538
|
+
declare function readOdfTable(tableElement: XmlElement, pkg: Package): ContentTable;
|
|
539
|
+
//#endregion
|
|
540
|
+
//#region src/typed/shared/transform.d.ts
|
|
541
|
+
type OdfTransformFunction = {
|
|
542
|
+
readonly kind: 'rotate';
|
|
543
|
+
readonly angleRad: number;
|
|
544
|
+
} | {
|
|
545
|
+
readonly kind: 'translate';
|
|
546
|
+
readonly xPt: number;
|
|
547
|
+
readonly yPt: number;
|
|
548
|
+
};
|
|
549
|
+
interface OdfPoint {
|
|
550
|
+
readonly xPt: number;
|
|
551
|
+
readonly yPt: number;
|
|
552
|
+
}
|
|
553
|
+
declare function parseOdfTransform(value: string): OdfTransformFunction[];
|
|
554
|
+
declare function applyOdfTransform(functions: readonly OdfTransformFunction[], point: OdfPoint): OdfPoint;
|
|
555
|
+
declare function netRotationDeg(functions: readonly OdfTransformFunction[]): number;
|
|
556
|
+
interface OdfShapeGeometry {
|
|
557
|
+
readonly frame: Box;
|
|
558
|
+
readonly rotationDeg: number | undefined;
|
|
559
|
+
}
|
|
560
|
+
declare function resolveOdfShapeGeometry(element: XmlElement): OdfShapeGeometry | undefined;
|
|
561
|
+
declare function composeOdfGroupTransform(groupFunctions: readonly OdfTransformFunction[], child: OdfShapeGeometry): OdfShapeGeometry;
|
|
562
|
+
//#endregion
|
|
563
|
+
//#region src/typed/draw/shapes.d.ts
|
|
564
|
+
declare function readDrawFrame(frame: XmlElement, groupFunctions: readonly OdfTransformFunction[], pkg: Package): ContentShape | undefined;
|
|
565
|
+
declare function walkDrawShapes(children: readonly XmlNode[], groupFunctions: readonly OdfTransformFunction[], pkg: Package, out: ContentShape[]): void;
|
|
566
|
+
//#endregion
|
|
567
|
+
//#region src/typed/odp/read.d.ts
|
|
568
|
+
interface OdpDocument {
|
|
569
|
+
metadata: LayoutMetadata;
|
|
570
|
+
slides: ContentSlide[];
|
|
571
|
+
}
|
|
572
|
+
declare function readOdp(pkg: Package): OdpDocument;
|
|
573
|
+
//#endregion
|
|
574
|
+
export { type Alignment, AlignmentSchema, type Attribute, AttributeSchema, type BinaryPart, BinaryPartSchema, type BuildManifestOptions, type CascadeDiagnostic, type ImageFormat, type InternRequest, type LengthUnit, MANIFEST_PART, META_PART, MIMETYPE_PART, type Manifest, type ManifestEntry, ManifestEntrySchema, type ManifestProblem, ManifestProblemSchema, ManifestSchema, ODF_MEDIA_TYPES, ODF_NAMESPACES, type OdfExtension, type OdfNamespacePrefix, type OdfPoint, type OdfShapeGeometry, type OdfTransformFunction, type OdpDocument, type OtherPartRef, type Package, PackageSchema, type ParsedProperties, type Part, PartSchema, STYLE_FAMILIES, type StyleCascadeResult, type StyleElementChainResult, type StyleFamily, type StyleProperties, StylePropertiesSchema, StyleRegistry, type StyleRegistryOptions, TableCursor, type XmlCdata, XmlCdataSchema, type XmlComment, XmlCommentSchema, type XmlDeclaration, XmlDeclarationSchema, type XmlElement, XmlElementSchema, type XmlNode, XmlNodeSchema, type XmlPart, XmlPartSchema, type XmlPi, XmlPiSchema, type XmlText, XmlTextSchema, type ZipEntry, applyOdfTransform, attrValue, base64ToBytes, buildManifest, buildStylePropertyElements, buildXml, bytesToBase64, canonicalPropertiesString, cellReference, childrenWithTag, columnIndexToLetters, composeOdfGroupTransform, decodeOdfText, decodePackage, decodeXmlText, el, elementsWithTag, encodePackage, encodeXmlText, ensureSpan, findChildElement, findStyleElement, formatOdfColor, formatOdfLength, formatPercentageMultiplier, formatPt, getOdfSpaceCount, isStyleFamily, isXmlNode, measureOdfNodeLength, mediaTypeForExtension, netRotationDeg, packageCodec, paragraphPropertiesToAttributes, parseBox, parseLength, parseMargins, parseOdfColor, parseOdfLength, parseOdfTransform, parsePackage, parsePageSize, parseParagraphProperties, parseStyleElementProperties, parseTextProperties, parseXml, readDrawFrame, readManifest, readMimetype, readOdfMetadata, readOdfParagraph, readOdfTable, readOdp, resolveOdfShapeGeometry, resolveStyle, resolveStyleElementChain, rootElement, serializePackage, setDocumentMediaType, sniffImageFormat, sumOdfNodeLength, syncManifest, textPropertiesToAttributes, txt, unzipPackage, validateManifest, walkDrawShapes, writeManifest, writeMimetype, xmlCodec, xmlnsAttributes, zipPackage };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { Alignment, AlignmentSchema, Box, Color, LayoutMetadata, Margins, PageSize } from "document-content-model";
|
|
2
|
+
import { Alignment, AlignmentSchema, Box, Color, ContentParagraph, ContentShape, ContentSlide, ContentTable, LayoutMetadata, Margins, PageSize } from "document-content-model";
|
|
3
3
|
//#region src/model/node.d.ts
|
|
4
4
|
declare const AttributeSchema: z.ZodObject<{
|
|
5
5
|
name: z.ZodString;
|
|
@@ -480,6 +480,7 @@ declare function ensureSpan(paragraph: XmlElement, start: number, end: number, s
|
|
|
480
480
|
declare function rootElement(nodes: readonly XmlNode[]): XmlElement | undefined;
|
|
481
481
|
declare function findChildElement(nodes: readonly XmlNode[], tag: string): XmlElement | undefined;
|
|
482
482
|
declare function childrenWithTag(element: XmlElement, tag: string): XmlElement[];
|
|
483
|
+
declare function elementsWithTag(nodes: readonly XmlNode[], tag: string): XmlElement[];
|
|
483
484
|
declare function attrValue(element: XmlElement, name: string): string | undefined;
|
|
484
485
|
//#endregion
|
|
485
486
|
//#region src/typed/shared/a1.d.ts
|
|
@@ -518,10 +519,56 @@ interface StyleCascadeResult {
|
|
|
518
519
|
properties: StyleProperties;
|
|
519
520
|
diagnostics: CascadeDiagnostic[];
|
|
520
521
|
}
|
|
522
|
+
interface StyleElementChainResult {
|
|
523
|
+
elements: XmlElement[];
|
|
524
|
+
diagnostics: CascadeDiagnostic[];
|
|
525
|
+
}
|
|
526
|
+
declare function resolveStyleElementChain(styleName: string | undefined, family: StyleFamily, pkg: Package): StyleElementChainResult;
|
|
521
527
|
declare function resolveStyle(styleName: string | undefined, family: StyleFamily, pkg: Package): StyleCascadeResult;
|
|
528
|
+
declare function findStyleElement(styleName: string, family: StyleFamily, pkg: Package): XmlElement | undefined;
|
|
522
529
|
//#endregion
|
|
523
530
|
//#region src/typed/shared/metadata.d.ts
|
|
524
531
|
declare const META_PART = "meta.xml";
|
|
525
532
|
declare function readOdfMetadata(pkg: Package): LayoutMetadata;
|
|
526
533
|
//#endregion
|
|
527
|
-
|
|
534
|
+
//#region src/typed/shared/paragraph.d.ts
|
|
535
|
+
declare function readOdfParagraph(pElement: XmlElement, pkg: Package): ContentParagraph;
|
|
536
|
+
//#endregion
|
|
537
|
+
//#region src/typed/shared/table.d.ts
|
|
538
|
+
declare function readOdfTable(tableElement: XmlElement, pkg: Package): ContentTable;
|
|
539
|
+
//#endregion
|
|
540
|
+
//#region src/typed/shared/transform.d.ts
|
|
541
|
+
type OdfTransformFunction = {
|
|
542
|
+
readonly kind: 'rotate';
|
|
543
|
+
readonly angleRad: number;
|
|
544
|
+
} | {
|
|
545
|
+
readonly kind: 'translate';
|
|
546
|
+
readonly xPt: number;
|
|
547
|
+
readonly yPt: number;
|
|
548
|
+
};
|
|
549
|
+
interface OdfPoint {
|
|
550
|
+
readonly xPt: number;
|
|
551
|
+
readonly yPt: number;
|
|
552
|
+
}
|
|
553
|
+
declare function parseOdfTransform(value: string): OdfTransformFunction[];
|
|
554
|
+
declare function applyOdfTransform(functions: readonly OdfTransformFunction[], point: OdfPoint): OdfPoint;
|
|
555
|
+
declare function netRotationDeg(functions: readonly OdfTransformFunction[]): number;
|
|
556
|
+
interface OdfShapeGeometry {
|
|
557
|
+
readonly frame: Box;
|
|
558
|
+
readonly rotationDeg: number | undefined;
|
|
559
|
+
}
|
|
560
|
+
declare function resolveOdfShapeGeometry(element: XmlElement): OdfShapeGeometry | undefined;
|
|
561
|
+
declare function composeOdfGroupTransform(groupFunctions: readonly OdfTransformFunction[], child: OdfShapeGeometry): OdfShapeGeometry;
|
|
562
|
+
//#endregion
|
|
563
|
+
//#region src/typed/draw/shapes.d.ts
|
|
564
|
+
declare function readDrawFrame(frame: XmlElement, groupFunctions: readonly OdfTransformFunction[], pkg: Package): ContentShape | undefined;
|
|
565
|
+
declare function walkDrawShapes(children: readonly XmlNode[], groupFunctions: readonly OdfTransformFunction[], pkg: Package, out: ContentShape[]): void;
|
|
566
|
+
//#endregion
|
|
567
|
+
//#region src/typed/odp/read.d.ts
|
|
568
|
+
interface OdpDocument {
|
|
569
|
+
metadata: LayoutMetadata;
|
|
570
|
+
slides: ContentSlide[];
|
|
571
|
+
}
|
|
572
|
+
declare function readOdp(pkg: Package): OdpDocument;
|
|
573
|
+
//#endregion
|
|
574
|
+
export { type Alignment, AlignmentSchema, type Attribute, AttributeSchema, type BinaryPart, BinaryPartSchema, type BuildManifestOptions, type CascadeDiagnostic, type ImageFormat, type InternRequest, type LengthUnit, MANIFEST_PART, META_PART, MIMETYPE_PART, type Manifest, type ManifestEntry, ManifestEntrySchema, type ManifestProblem, ManifestProblemSchema, ManifestSchema, ODF_MEDIA_TYPES, ODF_NAMESPACES, type OdfExtension, type OdfNamespacePrefix, type OdfPoint, type OdfShapeGeometry, type OdfTransformFunction, type OdpDocument, type OtherPartRef, type Package, PackageSchema, type ParsedProperties, type Part, PartSchema, STYLE_FAMILIES, type StyleCascadeResult, type StyleElementChainResult, type StyleFamily, type StyleProperties, StylePropertiesSchema, StyleRegistry, type StyleRegistryOptions, TableCursor, type XmlCdata, XmlCdataSchema, type XmlComment, XmlCommentSchema, type XmlDeclaration, XmlDeclarationSchema, type XmlElement, XmlElementSchema, type XmlNode, XmlNodeSchema, type XmlPart, XmlPartSchema, type XmlPi, XmlPiSchema, type XmlText, XmlTextSchema, type ZipEntry, applyOdfTransform, attrValue, base64ToBytes, buildManifest, buildStylePropertyElements, buildXml, bytesToBase64, canonicalPropertiesString, cellReference, childrenWithTag, columnIndexToLetters, composeOdfGroupTransform, decodeOdfText, decodePackage, decodeXmlText, el, elementsWithTag, encodePackage, encodeXmlText, ensureSpan, findChildElement, findStyleElement, formatOdfColor, formatOdfLength, formatPercentageMultiplier, formatPt, getOdfSpaceCount, isStyleFamily, isXmlNode, measureOdfNodeLength, mediaTypeForExtension, netRotationDeg, packageCodec, paragraphPropertiesToAttributes, parseBox, parseLength, parseMargins, parseOdfColor, parseOdfLength, parseOdfTransform, parsePackage, parsePageSize, parseParagraphProperties, parseStyleElementProperties, parseTextProperties, parseXml, readDrawFrame, readManifest, readMimetype, readOdfMetadata, readOdfParagraph, readOdfTable, readOdp, resolveOdfShapeGeometry, resolveStyle, resolveStyleElementChain, rootElement, serializePackage, setDocumentMediaType, sniffImageFormat, sumOdfNodeLength, syncManifest, textPropertiesToAttributes, txt, unzipPackage, validateManifest, walkDrawShapes, writeManifest, writeMimetype, xmlCodec, xmlnsAttributes, zipPackage };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { XMLBuilder, XMLParser } from "fast-xml-parser";
|
|
3
3
|
import { unzipSync, zipSync } from "fflate";
|
|
4
|
-
import { AlignmentSchema, AlignmentSchema as AlignmentSchema$1, ColorSchema, colorToRgbHex, rgbHexToColor } from "document-content-model";
|
|
4
|
+
import { AlignmentSchema, AlignmentSchema as AlignmentSchema$1, ColorSchema, SLIDE_SIZE_WIDESCREEN, colorToRgbHex, rgbHexToColor } from "document-content-model";
|
|
5
5
|
//#region src/model/node.ts
|
|
6
6
|
const AttributeSchema = z.object({
|
|
7
7
|
name: z.string(),
|
|
@@ -1242,6 +1242,17 @@ function childrenWithTag(element, tag) {
|
|
|
1242
1242
|
for (const child of element.children) if (child.type === "element" && child.tag === tag) out.push(child);
|
|
1243
1243
|
return out;
|
|
1244
1244
|
}
|
|
1245
|
+
function* walk(nodes) {
|
|
1246
|
+
for (const node of nodes) {
|
|
1247
|
+
yield node;
|
|
1248
|
+
if (node.type === "element") yield* walk(node.children);
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
function elementsWithTag(nodes, tag) {
|
|
1252
|
+
const out = [];
|
|
1253
|
+
for (const node of walk(nodes)) if (node.type === "element" && node.tag === tag) out.push(node);
|
|
1254
|
+
return out;
|
|
1255
|
+
}
|
|
1245
1256
|
function attrValue(element, name) {
|
|
1246
1257
|
return element.attributes.find((attribute) => attribute.name === name)?.value;
|
|
1247
1258
|
}
|
|
@@ -1521,13 +1532,14 @@ function collectStyles(pkg) {
|
|
|
1521
1532
|
defaultByFamily
|
|
1522
1533
|
};
|
|
1523
1534
|
}
|
|
1524
|
-
function
|
|
1535
|
+
function resolveStyleElementChain(styleName, family, pkg) {
|
|
1525
1536
|
const { byName, defaultByFamily } = collectStyles(pkg);
|
|
1526
1537
|
const diagnostics = [];
|
|
1538
|
+
const elements = [];
|
|
1527
1539
|
const defaultElement = defaultByFamily.get(family);
|
|
1528
|
-
|
|
1540
|
+
if (defaultElement !== void 0) elements.push(defaultElement);
|
|
1529
1541
|
if (styleName === void 0) return {
|
|
1530
|
-
|
|
1542
|
+
elements,
|
|
1531
1543
|
diagnostics
|
|
1532
1544
|
};
|
|
1533
1545
|
const chain = [];
|
|
@@ -1555,7 +1567,16 @@ function resolveStyle(styleName, family, pkg) {
|
|
|
1555
1567
|
currentName = attrValue(element, "style:parent-style-name");
|
|
1556
1568
|
}
|
|
1557
1569
|
chain.reverse();
|
|
1558
|
-
|
|
1570
|
+
elements.push(...chain);
|
|
1571
|
+
return {
|
|
1572
|
+
elements,
|
|
1573
|
+
diagnostics
|
|
1574
|
+
};
|
|
1575
|
+
}
|
|
1576
|
+
function resolveStyle(styleName, family, pkg) {
|
|
1577
|
+
const { elements, diagnostics } = resolveStyleElementChain(styleName, family, pkg);
|
|
1578
|
+
let properties = {};
|
|
1579
|
+
for (const element of elements) properties = {
|
|
1559
1580
|
...properties,
|
|
1560
1581
|
...parseStyleElementProperties(element).properties
|
|
1561
1582
|
};
|
|
@@ -1564,6 +1585,10 @@ function resolveStyle(styleName, family, pkg) {
|
|
|
1564
1585
|
diagnostics
|
|
1565
1586
|
};
|
|
1566
1587
|
}
|
|
1588
|
+
function findStyleElement(styleName, family, pkg) {
|
|
1589
|
+
const { byName } = collectStyles(pkg);
|
|
1590
|
+
return byName.get(nameKey(family, styleName));
|
|
1591
|
+
}
|
|
1567
1592
|
//#endregion
|
|
1568
1593
|
//#region src/typed/shared/metadata.ts
|
|
1569
1594
|
const META_PART = "meta.xml";
|
|
@@ -1602,4 +1627,374 @@ function readOdfMetadata(pkg) {
|
|
|
1602
1627
|
return metadata;
|
|
1603
1628
|
}
|
|
1604
1629
|
//#endregion
|
|
1605
|
-
|
|
1630
|
+
//#region src/typed/shared/paragraph.ts
|
|
1631
|
+
function collectRuns(nodes, baseProperties, pkg, out) {
|
|
1632
|
+
for (const node of nodes) {
|
|
1633
|
+
if (node.type === "text") {
|
|
1634
|
+
if (node.value.length > 0) out.push(runFromText(decodeXmlText(node.value), baseProperties));
|
|
1635
|
+
continue;
|
|
1636
|
+
}
|
|
1637
|
+
if (node.type !== "element") continue;
|
|
1638
|
+
if (node.tag === "text:s") out.push(runFromText(" ".repeat(getOdfSpaceCount(node)), baseProperties));
|
|
1639
|
+
else if (node.tag === "text:tab") out.push(runFromText(" ", baseProperties));
|
|
1640
|
+
else if (node.tag === "text:line-break") out.push(runFromText("\n", baseProperties));
|
|
1641
|
+
else if (node.tag === "text:span") {
|
|
1642
|
+
const styleName = attrValue(node, "text:style-name");
|
|
1643
|
+
const spanProperties = {
|
|
1644
|
+
...baseProperties,
|
|
1645
|
+
...resolveStyle(styleName, "text", pkg).properties
|
|
1646
|
+
};
|
|
1647
|
+
collectRuns(node.children, spanProperties, pkg, out);
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
function runFromText(text, properties) {
|
|
1652
|
+
return {
|
|
1653
|
+
text,
|
|
1654
|
+
bold: properties.bold,
|
|
1655
|
+
italic: properties.italic,
|
|
1656
|
+
underline: properties.underline,
|
|
1657
|
+
strike: properties.strike,
|
|
1658
|
+
fontFamily: properties.fontFamily,
|
|
1659
|
+
sizePt: properties.sizePt,
|
|
1660
|
+
color: properties.color
|
|
1661
|
+
};
|
|
1662
|
+
}
|
|
1663
|
+
function readOdfParagraph(pElement, pkg) {
|
|
1664
|
+
const styleName = attrValue(pElement, "text:style-name");
|
|
1665
|
+
const paragraphProperties = resolveStyle(styleName, "paragraph", pkg).properties;
|
|
1666
|
+
const runs = [];
|
|
1667
|
+
collectRuns(pElement.children, paragraphProperties, pkg, runs);
|
|
1668
|
+
return {
|
|
1669
|
+
kind: "paragraph",
|
|
1670
|
+
runs,
|
|
1671
|
+
styleId: styleName,
|
|
1672
|
+
alignment: paragraphProperties.alignment,
|
|
1673
|
+
spacingBeforePt: paragraphProperties.spacingBeforePt,
|
|
1674
|
+
spacingAfterPt: paragraphProperties.spacingAfterPt,
|
|
1675
|
+
lineSpacing: paragraphProperties.lineSpacing,
|
|
1676
|
+
indentLeftPt: paragraphProperties.indentLeftPt,
|
|
1677
|
+
indentFirstLinePt: paragraphProperties.indentFirstLinePt
|
|
1678
|
+
};
|
|
1679
|
+
}
|
|
1680
|
+
//#endregion
|
|
1681
|
+
//#region src/typed/shared/table.ts
|
|
1682
|
+
function readRepeatCount(element, attrName) {
|
|
1683
|
+
const raw = attrValue(element, attrName);
|
|
1684
|
+
if (raw === void 0) return 1;
|
|
1685
|
+
const parsed = Number.parseInt(raw, 10);
|
|
1686
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : 1;
|
|
1687
|
+
}
|
|
1688
|
+
function resolveColumnWidthPt(columnElement, pkg) {
|
|
1689
|
+
const styleName = attrValue(columnElement, "table:style-name");
|
|
1690
|
+
const styleElement = styleName === void 0 ? void 0 : findStyleElement(styleName, "table-column", pkg);
|
|
1691
|
+
const props = styleElement === void 0 ? void 0 : childrenWithTag(styleElement, "style:table-column-properties")[0];
|
|
1692
|
+
const widthValue = props === void 0 ? void 0 : attrValue(props, "style:column-width");
|
|
1693
|
+
return widthValue === void 0 ? 0 : parseOdfLength(widthValue) ?? 0;
|
|
1694
|
+
}
|
|
1695
|
+
function resolveRowHeightPt(rowElement, pkg) {
|
|
1696
|
+
const styleName = attrValue(rowElement, "table:style-name");
|
|
1697
|
+
const styleElement = styleName === void 0 ? void 0 : findStyleElement(styleName, "table-row", pkg);
|
|
1698
|
+
const props = styleElement === void 0 ? void 0 : childrenWithTag(styleElement, "style:table-row-properties")[0];
|
|
1699
|
+
const heightValue = props === void 0 ? void 0 : attrValue(props, "style:row-height");
|
|
1700
|
+
return heightValue === void 0 ? void 0 : parseOdfLength(heightValue);
|
|
1701
|
+
}
|
|
1702
|
+
function readTableCellBackground(cellElement, pkg) {
|
|
1703
|
+
const styleName = attrValue(cellElement, "table:style-name");
|
|
1704
|
+
const styleElement = styleName === void 0 ? void 0 : findStyleElement(styleName, "table-cell", pkg);
|
|
1705
|
+
const props = styleElement === void 0 ? void 0 : childrenWithTag(styleElement, "style:table-cell-properties")[0];
|
|
1706
|
+
const value = props === void 0 ? void 0 : attrValue(props, "fo:background-color");
|
|
1707
|
+
return value === void 0 ? void 0 : parseOdfColor(value);
|
|
1708
|
+
}
|
|
1709
|
+
function readTableCell(cellElement, pkg) {
|
|
1710
|
+
const blocks = childrenWithTag(cellElement, "text:p").map((p) => readOdfParagraph(p, pkg));
|
|
1711
|
+
const colSpanRaw = attrValue(cellElement, "table:number-columns-spanned");
|
|
1712
|
+
const rowSpanRaw = attrValue(cellElement, "table:number-rows-spanned");
|
|
1713
|
+
return {
|
|
1714
|
+
blocks,
|
|
1715
|
+
colSpan: colSpanRaw === void 0 ? void 0 : Number.parseInt(colSpanRaw, 10),
|
|
1716
|
+
rowSpan: rowSpanRaw === void 0 ? void 0 : Number.parseInt(rowSpanRaw, 10),
|
|
1717
|
+
background: readTableCellBackground(cellElement, pkg)
|
|
1718
|
+
};
|
|
1719
|
+
}
|
|
1720
|
+
function readTableRow(rowElement, pkg) {
|
|
1721
|
+
const cells = [];
|
|
1722
|
+
for (const child of rowElement.children) {
|
|
1723
|
+
if (child.type !== "element") continue;
|
|
1724
|
+
if (child.tag === "table:covered-table-cell") {
|
|
1725
|
+
const repeat = readRepeatCount(child, "table:number-columns-repeated");
|
|
1726
|
+
for (let i = 0; i < repeat; i++) cells.push({ blocks: [] });
|
|
1727
|
+
} else if (child.tag === "table:table-cell") {
|
|
1728
|
+
const cell = readTableCell(child, pkg);
|
|
1729
|
+
const repeat = readRepeatCount(child, "table:number-columns-repeated");
|
|
1730
|
+
for (let i = 0; i < repeat; i++) cells.push(cell);
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
return {
|
|
1734
|
+
cells,
|
|
1735
|
+
heightPt: resolveRowHeightPt(rowElement, pkg)
|
|
1736
|
+
};
|
|
1737
|
+
}
|
|
1738
|
+
function readOdfTable(tableElement, pkg) {
|
|
1739
|
+
const columnWidthsPt = [];
|
|
1740
|
+
for (const column of childrenWithTag(tableElement, "table:table-column")) {
|
|
1741
|
+
const widthPt = resolveColumnWidthPt(column, pkg);
|
|
1742
|
+
const repeat = readRepeatCount(column, "table:number-columns-repeated");
|
|
1743
|
+
for (let i = 0; i < repeat; i++) columnWidthsPt.push(widthPt);
|
|
1744
|
+
}
|
|
1745
|
+
const rows = [];
|
|
1746
|
+
for (const rowElement of childrenWithTag(tableElement, "table:table-row")) {
|
|
1747
|
+
const row = readTableRow(rowElement, pkg);
|
|
1748
|
+
const repeat = readRepeatCount(rowElement, "table:number-rows-repeated");
|
|
1749
|
+
for (let i = 0; i < repeat; i++) rows.push(row);
|
|
1750
|
+
}
|
|
1751
|
+
return {
|
|
1752
|
+
kind: "table",
|
|
1753
|
+
rows,
|
|
1754
|
+
columnWidthsPt
|
|
1755
|
+
};
|
|
1756
|
+
}
|
|
1757
|
+
//#endregion
|
|
1758
|
+
//#region src/typed/shared/transform.ts
|
|
1759
|
+
const FUNCTION_PATTERN = /([a-zA-Z]+)\s*\(\s*([^)]*?)\s*\)/g;
|
|
1760
|
+
function parseOdfTransform(value) {
|
|
1761
|
+
const functions = [];
|
|
1762
|
+
for (const match of value.matchAll(FUNCTION_PATTERN)) {
|
|
1763
|
+
const name = match[1];
|
|
1764
|
+
const argsRaw = match[2];
|
|
1765
|
+
if (name === void 0 || argsRaw === void 0) continue;
|
|
1766
|
+
const args = argsRaw.split(/\s+/).filter((arg) => arg.length > 0);
|
|
1767
|
+
if (name === "rotate") {
|
|
1768
|
+
const angleArg = args[0];
|
|
1769
|
+
if (angleArg === void 0) continue;
|
|
1770
|
+
const angleRad = Number(angleArg);
|
|
1771
|
+
if (!Number.isFinite(angleRad)) continue;
|
|
1772
|
+
functions.push({
|
|
1773
|
+
kind: "rotate",
|
|
1774
|
+
angleRad
|
|
1775
|
+
});
|
|
1776
|
+
} else if (name === "translate") {
|
|
1777
|
+
const xArg = args[0];
|
|
1778
|
+
if (xArg === void 0) continue;
|
|
1779
|
+
const yArg = args[1];
|
|
1780
|
+
const xPt = parseOdfLength(xArg);
|
|
1781
|
+
const yPt = yArg === void 0 ? 0 : parseOdfLength(yArg);
|
|
1782
|
+
if (xPt === void 0 || yPt === void 0) continue;
|
|
1783
|
+
functions.push({
|
|
1784
|
+
kind: "translate",
|
|
1785
|
+
xPt,
|
|
1786
|
+
yPt
|
|
1787
|
+
});
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
return functions;
|
|
1791
|
+
}
|
|
1792
|
+
function applyOdfTransform(functions, point) {
|
|
1793
|
+
let current = point;
|
|
1794
|
+
for (const fn of functions) if (fn.kind === "rotate") {
|
|
1795
|
+
const cos = Math.cos(fn.angleRad);
|
|
1796
|
+
const sin = Math.sin(fn.angleRad);
|
|
1797
|
+
current = {
|
|
1798
|
+
xPt: current.xPt * cos + current.yPt * sin,
|
|
1799
|
+
yPt: current.yPt * cos - current.xPt * sin
|
|
1800
|
+
};
|
|
1801
|
+
} else current = {
|
|
1802
|
+
xPt: current.xPt + fn.xPt,
|
|
1803
|
+
yPt: current.yPt + fn.yPt
|
|
1804
|
+
};
|
|
1805
|
+
return current;
|
|
1806
|
+
}
|
|
1807
|
+
function netRotationDeg(functions) {
|
|
1808
|
+
let totalRad = 0;
|
|
1809
|
+
for (const fn of functions) if (fn.kind === "rotate") totalRad += fn.angleRad;
|
|
1810
|
+
return -totalRad * 180 / Math.PI;
|
|
1811
|
+
}
|
|
1812
|
+
function resolveOdfShapeGeometry(element) {
|
|
1813
|
+
const transformValue = attrValue(element, "draw:transform");
|
|
1814
|
+
if (transformValue === void 0) {
|
|
1815
|
+
const box = parseBox(element);
|
|
1816
|
+
return box === void 0 ? void 0 : {
|
|
1817
|
+
frame: box,
|
|
1818
|
+
rotationDeg: void 0
|
|
1819
|
+
};
|
|
1820
|
+
}
|
|
1821
|
+
const widthValue = attrValue(element, "svg:width");
|
|
1822
|
+
const heightValue = attrValue(element, "svg:height");
|
|
1823
|
+
if (widthValue === void 0 || heightValue === void 0) return;
|
|
1824
|
+
const widthPt = parseOdfLength(widthValue);
|
|
1825
|
+
const heightPt = parseOdfLength(heightValue);
|
|
1826
|
+
if (widthPt === void 0 || heightPt === void 0) return;
|
|
1827
|
+
const functions = parseOdfTransform(transformValue);
|
|
1828
|
+
const center = applyOdfTransform(functions, {
|
|
1829
|
+
xPt: widthPt / 2,
|
|
1830
|
+
yPt: heightPt / 2
|
|
1831
|
+
});
|
|
1832
|
+
const rotationDeg = netRotationDeg(functions);
|
|
1833
|
+
return {
|
|
1834
|
+
frame: {
|
|
1835
|
+
xPt: center.xPt - widthPt / 2,
|
|
1836
|
+
yPt: center.yPt - heightPt / 2,
|
|
1837
|
+
widthPt,
|
|
1838
|
+
heightPt
|
|
1839
|
+
},
|
|
1840
|
+
rotationDeg: rotationDeg === 0 ? void 0 : rotationDeg
|
|
1841
|
+
};
|
|
1842
|
+
}
|
|
1843
|
+
function composeOdfGroupTransform(groupFunctions, child) {
|
|
1844
|
+
if (groupFunctions.length === 0) return child;
|
|
1845
|
+
const newCenter = applyOdfTransform(groupFunctions, {
|
|
1846
|
+
xPt: child.frame.xPt + child.frame.widthPt / 2,
|
|
1847
|
+
yPt: child.frame.yPt + child.frame.heightPt / 2
|
|
1848
|
+
});
|
|
1849
|
+
const newRotationDeg = (child.rotationDeg ?? 0) + netRotationDeg(groupFunctions);
|
|
1850
|
+
return {
|
|
1851
|
+
frame: {
|
|
1852
|
+
xPt: newCenter.xPt - child.frame.widthPt / 2,
|
|
1853
|
+
yPt: newCenter.yPt - child.frame.heightPt / 2,
|
|
1854
|
+
widthPt: child.frame.widthPt,
|
|
1855
|
+
heightPt: child.frame.heightPt
|
|
1856
|
+
},
|
|
1857
|
+
rotationDeg: newRotationDeg === 0 ? void 0 : newRotationDeg
|
|
1858
|
+
};
|
|
1859
|
+
}
|
|
1860
|
+
//#endregion
|
|
1861
|
+
//#region src/typed/draw/shapes.ts
|
|
1862
|
+
const ZERO_INSETS = {
|
|
1863
|
+
insetLeftPt: 0,
|
|
1864
|
+
insetTopPt: 0,
|
|
1865
|
+
insetRightPt: 0,
|
|
1866
|
+
insetBottomPt: 0
|
|
1867
|
+
};
|
|
1868
|
+
function readPaddingPt(props, attrName) {
|
|
1869
|
+
const value = attrValue(props, attrName);
|
|
1870
|
+
return value === void 0 ? void 0 : parseOdfLength(value);
|
|
1871
|
+
}
|
|
1872
|
+
function readFrameInsets(frame, pkg) {
|
|
1873
|
+
const { elements } = resolveStyleElementChain(attrValue(frame, "draw:style-name"), "graphic", pkg);
|
|
1874
|
+
let insets = ZERO_INSETS;
|
|
1875
|
+
for (const element of elements) {
|
|
1876
|
+
const props = childrenWithTag(element, "style:graphic-properties")[0];
|
|
1877
|
+
if (props === void 0) continue;
|
|
1878
|
+
insets = {
|
|
1879
|
+
insetLeftPt: readPaddingPt(props, "fo:padding-left") ?? insets.insetLeftPt,
|
|
1880
|
+
insetTopPt: readPaddingPt(props, "fo:padding-top") ?? insets.insetTopPt,
|
|
1881
|
+
insetRightPt: readPaddingPt(props, "fo:padding-right") ?? insets.insetRightPt,
|
|
1882
|
+
insetBottomPt: readPaddingPt(props, "fo:padding-bottom") ?? insets.insetBottomPt
|
|
1883
|
+
};
|
|
1884
|
+
}
|
|
1885
|
+
return insets;
|
|
1886
|
+
}
|
|
1887
|
+
function readDrawImageBlock(image, frameBox, pkg) {
|
|
1888
|
+
const href = attrValue(image, "xlink:href");
|
|
1889
|
+
const part = href === void 0 ? void 0 : pkg.parts[href];
|
|
1890
|
+
if (part?.kind !== "binary") return;
|
|
1891
|
+
const format = sniffImageFormat(base64ToBytes(part.base64));
|
|
1892
|
+
if (format === void 0) return;
|
|
1893
|
+
return {
|
|
1894
|
+
kind: "image",
|
|
1895
|
+
format,
|
|
1896
|
+
base64: part.base64,
|
|
1897
|
+
widthPt: frameBox.widthPt,
|
|
1898
|
+
heightPt: frameBox.heightPt
|
|
1899
|
+
};
|
|
1900
|
+
}
|
|
1901
|
+
function readDrawFrameContent(frame, frameBox, pkg) {
|
|
1902
|
+
const table = childrenWithTag(frame, "table:table")[0];
|
|
1903
|
+
if (table !== void 0) return [readOdfTable(table, pkg)];
|
|
1904
|
+
const textBox = childrenWithTag(frame, "draw:text-box")[0];
|
|
1905
|
+
if (textBox !== void 0) return elementsWithTag(textBox.children, "text:p").map((p) => readOdfParagraph(p, pkg));
|
|
1906
|
+
const image = childrenWithTag(frame, "draw:image")[0];
|
|
1907
|
+
if (image !== void 0) {
|
|
1908
|
+
const block = readDrawImageBlock(image, frameBox, pkg);
|
|
1909
|
+
return block === void 0 ? [] : [block];
|
|
1910
|
+
}
|
|
1911
|
+
return [];
|
|
1912
|
+
}
|
|
1913
|
+
function readDrawFrame(frame, groupFunctions, pkg) {
|
|
1914
|
+
const ownGeometry = resolveOdfShapeGeometry(frame);
|
|
1915
|
+
if (ownGeometry === void 0) return;
|
|
1916
|
+
const geometry = composeOdfGroupTransform(groupFunctions, ownGeometry);
|
|
1917
|
+
return {
|
|
1918
|
+
name: attrValue(frame, "draw:name"),
|
|
1919
|
+
frame: geometry.frame,
|
|
1920
|
+
rotationDeg: geometry.rotationDeg,
|
|
1921
|
+
...readFrameInsets(frame, pkg),
|
|
1922
|
+
blocks: readDrawFrameContent(frame, geometry.frame, pkg)
|
|
1923
|
+
};
|
|
1924
|
+
}
|
|
1925
|
+
function readOwnTransformFunctions(element) {
|
|
1926
|
+
const value = attrValue(element, "draw:transform");
|
|
1927
|
+
return value === void 0 ? [] : parseOdfTransform(value);
|
|
1928
|
+
}
|
|
1929
|
+
function walkDrawShapes(children, groupFunctions, pkg, out) {
|
|
1930
|
+
for (const node of children) {
|
|
1931
|
+
if (node.type !== "element") continue;
|
|
1932
|
+
if (node.tag === "draw:frame") {
|
|
1933
|
+
const shape = readDrawFrame(node, groupFunctions, pkg);
|
|
1934
|
+
if (shape !== void 0) out.push(shape);
|
|
1935
|
+
} else if (node.tag === "draw:g") {
|
|
1936
|
+
const ownFunctions = readOwnTransformFunctions(node);
|
|
1937
|
+
const nested = ownFunctions.length === 0 ? groupFunctions : [...ownFunctions, ...groupFunctions];
|
|
1938
|
+
walkDrawShapes(node.children, nested, pkg, out);
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
//#endregion
|
|
1943
|
+
//#region src/typed/odp/read.ts
|
|
1944
|
+
const CONTENT_PART = "content.xml";
|
|
1945
|
+
const STYLES_PART = "styles.xml";
|
|
1946
|
+
const AUTOMATIC_STYLE_PARTS = [CONTENT_PART, STYLES_PART];
|
|
1947
|
+
function findMasterPageElement(pkg, masterPageName) {
|
|
1948
|
+
if (masterPageName === void 0) return;
|
|
1949
|
+
const stylesPart = pkg.parts[STYLES_PART];
|
|
1950
|
+
if (stylesPart?.kind !== "xml") return;
|
|
1951
|
+
const root = rootElement(stylesPart.nodes);
|
|
1952
|
+
const masterStyles = root === void 0 ? void 0 : findChildElement(root.children, "office:master-styles");
|
|
1953
|
+
if (masterStyles === void 0) return;
|
|
1954
|
+
return childrenWithTag(masterStyles, "style:master-page").find((element) => attrValue(element, "style:name") === masterPageName);
|
|
1955
|
+
}
|
|
1956
|
+
function findPageLayoutElement(pkg, pageLayoutName) {
|
|
1957
|
+
if (pageLayoutName === void 0) return;
|
|
1958
|
+
for (const partPath of AUTOMATIC_STYLE_PARTS) {
|
|
1959
|
+
const part = pkg.parts[partPath];
|
|
1960
|
+
if (part?.kind !== "xml") continue;
|
|
1961
|
+
const root = rootElement(part.nodes);
|
|
1962
|
+
const automaticStyles = root === void 0 ? void 0 : findChildElement(root.children, "office:automatic-styles");
|
|
1963
|
+
if (automaticStyles === void 0) continue;
|
|
1964
|
+
const found = childrenWithTag(automaticStyles, "style:page-layout").find((element) => attrValue(element, "style:name") === pageLayoutName);
|
|
1965
|
+
if (found !== void 0) return found;
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
function readSlideSize(page, pkg) {
|
|
1969
|
+
const masterPage = findMasterPageElement(pkg, attrValue(page, "draw:master-page-name"));
|
|
1970
|
+
const pageLayout = findPageLayoutElement(pkg, masterPage === void 0 ? void 0 : attrValue(masterPage, "style:page-layout-name"));
|
|
1971
|
+
const properties = pageLayout === void 0 ? void 0 : childrenWithTag(pageLayout, "style:page-layout-properties")[0];
|
|
1972
|
+
return (properties === void 0 ? void 0 : parsePageSize(properties)) ?? SLIDE_SIZE_WIDESCREEN;
|
|
1973
|
+
}
|
|
1974
|
+
function readSlideNotes(page) {
|
|
1975
|
+
const notes = childrenWithTag(page, "presentation:notes")[0];
|
|
1976
|
+
if (notes === void 0) return "";
|
|
1977
|
+
return elementsWithTag(notes.children, "text:p").map(decodeOdfText).join("\n");
|
|
1978
|
+
}
|
|
1979
|
+
function readSlide(page, pkg) {
|
|
1980
|
+
const shapes = [];
|
|
1981
|
+
walkDrawShapes(page.children, [], pkg, shapes);
|
|
1982
|
+
return {
|
|
1983
|
+
size: readSlideSize(page, pkg),
|
|
1984
|
+
shapes,
|
|
1985
|
+
notes: readSlideNotes(page)
|
|
1986
|
+
};
|
|
1987
|
+
}
|
|
1988
|
+
function readOdp(pkg) {
|
|
1989
|
+
const contentPart = pkg.parts[CONTENT_PART];
|
|
1990
|
+
const root = contentPart?.kind === "xml" ? rootElement(contentPart.nodes) : void 0;
|
|
1991
|
+
const body = root === void 0 ? void 0 : findChildElement(root.children, "office:body");
|
|
1992
|
+
const presentation = body === void 0 ? void 0 : findChildElement(body.children, "office:presentation");
|
|
1993
|
+
const pages = presentation === void 0 ? [] : childrenWithTag(presentation, "draw:page");
|
|
1994
|
+
return {
|
|
1995
|
+
metadata: readOdfMetadata(pkg),
|
|
1996
|
+
slides: pages.map((page) => readSlide(page, pkg))
|
|
1997
|
+
};
|
|
1998
|
+
}
|
|
1999
|
+
//#endregion
|
|
2000
|
+
export { AlignmentSchema, AttributeSchema, BinaryPartSchema, MANIFEST_PART, META_PART, MIMETYPE_PART, ManifestEntrySchema, ManifestProblemSchema, ManifestSchema, ODF_MEDIA_TYPES, ODF_NAMESPACES, PackageSchema, PartSchema, STYLE_FAMILIES, StylePropertiesSchema, StyleRegistry, TableCursor, XmlCdataSchema, XmlCommentSchema, XmlDeclarationSchema, XmlElementSchema, XmlNodeSchema, XmlPartSchema, XmlPiSchema, XmlTextSchema, applyOdfTransform, attrValue, base64ToBytes, buildManifest, buildStylePropertyElements, buildXml, bytesToBase64, canonicalPropertiesString, cellReference, childrenWithTag, columnIndexToLetters, composeOdfGroupTransform, decodeOdfText, decodePackage, decodeXmlText, el, elementsWithTag, encodePackage, encodeXmlText, ensureSpan, findChildElement, findStyleElement, formatOdfColor, formatOdfLength, formatPercentageMultiplier, formatPt, getOdfSpaceCount, isStyleFamily, isXmlNode, measureOdfNodeLength, mediaTypeForExtension, netRotationDeg, packageCodec, paragraphPropertiesToAttributes, parseBox, parseLength, parseMargins, parseOdfColor, parseOdfLength, parseOdfTransform, parsePackage, parsePageSize, parseParagraphProperties, parseStyleElementProperties, parseTextProperties, parseXml, readDrawFrame, readManifest, readMimetype, readOdfMetadata, readOdfParagraph, readOdfTable, readOdp, resolveOdfShapeGeometry, resolveStyle, resolveStyleElementChain, rootElement, serializePackage, setDocumentMediaType, sniffImageFormat, sumOdfNodeLength, syncManifest, textPropertiesToAttributes, txt, unzipPackage, validateManifest, walkDrawShapes, writeManifest, writeMimetype, xmlCodec, xmlnsAttributes, zipPackage };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "odf.js",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Type-safe, lossless round-trip conversion between OpenDocument Format packages (odt, ods, odp) and JSON, hand-written and dependency-minimal, built on Zod 4 codecs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|