reamkit 1.9.0 → 1.10.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 +8 -2
- package/dist/esm/core/converter/facade.js +2 -0
- package/dist/esm/core/converter/ream.js +1 -0
- package/dist/esm/pptx/placeholder-cascade.d.ts +9 -0
- package/dist/esm/pptx/placeholder-cascade.js +77 -0
- package/dist/esm/pptx/pptx-reader.d.ts +4 -0
- package/dist/esm/pptx/pptx-reader.js +213 -0
- package/dist/esm/pptx/slide-parser.d.ts +18 -0
- package/dist/esm/pptx/slide-parser.js +359 -0
- package/dist/esm/pptx/sp-helpers.d.ts +17 -0
- package/dist/esm/pptx/sp-helpers.js +60 -0
- package/dist/esm/word/drawing-parser.d.ts +4 -0
- package/dist/esm/word/drawing-parser.js +1 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ import { Ream } from 'reamkit';
|
|
|
30
30
|
// e.g. from an <input type="file"> or a fetch() — anything that yields bytes.
|
|
31
31
|
const bytes = new Uint8Array(await file.arrayBuffer());
|
|
32
32
|
|
|
33
|
-
const doc = Ream.parse(bytes); // docx, xlsx or pdf — sniffed
|
|
33
|
+
const doc = Ream.parse(bytes); // docx, xlsx, pptx or pdf — sniffed
|
|
34
34
|
const pdf = await doc.convert('pdf'); // async — fetches a font if needed
|
|
35
35
|
const svg = await doc.convert('svg'); // same parse, different target
|
|
36
36
|
const html = await doc.convert('html'); // flowed HTML — needs no fonts at all
|
|
@@ -130,7 +130,7 @@ rendering — inspect or analyze it without converting:
|
|
|
130
130
|
|
|
131
131
|
```ts
|
|
132
132
|
const doc = Ream.parse(bytes);
|
|
133
|
-
doc.format; // 'docx' | 'xlsx' | 'pdf'
|
|
133
|
+
doc.format; // 'docx' | 'xlsx' | 'pptx' | 'pdf'
|
|
134
134
|
doc.flow.body; // paragraphs / tables / images / charts …
|
|
135
135
|
doc.losses; // read-time losses
|
|
136
136
|
```
|
|
@@ -187,6 +187,12 @@ gradient vector shapes. It reads modern compressed files (cross-reference + obje
|
|
|
187
187
|
streams) and encrypted ones (RC4 / AES — the user password is passed to
|
|
188
188
|
`Ream.parse(bytes, { password })`, defaulting to the permissions-only case).
|
|
189
189
|
|
|
190
|
+
**Reads PowerPoint, too.** `Ream.parse` accepts a `.pptx` and turns each slide
|
|
191
|
+
into a page at the deck size — text boxes (with run formatting, alignment,
|
|
192
|
+
bullets and indents), layout/master placeholders, pictures, shapes, DrawingML
|
|
193
|
+
tables, embedded charts, theme colours, slide backgrounds, grouped shapes and
|
|
194
|
+
hyperlinks — then converts onward to PDF, SVG, HTML or DOCX like any source.
|
|
195
|
+
|
|
190
196
|
See [`CHANGELOG.md`](./CHANGELOG.md) for the release history; the docs
|
|
191
197
|
[**Scope**](https://reamkit.dev/guides/scope/) guide has the full feature matrix
|
|
192
198
|
and known limitations.
|
|
@@ -13,6 +13,7 @@ import { writeHtml } from "../../html/html-writer.js";
|
|
|
13
13
|
import { writeSvg } from "../../svg/svg-writer.js";
|
|
14
14
|
import { convertDocxToPdf } from "../../word/docx-to-pdf.js";
|
|
15
15
|
import { convertXlsxToPdf } from "../../excel/xlsx-to-pdf.js";
|
|
16
|
+
import { pptxReader } from "../../pptx/pptx-reader.js";
|
|
16
17
|
import { pdfReader } from "../../pdf-reader/reader.js";
|
|
17
18
|
//#region src/core/converter/facade.ts
|
|
18
19
|
function toFlowDoc(doc) {
|
|
@@ -28,6 +29,7 @@ function readToFlow(reader, bytes) {
|
|
|
28
29
|
var DEFAULT_READERS = [
|
|
29
30
|
docxReader,
|
|
30
31
|
xlsxReader,
|
|
32
|
+
pptxReader,
|
|
31
33
|
pdfReader
|
|
32
34
|
];
|
|
33
35
|
function createConverter(opts = {}) {
|
|
@@ -16,6 +16,7 @@ import { DEFAULT_READERS, resolveFontsViaChain, toFlowDoc } from "./facade.js";
|
|
|
16
16
|
var SOURCE_MIME = {
|
|
17
17
|
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
18
18
|
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
19
|
+
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
19
20
|
pdf: "application/pdf"
|
|
20
21
|
};
|
|
21
22
|
var Ream = class Ream {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { RunProperties } from '../core/document-model/index.js';
|
|
2
|
+
import { ColorResolver } from '../core/drawingml/colors.js';
|
|
3
|
+
import { PoNode } from '../core/po-helpers.js';
|
|
4
|
+
import { PlaceholderRef, ShapeBoxEmu } from './sp-helpers.js';
|
|
5
|
+
export interface PlaceholderCascade {
|
|
6
|
+
readonly geometryFor: (ph: PlaceholderRef) => ShapeBoxEmu | undefined;
|
|
7
|
+
readonly defaultsFor: (ph: PlaceholderRef, level: number) => RunProperties;
|
|
8
|
+
}
|
|
9
|
+
export declare function buildPlaceholderCascade(layoutTree: ReadonlyArray<PoNode>, masterTree: ReadonlyArray<PoNode> | undefined, colors: ColorResolver): PlaceholderCascade;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { poChildren, poIs } from "../core/po-helpers.js";
|
|
2
|
+
import { parsePh, parseXfrmBox, rPrToRunProps } from "./sp-helpers.js";
|
|
3
|
+
//#region src/pptx/placeholder-cascade.ts
|
|
4
|
+
function categoryOf(type) {
|
|
5
|
+
if (type === "title" || type === "ctrTitle") return "title";
|
|
6
|
+
if (type === void 0 || type === "body" || type === "subTitle" || type === "obj") return "body";
|
|
7
|
+
return "other";
|
|
8
|
+
}
|
|
9
|
+
function buildPlaceholderCascade(layoutTree, masterTree, colors) {
|
|
10
|
+
const layoutPhs = collectPlaceholders(layoutTree, "p:sldLayout");
|
|
11
|
+
const masterPhs = masterTree ? collectPlaceholders(masterTree, "p:sldMaster") : [];
|
|
12
|
+
const txStyles = masterTree ? collectTxStyles(masterTree, colors) : {
|
|
13
|
+
title: [],
|
|
14
|
+
body: [],
|
|
15
|
+
other: []
|
|
16
|
+
};
|
|
17
|
+
return {
|
|
18
|
+
geometryFor(ph) {
|
|
19
|
+
return matchPlaceholder(layoutPhs, ph)?.box ?? matchPlaceholder(masterPhs, ph)?.box;
|
|
20
|
+
},
|
|
21
|
+
defaultsFor(ph, level) {
|
|
22
|
+
const levels = txStyles[categoryOf(ph.type)];
|
|
23
|
+
if (levels.length === 0) return {};
|
|
24
|
+
return levels[Math.min(Math.max(level, 0), levels.length - 1)] ?? {};
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function collectPlaceholders(tree, root) {
|
|
29
|
+
const sld = tree.find((n) => poIs(n, root));
|
|
30
|
+
const cSld = sld ? poChildren(sld).find((c) => poIs(c, "p:cSld")) : void 0;
|
|
31
|
+
const spTree = cSld ? poChildren(cSld).find((c) => poIs(c, "p:spTree")) : void 0;
|
|
32
|
+
if (!spTree) return [];
|
|
33
|
+
const out = [];
|
|
34
|
+
for (const sp of poChildren(spTree)) {
|
|
35
|
+
if (!poIs(sp, "p:sp")) continue;
|
|
36
|
+
const ref = parsePh(sp);
|
|
37
|
+
if (!ref) continue;
|
|
38
|
+
const box = parseXfrmBox(poChildren(sp).find((c) => poIs(c, "p:spPr")));
|
|
39
|
+
out.push({
|
|
40
|
+
ref,
|
|
41
|
+
...box ? { box } : {}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
function matchPlaceholder(list, ph) {
|
|
47
|
+
if (ph.idx !== void 0) {
|
|
48
|
+
const byIdx = list.find((p) => p.ref.idx === ph.idx);
|
|
49
|
+
if (byIdx) return byIdx;
|
|
50
|
+
}
|
|
51
|
+
const byType = list.find((p) => p.ref.type === ph.type);
|
|
52
|
+
if (byType) return byType;
|
|
53
|
+
const cat = categoryOf(ph.type);
|
|
54
|
+
return list.find((p) => categoryOf(p.ref.type) === cat);
|
|
55
|
+
}
|
|
56
|
+
function collectTxStyles(masterTree, colors) {
|
|
57
|
+
const sld = masterTree.find((n) => poIs(n, "p:sldMaster"));
|
|
58
|
+
const txStyles = sld ? poChildren(sld).find((c) => poIs(c, "p:txStyles")) : void 0;
|
|
59
|
+
return {
|
|
60
|
+
title: levelStyles(txStyles, "p:titleStyle", colors),
|
|
61
|
+
body: levelStyles(txStyles, "p:bodyStyle", colors),
|
|
62
|
+
other: levelStyles(txStyles, "p:otherStyle", colors)
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function levelStyles(txStyles, tag, colors) {
|
|
66
|
+
const style = txStyles ? poChildren(txStyles).find((c) => poIs(c, tag)) : void 0;
|
|
67
|
+
if (!style) return [];
|
|
68
|
+
const out = [];
|
|
69
|
+
for (let lvl = 1; lvl <= 9; lvl++) {
|
|
70
|
+
const lvlPr = poChildren(style).find((c) => poIs(c, `a:lvl${lvl}pPr`));
|
|
71
|
+
const defRPr = lvlPr ? poChildren(lvlPr).find((c) => poIs(c, "a:defRPr")) : void 0;
|
|
72
|
+
out.push(rPrToRunProps(defRPr, colors));
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
//#endregion
|
|
77
|
+
export { buildPlaceholderCascade };
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { pt } from "../core/ir/units.js";
|
|
2
|
+
import { ResourceStore } from "../core/ir/resources.js";
|
|
3
|
+
import { FEATURES } from "../core/ir/features.js";
|
|
4
|
+
import { EMPTY_STYLE_SHEET, resolveBodyStyles } from "../core/style-cascade/resolver.js";
|
|
5
|
+
import "../core/style-cascade/index.js";
|
|
6
|
+
import { bytesInclude } from "../core/bytes.js";
|
|
7
|
+
import { poAttr, poChildren, poIntAttr, poIs } from "../core/po-helpers.js";
|
|
8
|
+
import { DEFAULT_THEME_PALETTE, defaultColorResolver, makeColorResolver } from "../core/drawingml/colors.js";
|
|
9
|
+
import { parseChart, withChartColorStyle } from "../core/drawingml/chart-parser.js";
|
|
10
|
+
import { parseTheme } from "../core/drawingml/theme-parser.js";
|
|
11
|
+
import { OpcPackage } from "../core/opc/package.js";
|
|
12
|
+
import "../core/opc/index.js";
|
|
13
|
+
import { buildPlaceholderCascade } from "./placeholder-cascade.js";
|
|
14
|
+
import { backdropElement, parseBackgroundFill, parseSlideShapes } from "./slide-parser.js";
|
|
15
|
+
import { XMLParser } from "fast-xml-parser";
|
|
16
|
+
//#region src/pptx/pptx-reader.ts
|
|
17
|
+
var EMU_PER_PT = 12700;
|
|
18
|
+
var DEFAULT_CX = 9144e3;
|
|
19
|
+
var DEFAULT_CY = 6858e3;
|
|
20
|
+
var decoder = new TextDecoder();
|
|
21
|
+
var parser = new XMLParser({
|
|
22
|
+
ignoreAttributes: false,
|
|
23
|
+
attributeNamePrefix: "@_",
|
|
24
|
+
preserveOrder: true,
|
|
25
|
+
parseAttributeValue: false,
|
|
26
|
+
parseTagValue: false,
|
|
27
|
+
trimValues: false
|
|
28
|
+
});
|
|
29
|
+
function readPptx(bytes) {
|
|
30
|
+
const losses = [];
|
|
31
|
+
const pkg = OpcPackage.open(bytes);
|
|
32
|
+
const presPath = pkg.getMainDocumentPath();
|
|
33
|
+
const presData = pkg.getPart(presPath);
|
|
34
|
+
const resources = new ResourceStore();
|
|
35
|
+
const charts = /* @__PURE__ */ new Map();
|
|
36
|
+
let cx = DEFAULT_CX;
|
|
37
|
+
let cy = DEFAULT_CY;
|
|
38
|
+
const slideParts = [];
|
|
39
|
+
if (presData) {
|
|
40
|
+
const pres = parser.parse(decoder.decode(presData)).find((n) => poIs(n, "p:presentation"));
|
|
41
|
+
const kids = pres ? poChildren(pres) : [];
|
|
42
|
+
const sldSz = kids.find((c) => poIs(c, "p:sldSz"));
|
|
43
|
+
cx = (sldSz ? poIntAttr(sldSz, "cx") : void 0) || DEFAULT_CX;
|
|
44
|
+
cy = (sldSz ? poIntAttr(sldSz, "cy") : void 0) || DEFAULT_CY;
|
|
45
|
+
const slideRelById = new Map(pkg.getPartRelationships(presPath).filter((r) => r.type.endsWith("/slide")).map((r) => [r.id, r]));
|
|
46
|
+
const lst = kids.find((c) => poIs(c, "p:sldIdLst"));
|
|
47
|
+
const ids = lst ? poChildren(lst).filter((c) => poIs(c, "p:sldId")) : [];
|
|
48
|
+
for (const sldId of ids) {
|
|
49
|
+
const rid = poAttr(sldId, "r:id");
|
|
50
|
+
const rel = rid !== void 0 ? slideRelById.get(rid) : void 0;
|
|
51
|
+
const part = rel ? pkg.resolveRelatedPart(presPath, rel) : void 0;
|
|
52
|
+
if (part) slideParts.push(part);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (slideParts.length === 0) losses.push({
|
|
56
|
+
severity: "dropped",
|
|
57
|
+
feature: FEATURES.text,
|
|
58
|
+
detail: "presentation has no slides"
|
|
59
|
+
});
|
|
60
|
+
const slideCount = Math.max(1, slideParts.length);
|
|
61
|
+
const pageW = pt(cx / EMU_PER_PT);
|
|
62
|
+
const pageH = pt(cy / EMU_PER_PT);
|
|
63
|
+
const stylesByLayout = /* @__PURE__ */ new Map();
|
|
64
|
+
const body = [];
|
|
65
|
+
for (let i = 0; i < slideCount; i++) {
|
|
66
|
+
body.push({
|
|
67
|
+
kind: "paragraph",
|
|
68
|
+
paragraph: {
|
|
69
|
+
properties: i > 0 ? { pageBreakBefore: true } : {},
|
|
70
|
+
runs: [{
|
|
71
|
+
text: "",
|
|
72
|
+
properties: {}
|
|
73
|
+
}]
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
const part = slideParts[i];
|
|
77
|
+
if (part) {
|
|
78
|
+
const styles = slideStylesFor(pkg, part.path, stylesByLayout);
|
|
79
|
+
const ctx = {
|
|
80
|
+
...styles.cascade ? { cascade: styles.cascade } : {},
|
|
81
|
+
colors: styles.colors,
|
|
82
|
+
resolveImage: makeSlideImageResolver(pkg, part.path, resources),
|
|
83
|
+
resolveChart: makeSlideChartResolver(pkg, part.path, charts, styles.colors),
|
|
84
|
+
resolveHyperlink: makeHyperlinkResolver(pkg, part.path)
|
|
85
|
+
};
|
|
86
|
+
body.push(...parseSlide(part.data, ctx, styles.background, pageW, pageH));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const section = {
|
|
90
|
+
pageSize: {
|
|
91
|
+
width: pageW,
|
|
92
|
+
height: pageH
|
|
93
|
+
},
|
|
94
|
+
margins: {
|
|
95
|
+
top: pt(0),
|
|
96
|
+
right: pt(0),
|
|
97
|
+
bottom: pt(0),
|
|
98
|
+
left: pt(0)
|
|
99
|
+
},
|
|
100
|
+
headers: [],
|
|
101
|
+
footers: []
|
|
102
|
+
};
|
|
103
|
+
return {
|
|
104
|
+
doc: {
|
|
105
|
+
kind: "flow",
|
|
106
|
+
body: resolveBodyStyles([...body], EMPTY_STYLE_SHEET),
|
|
107
|
+
sections: [],
|
|
108
|
+
section,
|
|
109
|
+
styles: EMPTY_STYLE_SHEET,
|
|
110
|
+
resources,
|
|
111
|
+
...charts.size > 0 ? { charts } : {}
|
|
112
|
+
},
|
|
113
|
+
losses
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function parseXml(data) {
|
|
117
|
+
return parser.parse(decoder.decode(data));
|
|
118
|
+
}
|
|
119
|
+
function parseSlide(data, ctx, inheritedBg, pageW, pageH) {
|
|
120
|
+
const sld = parseXml(data).find((n) => poIs(n, "p:sld"));
|
|
121
|
+
const cSld = sld ? poChildren(sld).find((c) => poIs(c, "p:cSld")) : void 0;
|
|
122
|
+
const colors = ctx.colors ?? defaultColorResolver;
|
|
123
|
+
const bgNode = cSld ? poChildren(cSld).find((c) => poIs(c, "p:bg")) : void 0;
|
|
124
|
+
const bg = (bgNode ? parseBackgroundFill(bgNode, colors) : void 0) ?? inheritedBg;
|
|
125
|
+
const spTree = cSld ? poChildren(cSld).find((c) => poIs(c, "p:spTree")) : void 0;
|
|
126
|
+
const out = [];
|
|
127
|
+
if (bg) out.push(backdropElement(bg, pageW, pageH));
|
|
128
|
+
if (spTree) out.push(...parseSlideShapes(spTree, ctx));
|
|
129
|
+
return out;
|
|
130
|
+
}
|
|
131
|
+
function partBackground(tree, root, colors) {
|
|
132
|
+
const sld = tree.find((n) => poIs(n, root));
|
|
133
|
+
const cSld = sld ? poChildren(sld).find((c) => poIs(c, "p:cSld")) : void 0;
|
|
134
|
+
const bg = cSld ? poChildren(cSld).find((c) => poIs(c, "p:bg")) : void 0;
|
|
135
|
+
return bg ? parseBackgroundFill(bg, colors) : void 0;
|
|
136
|
+
}
|
|
137
|
+
function makeSlideImageResolver(pkg, slidePath, resources) {
|
|
138
|
+
const cache = /* @__PURE__ */ new Map();
|
|
139
|
+
return (relId) => {
|
|
140
|
+
if (cache.has(relId)) return cache.get(relId);
|
|
141
|
+
const rel = pkg.getPartRelationships(slidePath).find((r) => r.id === relId);
|
|
142
|
+
const resolved = rel ? pkg.resolveRelatedPart(slidePath, rel) : void 0;
|
|
143
|
+
const id = resolved ? resources.put(resolved.data) : void 0;
|
|
144
|
+
cache.set(relId, id);
|
|
145
|
+
return id;
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function makeHyperlinkResolver(pkg, slidePath) {
|
|
149
|
+
return (relId) => {
|
|
150
|
+
const rel = pkg.getPartRelationships(slidePath).find((r) => r.id === relId);
|
|
151
|
+
return rel && rel.targetMode === "External" ? rel.target : void 0;
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function makeSlideChartResolver(pkg, slidePath, charts, colors) {
|
|
155
|
+
const cache = /* @__PURE__ */ new Map();
|
|
156
|
+
return (relId) => {
|
|
157
|
+
if (cache.has(relId)) return cache.get(relId);
|
|
158
|
+
const rel = pkg.getPartRelationships(slidePath).find((r) => r.id === relId);
|
|
159
|
+
const resolved = rel ? pkg.resolveRelatedPart(slidePath, rel) : void 0;
|
|
160
|
+
const chart = resolved ? parseChart(resolved.data, colors) : null;
|
|
161
|
+
let key;
|
|
162
|
+
if (chart && resolved) {
|
|
163
|
+
key = `${slidePath}!${relId}`;
|
|
164
|
+
charts.set(key, withChartColorStyle(chart, pkg, resolved.path, colors));
|
|
165
|
+
}
|
|
166
|
+
cache.set(relId, key);
|
|
167
|
+
return key;
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function slideStylesFor(pkg, slidePath, cache) {
|
|
171
|
+
const layoutRel = pkg.getPartRelationships(slidePath).find((r) => r.type.endsWith("/slideLayout"));
|
|
172
|
+
const layout = layoutRel ? pkg.resolveRelatedPart(slidePath, layoutRel) : void 0;
|
|
173
|
+
if (!layout) return { colors: defaultColorResolver };
|
|
174
|
+
const cached = cache.get(layout.path);
|
|
175
|
+
if (cached) return cached;
|
|
176
|
+
const masterRel = pkg.getPartRelationships(layout.path).find((r) => r.type.endsWith("/slideMaster"));
|
|
177
|
+
const master = masterRel ? pkg.resolveRelatedPart(layout.path, masterRel) : void 0;
|
|
178
|
+
const colors = master ? deckColorResolver(pkg, master.path) : defaultColorResolver;
|
|
179
|
+
const layoutTree = parseXml(layout.data);
|
|
180
|
+
const masterTree = master ? parseXml(master.data) : void 0;
|
|
181
|
+
const cascade = buildPlaceholderCascade(layoutTree, masterTree, colors);
|
|
182
|
+
const background = partBackground(layoutTree, "p:sldLayout", colors) ?? (masterTree ? partBackground(masterTree, "p:sldMaster", colors) : void 0);
|
|
183
|
+
const styles = {
|
|
184
|
+
cascade,
|
|
185
|
+
colors,
|
|
186
|
+
...background ? { background } : {}
|
|
187
|
+
};
|
|
188
|
+
cache.set(layout.path, styles);
|
|
189
|
+
return styles;
|
|
190
|
+
}
|
|
191
|
+
function deckColorResolver(pkg, masterPath) {
|
|
192
|
+
const themeRel = pkg.getPartRelationships(masterPath).find((r) => r.type.endsWith("/theme"));
|
|
193
|
+
const theme = themeRel ? pkg.resolveRelatedPart(masterPath, themeRel) : void 0;
|
|
194
|
+
if (!theme) return defaultColorResolver;
|
|
195
|
+
const palette = new Map(DEFAULT_THEME_PALETTE);
|
|
196
|
+
for (const [slot, hex] of parseTheme(theme.data)) palette.set(slot, hex);
|
|
197
|
+
return makeColorResolver(palette);
|
|
198
|
+
}
|
|
199
|
+
var pptxReader = {
|
|
200
|
+
id: "pptx",
|
|
201
|
+
produces: "flow",
|
|
202
|
+
supports: new Set([
|
|
203
|
+
FEATURES.text,
|
|
204
|
+
FEATURES.images,
|
|
205
|
+
FEATURES.shapes,
|
|
206
|
+
FEATURES.charts,
|
|
207
|
+
FEATURES.tables
|
|
208
|
+
]),
|
|
209
|
+
sniff: (bytes) => bytes[0] === 80 && bytes[1] === 75 && bytesInclude(bytes, "ppt/presentation.xml"),
|
|
210
|
+
read: (bytes) => readPptx(bytes)
|
|
211
|
+
};
|
|
212
|
+
//#endregion
|
|
213
|
+
export { pptxReader };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { BodyElement, ShapeFill } from '../core/document-model/index.js';
|
|
2
|
+
import { ColorResolver } from '../core/drawingml/colors.js';
|
|
3
|
+
import { Pt, ResourceId } from '../core/ir/index.js';
|
|
4
|
+
import { PoNode } from '../core/po-helpers.js';
|
|
5
|
+
import { PlaceholderCascade } from './placeholder-cascade.js';
|
|
6
|
+
import { ShapeBoxEmu } from './sp-helpers.js';
|
|
7
|
+
export interface SlideContext {
|
|
8
|
+
readonly cascade?: PlaceholderCascade;
|
|
9
|
+
readonly resolveImage?: (relId: string) => ResourceId | undefined;
|
|
10
|
+
readonly resolveChart?: (relId: string) => string | undefined;
|
|
11
|
+
readonly colors?: ColorResolver;
|
|
12
|
+
readonly resolveHyperlink?: (relId: string) => string | undefined;
|
|
13
|
+
}
|
|
14
|
+
type GroupTransform = (box: ShapeBoxEmu) => ShapeBoxEmu;
|
|
15
|
+
export declare function parseSlideShapes(container: PoNode, ctx?: SlideContext, transform?: GroupTransform): Array<BodyElement>;
|
|
16
|
+
export declare function parseBackgroundFill(bg: PoNode, colors: ColorResolver): ShapeFill | undefined;
|
|
17
|
+
export declare function backdropElement(fill: ShapeFill, widthPt: Pt, heightPt: Pt): BodyElement;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import { emuToPt } from "../core/ir/units.js";
|
|
2
|
+
import { poAttr, poChildren, poFindDescendant, poIntAttr, poIs, poText } from "../core/po-helpers.js";
|
|
3
|
+
import { defaultColorResolver, resolveColorNode } from "../core/drawingml/colors.js";
|
|
4
|
+
import { parseCustGeom, parseFill, parseLine, parsePrstGeom } from "../word/drawing-parser.js";
|
|
5
|
+
import { boxFromXfrm, parsePh, parseXfrmBox, rPrToRunProps } from "./sp-helpers.js";
|
|
6
|
+
//#region src/pptx/slide-parser.ts
|
|
7
|
+
var CHART_URI = "http://schemas.openxmlformats.org/drawingml/2006/chart";
|
|
8
|
+
var TABLE_URI = "http://schemas.openxmlformats.org/drawingml/2006/table";
|
|
9
|
+
var ALGN_TO_ALIGNMENT = {
|
|
10
|
+
l: "left",
|
|
11
|
+
ctr: "center",
|
|
12
|
+
r: "right",
|
|
13
|
+
just: "both",
|
|
14
|
+
dist: "distribute"
|
|
15
|
+
};
|
|
16
|
+
var RECT_GEOMETRY = {
|
|
17
|
+
kind: "preset",
|
|
18
|
+
preset: "rect",
|
|
19
|
+
adjust: /* @__PURE__ */ new Map()
|
|
20
|
+
};
|
|
21
|
+
var IDENTITY_TRANSFORM = (box) => box;
|
|
22
|
+
function parseSlideShapes(container, ctx = {}, transform = IDENTITY_TRANSFORM) {
|
|
23
|
+
const out = [];
|
|
24
|
+
for (const child of poChildren(container)) if (poIs(child, "p:sp")) {
|
|
25
|
+
const shape = parseSp(child, ctx, transform);
|
|
26
|
+
if (shape) out.push({
|
|
27
|
+
kind: "shape",
|
|
28
|
+
shape
|
|
29
|
+
});
|
|
30
|
+
} else if (poIs(child, "p:pic")) {
|
|
31
|
+
const image = parsePic(child, ctx, transform);
|
|
32
|
+
if (image) out.push({
|
|
33
|
+
kind: "image",
|
|
34
|
+
image
|
|
35
|
+
});
|
|
36
|
+
} else if (poIs(child, "p:graphicFrame")) {
|
|
37
|
+
const el = parseGraphicFrame(child, ctx, transform);
|
|
38
|
+
if (el) out.push(el);
|
|
39
|
+
} else if (poIs(child, "p:grpSp")) out.push(...parseSlideShapes(child, ctx, composeGroupTransform(child, transform)));
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
function composeGroupTransform(grpSp, parent) {
|
|
43
|
+
const grpSpPr = poChildren(grpSp).find((c) => poIs(c, "p:grpSpPr"));
|
|
44
|
+
const xfrm = grpSpPr ? poChildren(grpSpPr).find((c) => poIs(c, "a:xfrm")) : void 0;
|
|
45
|
+
if (!xfrm) return parent;
|
|
46
|
+
const off = poChildren(xfrm).find((c) => poIs(c, "a:off"));
|
|
47
|
+
const ext = poChildren(xfrm).find((c) => poIs(c, "a:ext"));
|
|
48
|
+
const chOff = poChildren(xfrm).find((c) => poIs(c, "a:chOff"));
|
|
49
|
+
const chExt = poChildren(xfrm).find((c) => poIs(c, "a:chExt"));
|
|
50
|
+
const extCx = ext ? poIntAttr(ext, "cx") : void 0;
|
|
51
|
+
const extCy = ext ? poIntAttr(ext, "cy") : void 0;
|
|
52
|
+
const chExtCx = chExt ? poIntAttr(chExt, "cx") : void 0;
|
|
53
|
+
const chExtCy = chExt ? poIntAttr(chExt, "cy") : void 0;
|
|
54
|
+
if (!extCx || !extCy || !chExtCx || !chExtCy) return parent;
|
|
55
|
+
const offX = (off ? poIntAttr(off, "x") : void 0) ?? 0;
|
|
56
|
+
const offY = (off ? poIntAttr(off, "y") : void 0) ?? 0;
|
|
57
|
+
const chOffX = (chOff ? poIntAttr(chOff, "x") : void 0) ?? 0;
|
|
58
|
+
const chOffY = (chOff ? poIntAttr(chOff, "y") : void 0) ?? 0;
|
|
59
|
+
const sx = extCx / chExtCx;
|
|
60
|
+
const sy = extCy / chExtCy;
|
|
61
|
+
return (box) => parent({
|
|
62
|
+
x: offX + (box.x - chOffX) * sx,
|
|
63
|
+
y: offY + (box.y - chOffY) * sy,
|
|
64
|
+
cx: box.cx * sx,
|
|
65
|
+
cy: box.cy * sy
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function floatAt(box) {
|
|
69
|
+
return {
|
|
70
|
+
wrap: "none",
|
|
71
|
+
posH: {
|
|
72
|
+
relativeFrom: "page",
|
|
73
|
+
offsetPt: emuToPt(box.x)
|
|
74
|
+
},
|
|
75
|
+
posV: {
|
|
76
|
+
relativeFrom: "page",
|
|
77
|
+
offsetPt: emuToPt(box.y)
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function parseSp(sp, ctx, transform) {
|
|
82
|
+
const ph = parsePh(sp);
|
|
83
|
+
const colors = ctx.colors ?? defaultColorResolver;
|
|
84
|
+
const spPr = poChildren(sp).find((c) => poIs(c, "p:spPr"));
|
|
85
|
+
let own = parseXfrmBox(spPr);
|
|
86
|
+
if (!own && ph && ctx.cascade) own = ctx.cascade.geometryFor(ph);
|
|
87
|
+
if (!own) return void 0;
|
|
88
|
+
const box = transform(own);
|
|
89
|
+
const txBody = poChildren(sp).find((c) => poIs(c, "p:txBody"));
|
|
90
|
+
const text = txBody ? parseTxBody(txBody, ph, ctx.cascade, colors, ctx.resolveHyperlink) : void 0;
|
|
91
|
+
const geometry = parseGeometry(spPr);
|
|
92
|
+
const fill = spPr ? parseFill(spPr, colors) : { kind: "none" };
|
|
93
|
+
const line = spPr ? parseLine(spPr, colors) : void 0;
|
|
94
|
+
const visibleLine = line !== void 0 && line.fill !== "none";
|
|
95
|
+
if (!text && fill.kind === "none" && !visibleLine) return void 0;
|
|
96
|
+
return {
|
|
97
|
+
float: floatAt(box),
|
|
98
|
+
width: emuToPt(box.cx),
|
|
99
|
+
height: emuToPt(box.cy),
|
|
100
|
+
geometry,
|
|
101
|
+
fill,
|
|
102
|
+
...line ? { line } : {},
|
|
103
|
+
...text ? { text } : {},
|
|
104
|
+
paragraphProperties: {}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function parseGraphicFrame(gf, ctx, transform) {
|
|
108
|
+
const own = boxFromXfrm(poChildren(gf).find((c) => poIs(c, "p:xfrm")));
|
|
109
|
+
if (!own) return void 0;
|
|
110
|
+
const box = transform(own);
|
|
111
|
+
const graphicData = poFindDescendant(gf, "a:graphicData");
|
|
112
|
+
const uri = graphicData ? poAttr(graphicData, "uri") : void 0;
|
|
113
|
+
if (uri === CHART_URI) {
|
|
114
|
+
const cChart = poFindDescendant(gf, "c:chart");
|
|
115
|
+
const relId = cChart ? poAttr(cChart, "id") : void 0;
|
|
116
|
+
const key = relId !== void 0 ? ctx.resolveChart?.(relId) : void 0;
|
|
117
|
+
if (key === void 0) return void 0;
|
|
118
|
+
return {
|
|
119
|
+
kind: "chart",
|
|
120
|
+
chart: {
|
|
121
|
+
float: floatAt(box),
|
|
122
|
+
chartRelId: key,
|
|
123
|
+
width: emuToPt(box.cx),
|
|
124
|
+
height: emuToPt(box.cy),
|
|
125
|
+
paragraphProperties: {}
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
if (uri === TABLE_URI) {
|
|
130
|
+
const tbl = poFindDescendant(gf, "a:tbl");
|
|
131
|
+
return tbl ? {
|
|
132
|
+
kind: "table",
|
|
133
|
+
table: parseTable(tbl, ctx.colors ?? defaultColorResolver)
|
|
134
|
+
} : void 0;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function parseBackgroundFill(bg, colors) {
|
|
138
|
+
const bgPr = poChildren(bg).find((c) => poIs(c, "p:bgPr"));
|
|
139
|
+
if (bgPr) {
|
|
140
|
+
const fill = parseFill(bgPr, colors);
|
|
141
|
+
return fill.kind !== "none" ? fill : void 0;
|
|
142
|
+
}
|
|
143
|
+
const bgRef = poChildren(bg).find((c) => poIs(c, "p:bgRef"));
|
|
144
|
+
if (bgRef) for (const c of poChildren(bgRef)) {
|
|
145
|
+
const hex = resolveColorNode(c, colors);
|
|
146
|
+
if (hex) return {
|
|
147
|
+
kind: "solid",
|
|
148
|
+
colorHex: hex
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function backdropElement(fill, widthPt, heightPt) {
|
|
153
|
+
return {
|
|
154
|
+
kind: "shape",
|
|
155
|
+
shape: {
|
|
156
|
+
float: {
|
|
157
|
+
wrap: "none",
|
|
158
|
+
behind: true,
|
|
159
|
+
posH: {
|
|
160
|
+
relativeFrom: "page",
|
|
161
|
+
offsetPt: emuToPt(0)
|
|
162
|
+
},
|
|
163
|
+
posV: {
|
|
164
|
+
relativeFrom: "page",
|
|
165
|
+
offsetPt: emuToPt(0)
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
width: widthPt,
|
|
169
|
+
height: heightPt,
|
|
170
|
+
geometry: RECT_GEOMETRY,
|
|
171
|
+
fill,
|
|
172
|
+
paragraphProperties: {}
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function parseGeometry(spPr) {
|
|
177
|
+
if (!spPr) return RECT_GEOMETRY;
|
|
178
|
+
const prst = poChildren(spPr).find((c) => poIs(c, "a:prstGeom"));
|
|
179
|
+
if (prst) return parsePrstGeom(prst);
|
|
180
|
+
const cust = poChildren(spPr).find((c) => poIs(c, "a:custGeom"));
|
|
181
|
+
if (cust) return parseCustGeom(cust);
|
|
182
|
+
return RECT_GEOMETRY;
|
|
183
|
+
}
|
|
184
|
+
function parsePic(pic, ctx, transform) {
|
|
185
|
+
const own = parseXfrmBox(poChildren(pic).find((c) => poIs(c, "p:spPr")));
|
|
186
|
+
if (!own) return void 0;
|
|
187
|
+
const box = transform(own);
|
|
188
|
+
const blipFill = poChildren(pic).find((c) => poIs(c, "p:blipFill"));
|
|
189
|
+
const blip = blipFill ? poChildren(blipFill).find((c) => poIs(c, "a:blip")) : void 0;
|
|
190
|
+
const relId = blip ? poAttr(blip, "embed") : void 0;
|
|
191
|
+
const resource = relId !== void 0 ? ctx.resolveImage?.(relId) : void 0;
|
|
192
|
+
const altText = picAltText(pic);
|
|
193
|
+
return {
|
|
194
|
+
float: floatAt(box),
|
|
195
|
+
...resource !== void 0 ? { resource } : {},
|
|
196
|
+
width: emuToPt(box.cx),
|
|
197
|
+
height: emuToPt(box.cy),
|
|
198
|
+
paragraphProperties: {},
|
|
199
|
+
...altText ? { altText } : {}
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function picAltText(pic) {
|
|
203
|
+
const nvPicPr = poChildren(pic).find((c) => poIs(c, "p:nvPicPr"));
|
|
204
|
+
const cNvPr = nvPicPr ? poChildren(nvPicPr).find((c) => poIs(c, "p:cNvPr")) : void 0;
|
|
205
|
+
const descr = cNvPr ? poAttr(cNvPr, "descr") : void 0;
|
|
206
|
+
const title = cNvPr ? poAttr(cNvPr, "title") : void 0;
|
|
207
|
+
return (descr ?? title)?.trim() || void 0;
|
|
208
|
+
}
|
|
209
|
+
function txBodyParagraphs(txBody, ph, cascade, colors, resolveLink) {
|
|
210
|
+
const content = [];
|
|
211
|
+
const counters = [];
|
|
212
|
+
for (const child of poChildren(txBody)) {
|
|
213
|
+
if (!poIs(child, "a:p")) continue;
|
|
214
|
+
content.push({
|
|
215
|
+
kind: "paragraph",
|
|
216
|
+
paragraph: parseSlideParagraph(child, ph, cascade, colors, resolveLink, counters)
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
return content;
|
|
220
|
+
}
|
|
221
|
+
function parseTxBody(txBody, ph, cascade, colors, resolveLink) {
|
|
222
|
+
const content = txBodyParagraphs(txBody, ph, cascade, colors, resolveLink);
|
|
223
|
+
if (content.length === 0) return void 0;
|
|
224
|
+
const bodyPr = poChildren(txBody).find((c) => poIs(c, "a:bodyPr"));
|
|
225
|
+
const lIns = bodyPr ? poIntAttr(bodyPr, "lIns") : void 0;
|
|
226
|
+
const tIns = bodyPr ? poIntAttr(bodyPr, "tIns") : void 0;
|
|
227
|
+
const rIns = bodyPr ? poIntAttr(bodyPr, "rIns") : void 0;
|
|
228
|
+
const bIns = bodyPr ? poIntAttr(bodyPr, "bIns") : void 0;
|
|
229
|
+
const a = bodyPr ? poAttr(bodyPr, "anchor") : void 0;
|
|
230
|
+
const anchor = a === "ctr" ? "ctr" : a === "b" ? "b" : a === "t" ? "t" : void 0;
|
|
231
|
+
return {
|
|
232
|
+
content,
|
|
233
|
+
...lIns !== void 0 ? { insetLeft: emuToPt(lIns) } : {},
|
|
234
|
+
...tIns !== void 0 ? { insetTop: emuToPt(tIns) } : {},
|
|
235
|
+
...rIns !== void 0 ? { insetRight: emuToPt(rIns) } : {},
|
|
236
|
+
...bIns !== void 0 ? { insetBottom: emuToPt(bIns) } : {},
|
|
237
|
+
...anchor ? { anchor } : {}
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
function parseSlideParagraph(aP, ph, cascade, colors, resolveLink, counters) {
|
|
241
|
+
const pPr = poChildren(aP).find((c) => poIs(c, "a:pPr"));
|
|
242
|
+
const level = (pPr ? poIntAttr(pPr, "lvl") : void 0) ?? 0;
|
|
243
|
+
const defaults = ph && cascade ? cascade.defaultsFor(ph, level) : {};
|
|
244
|
+
const algn = pPr ? poAttr(pPr, "algn") : void 0;
|
|
245
|
+
const alignment = algn !== void 0 ? ALGN_TO_ALIGNMENT[algn] : void 0;
|
|
246
|
+
const runs = [];
|
|
247
|
+
const marker = bulletMarker(pPr, level, counters);
|
|
248
|
+
if (marker !== void 0) runs.push({
|
|
249
|
+
text: marker,
|
|
250
|
+
properties: defaults,
|
|
251
|
+
listMarker: true
|
|
252
|
+
});
|
|
253
|
+
for (const child of poChildren(aP)) if (poIs(child, "a:r") || poIs(child, "a:fld")) {
|
|
254
|
+
const run = parseSlideRun(child, defaults, colors, resolveLink);
|
|
255
|
+
if (run) runs.push(run);
|
|
256
|
+
}
|
|
257
|
+
const marL = pPr ? poIntAttr(pPr, "marL") : void 0;
|
|
258
|
+
const indent = pPr ? poIntAttr(pPr, "indent") : void 0;
|
|
259
|
+
const indentLeft = marL !== void 0 ? emuToPt(marL) : level > 0 ? emuToPt(level * 457200) : void 0;
|
|
260
|
+
return {
|
|
261
|
+
properties: {
|
|
262
|
+
...alignment ? { alignment } : {},
|
|
263
|
+
...indentLeft !== void 0 ? { indentLeft } : {},
|
|
264
|
+
...indent !== void 0 ? { indentFirstLine: emuToPt(indent) } : {}
|
|
265
|
+
},
|
|
266
|
+
runs
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
function bulletMarker(pPr, level, counters) {
|
|
270
|
+
if (!pPr) return void 0;
|
|
271
|
+
if (poChildren(pPr).some((c) => poIs(c, "a:buNone"))) return void 0;
|
|
272
|
+
const buChar = poChildren(pPr).find((c) => poIs(c, "a:buChar"));
|
|
273
|
+
if (buChar) return `${poAttr(buChar, "char") ?? "•"} `;
|
|
274
|
+
const buAuto = poChildren(pPr).find((c) => poIs(c, "a:buAutoNum"));
|
|
275
|
+
if (!buAuto) return void 0;
|
|
276
|
+
const type = poAttr(buAuto, "type") ?? "arabicPeriod";
|
|
277
|
+
const startAt = poIntAttr(buAuto, "startAt") ?? 1;
|
|
278
|
+
const prev = counters[level];
|
|
279
|
+
const n = (prev === void 0 ? startAt - 1 : prev) + 1;
|
|
280
|
+
counters[level] = n;
|
|
281
|
+
counters.length = level + 1;
|
|
282
|
+
return `${n}${autoNumSuffix(type)} `;
|
|
283
|
+
}
|
|
284
|
+
function autoNumSuffix(type) {
|
|
285
|
+
if (type.endsWith("ParenR") || type.endsWith("ParenBoth")) return ")";
|
|
286
|
+
if (type.endsWith("Period")) return ".";
|
|
287
|
+
return "";
|
|
288
|
+
}
|
|
289
|
+
function parseSlideRun(node, defaults, colors, resolveLink) {
|
|
290
|
+
const t = poChildren(node).find((c) => poIs(c, "a:t"));
|
|
291
|
+
const text = t ? poText(t) : "";
|
|
292
|
+
if (text.length === 0) return void 0;
|
|
293
|
+
const rPr = poChildren(node).find((c) => poIs(c, "a:rPr"));
|
|
294
|
+
const hlink = rPr ? poChildren(rPr).find((c) => poIs(c, "a:hlinkClick")) : void 0;
|
|
295
|
+
const linkId = hlink ? poAttr(hlink, "id") : void 0;
|
|
296
|
+
const href = linkId !== void 0 ? resolveLink?.(linkId) : void 0;
|
|
297
|
+
return {
|
|
298
|
+
text,
|
|
299
|
+
properties: {
|
|
300
|
+
...defaults,
|
|
301
|
+
...rPrToRunProps(rPr, colors)
|
|
302
|
+
},
|
|
303
|
+
...href ? { href } : {}
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
function parseTable(tbl, colors) {
|
|
307
|
+
const grid = [];
|
|
308
|
+
const tblGrid = poChildren(tbl).find((c) => poIs(c, "a:tblGrid"));
|
|
309
|
+
if (tblGrid) {
|
|
310
|
+
for (const col of poChildren(tblGrid)) if (poIs(col, "a:gridCol")) grid.push(emuToPt(poIntAttr(col, "w") ?? 0));
|
|
311
|
+
}
|
|
312
|
+
const rows = [];
|
|
313
|
+
for (const tr of poChildren(tbl)) if (poIs(tr, "a:tr")) rows.push(parseTableRow(tr, colors));
|
|
314
|
+
return {
|
|
315
|
+
properties: {},
|
|
316
|
+
grid,
|
|
317
|
+
rows
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
function parseTableRow(tr, colors) {
|
|
321
|
+
const h = poIntAttr(tr, "h");
|
|
322
|
+
const cells = [];
|
|
323
|
+
for (const tc of poChildren(tr)) {
|
|
324
|
+
if (!poIs(tc, "a:tc")) continue;
|
|
325
|
+
if (poAttr(tc, "hMerge") === "1") continue;
|
|
326
|
+
cells.push(parseTableCell(tc, colors));
|
|
327
|
+
}
|
|
328
|
+
return {
|
|
329
|
+
properties: { ...h !== void 0 ? { height: emuToPt(h) } : {} },
|
|
330
|
+
cells
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
function parseTableCell(tc, colors) {
|
|
334
|
+
const txBody = poChildren(tc).find((c) => poIs(c, "a:txBody"));
|
|
335
|
+
const content = txBody ? txBodyParagraphs(txBody, void 0, void 0, colors, void 0) : [];
|
|
336
|
+
const gridSpan = poIntAttr(tc, "gridSpan");
|
|
337
|
+
const rowSpan = poIntAttr(tc, "rowSpan");
|
|
338
|
+
const merge = rowSpan !== void 0 && rowSpan > 1 ? "start" : poAttr(tc, "vMerge") === "1" ? "middle" : void 0;
|
|
339
|
+
const tcPr = poChildren(tc).find((c) => poIs(c, "a:tcPr"));
|
|
340
|
+
const shadingHex = tcPr ? cellFillHex(tcPr, colors) : void 0;
|
|
341
|
+
return {
|
|
342
|
+
properties: {
|
|
343
|
+
...gridSpan !== void 0 && gridSpan > 1 ? { colSpan: gridSpan } : {},
|
|
344
|
+
...merge ? { merge } : {},
|
|
345
|
+
...shadingHex ? { shading: { colorHex: shadingHex } } : {}
|
|
346
|
+
},
|
|
347
|
+
content
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
function cellFillHex(tcPr, colors) {
|
|
351
|
+
const solidFill = poChildren(tcPr).find((c) => poIs(c, "a:solidFill"));
|
|
352
|
+
if (!solidFill) return void 0;
|
|
353
|
+
for (const c of poChildren(solidFill)) {
|
|
354
|
+
const hex = resolveColorNode(c, colors);
|
|
355
|
+
if (hex) return hex;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
//#endregion
|
|
359
|
+
export { backdropElement, parseBackgroundFill, parseSlideShapes };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { RunProperties } from '../core/document-model/index.js';
|
|
2
|
+
import { ColorResolver } from '../core/drawingml/colors.js';
|
|
3
|
+
import { PoNode } from '../core/po-helpers.js';
|
|
4
|
+
export interface PlaceholderRef {
|
|
5
|
+
readonly type?: string;
|
|
6
|
+
readonly idx?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ShapeBoxEmu {
|
|
9
|
+
readonly x: number;
|
|
10
|
+
readonly y: number;
|
|
11
|
+
readonly cx: number;
|
|
12
|
+
readonly cy: number;
|
|
13
|
+
}
|
|
14
|
+
export declare function parsePh(sp: PoNode): PlaceholderRef | undefined;
|
|
15
|
+
export declare function boxFromXfrm(xfrm: PoNode | undefined): ShapeBoxEmu | undefined;
|
|
16
|
+
export declare function parseXfrmBox(spPr: PoNode | undefined): ShapeBoxEmu | undefined;
|
|
17
|
+
export declare function rPrToRunProps(rPr: PoNode | undefined, colors: ColorResolver): RunProperties;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { pt } from "../core/ir/units.js";
|
|
2
|
+
import { poAttr, poChildren, poIntAttr, poIs } from "../core/po-helpers.js";
|
|
3
|
+
import { resolveColorNode } from "../core/drawingml/colors.js";
|
|
4
|
+
//#region src/pptx/sp-helpers.ts
|
|
5
|
+
var isTrue = (v) => v === "1" || v === "true" || v === "on";
|
|
6
|
+
function parsePh(sp) {
|
|
7
|
+
const nvSpPr = poChildren(sp).find((c) => poIs(c, "p:nvSpPr"));
|
|
8
|
+
const nvPr = nvSpPr ? poChildren(nvSpPr).find((c) => poIs(c, "p:nvPr")) : void 0;
|
|
9
|
+
const ph = nvPr ? poChildren(nvPr).find((c) => poIs(c, "p:ph")) : void 0;
|
|
10
|
+
if (!ph) return void 0;
|
|
11
|
+
const type = poAttr(ph, "type");
|
|
12
|
+
const idx = poAttr(ph, "idx");
|
|
13
|
+
return {
|
|
14
|
+
...type !== void 0 ? { type } : {},
|
|
15
|
+
...idx !== void 0 ? { idx } : {}
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function boxFromXfrm(xfrm) {
|
|
19
|
+
if (!xfrm) return void 0;
|
|
20
|
+
const off = poChildren(xfrm).find((c) => poIs(c, "a:off"));
|
|
21
|
+
const ext = poChildren(xfrm).find((c) => poIs(c, "a:ext"));
|
|
22
|
+
const cx = ext ? poIntAttr(ext, "cx") : void 0;
|
|
23
|
+
const cy = ext ? poIntAttr(ext, "cy") : void 0;
|
|
24
|
+
if (cx === void 0 || cy === void 0 || cx <= 0 || cy <= 0) return void 0;
|
|
25
|
+
return {
|
|
26
|
+
x: (off ? poIntAttr(off, "x") : void 0) ?? 0,
|
|
27
|
+
y: (off ? poIntAttr(off, "y") : void 0) ?? 0,
|
|
28
|
+
cx,
|
|
29
|
+
cy
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function parseXfrmBox(spPr) {
|
|
33
|
+
return boxFromXfrm(spPr ? poChildren(spPr).find((c) => poIs(c, "a:xfrm")) : void 0);
|
|
34
|
+
}
|
|
35
|
+
function rPrToRunProps(rPr, colors) {
|
|
36
|
+
if (!rPr) return {};
|
|
37
|
+
const sz = poIntAttr(rPr, "sz");
|
|
38
|
+
const u = poAttr(rPr, "u");
|
|
39
|
+
const colorHex = solidFillColor(rPr, colors);
|
|
40
|
+
const latin = poChildren(rPr).find((c) => poIs(c, "a:latin"));
|
|
41
|
+
const typeface = latin ? poAttr(latin, "typeface")?.trim() : void 0;
|
|
42
|
+
return {
|
|
43
|
+
...isTrue(poAttr(rPr, "b")) ? { bold: true } : {},
|
|
44
|
+
...isTrue(poAttr(rPr, "i")) ? { italic: true } : {},
|
|
45
|
+
...u !== void 0 && u !== "none" ? { underline: "single" } : {},
|
|
46
|
+
...sz !== void 0 ? { fontSizePt: pt(sz / 100) } : {},
|
|
47
|
+
...colorHex ? { colorHex } : {},
|
|
48
|
+
...typeface ? { fontFamily: { ascii: typeface } } : {}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function solidFillColor(rPr, colors) {
|
|
52
|
+
const solidFill = poChildren(rPr).find((c) => poIs(c, "a:solidFill"));
|
|
53
|
+
if (!solidFill) return void 0;
|
|
54
|
+
for (const c of poChildren(solidFill)) {
|
|
55
|
+
const hex = resolveColorNode(c, colors);
|
|
56
|
+
if (hex) return hex;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
export { boxFromXfrm, parsePh, parseXfrmBox, rPrToRunProps };
|
|
@@ -36,3 +36,7 @@ export declare function resolveMc(altContent: PoNode): ReadonlyArray<PoNode>;
|
|
|
36
36
|
export declare function expandMcChildren(children: ReadonlyArray<PoNode>): Array<PoNode>;
|
|
37
37
|
export declare function parseDrawing(drawing: PoNode, resolveColor: ColorResolver, parseBody?: ParseBody): DrawingContent | null;
|
|
38
38
|
export declare function parseVmlPicture(node: PoNode): DrawingContent | null;
|
|
39
|
+
export declare function parsePrstGeom(prst: PoNode): ShapeGeometry;
|
|
40
|
+
export declare function parseCustGeom(cust: PoNode): ShapeGeometry;
|
|
41
|
+
export declare function parseFill(spPr: PoNode, resolveColor: ColorResolver): ShapeFill;
|
|
42
|
+
export declare function parseLine(spPr: PoNode, resolveColor: ColorResolver): ShapeLine | undefined;
|
|
@@ -441,4 +441,4 @@ function clampUnit(x) {
|
|
|
441
441
|
return x < 0 ? 0 : x > 1 ? 1 : x;
|
|
442
442
|
}
|
|
443
443
|
//#endregion
|
|
444
|
-
export { expandMcChildren, parseDrawing, parseVmlPicture };
|
|
444
|
+
export { expandMcChildren, parseCustGeom, parseDrawing, parseFill, parseLine, parsePrstGeom, parseVmlPicture };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reamkit",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Ream — convert DOCX, XLSX and PDF to PDF, SVG, HTML, DOCX and XLSX, built from scratch on the ECMA-376 and ISO 32000 specifications. Parse once, convert anywhere.",
|
|
3
|
+
"version": "1.10.0",
|
|
4
|
+
"description": "Ream — convert DOCX, XLSX, PPTX and PDF to PDF, SVG, HTML, DOCX and XLSX, built from scratch on the ECMA-376 and ISO 32000 specifications. Parse once, convert anywhere.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Alex Krassavin <info@reamkit.dev>",
|
|
7
7
|
"repository": {
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"keywords": [
|
|
39
39
|
"docx",
|
|
40
40
|
"xlsx",
|
|
41
|
+
"pptx",
|
|
41
42
|
"pdf",
|
|
42
43
|
"ooxml",
|
|
43
44
|
"ecma-376",
|
|
@@ -46,6 +47,7 @@
|
|
|
46
47
|
"office",
|
|
47
48
|
"word",
|
|
48
49
|
"excel",
|
|
50
|
+
"powerpoint",
|
|
49
51
|
"converter"
|
|
50
52
|
],
|
|
51
53
|
"scripts": {
|