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 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,4 @@
1
+ import { DocumentReader, ReadResult } from '../core/ir/adapters.js';
2
+ import { FlowDoc } from '../core/ir/flow.js';
3
+ export declare function readPptx(bytes: Uint8Array): ReadResult<FlowDoc>;
4
+ export declare const pptxReader: DocumentReader<FlowDoc>;
@@ -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.9.0",
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": {