quicklook-pptx-renderer 0.1.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/LICENSE +21 -0
- package/README.md +266 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +175 -0
- package/dist/diff/compare.d.ts +17 -0
- package/dist/diff/compare.js +71 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +72 -0
- package/dist/lint.d.ts +27 -0
- package/dist/lint.js +328 -0
- package/dist/mapper/bleed-map.d.ts +6 -0
- package/dist/mapper/bleed-map.js +1 -0
- package/dist/mapper/constants.d.ts +2 -0
- package/dist/mapper/constants.js +4 -0
- package/dist/mapper/drawable-mapper.d.ts +16 -0
- package/dist/mapper/drawable-mapper.js +1464 -0
- package/dist/mapper/html-generator.d.ts +13 -0
- package/dist/mapper/html-generator.js +539 -0
- package/dist/mapper/image-mapper.d.ts +14 -0
- package/dist/mapper/image-mapper.js +70 -0
- package/dist/mapper/nano-malloc.d.ts +130 -0
- package/dist/mapper/nano-malloc.js +197 -0
- package/dist/mapper/ql-bleed.d.ts +35 -0
- package/dist/mapper/ql-bleed.js +254 -0
- package/dist/mapper/shape-mapper.d.ts +41 -0
- package/dist/mapper/shape-mapper.js +2384 -0
- package/dist/mapper/slide-mapper.d.ts +4 -0
- package/dist/mapper/slide-mapper.js +112 -0
- package/dist/mapper/style-builder.d.ts +12 -0
- package/dist/mapper/style-builder.js +30 -0
- package/dist/mapper/text-mapper.d.ts +14 -0
- package/dist/mapper/text-mapper.js +302 -0
- package/dist/model/enums.d.ts +25 -0
- package/dist/model/enums.js +2 -0
- package/dist/model/types.d.ts +482 -0
- package/dist/model/types.js +7 -0
- package/dist/package/content-types.d.ts +1 -0
- package/dist/package/content-types.js +4 -0
- package/dist/package/package.d.ts +10 -0
- package/dist/package/package.js +52 -0
- package/dist/package/relationships.d.ts +6 -0
- package/dist/package/relationships.js +25 -0
- package/dist/package/zip.d.ts +6 -0
- package/dist/package/zip.js +17 -0
- package/dist/reader/color.d.ts +3 -0
- package/dist/reader/color.js +79 -0
- package/dist/reader/drawing.d.ts +17 -0
- package/dist/reader/drawing.js +403 -0
- package/dist/reader/effects.d.ts +2 -0
- package/dist/reader/effects.js +83 -0
- package/dist/reader/fill.d.ts +2 -0
- package/dist/reader/fill.js +94 -0
- package/dist/reader/presentation.d.ts +5 -0
- package/dist/reader/presentation.js +127 -0
- package/dist/reader/slide-layout.d.ts +2 -0
- package/dist/reader/slide-layout.js +28 -0
- package/dist/reader/slide-master.d.ts +4 -0
- package/dist/reader/slide-master.js +49 -0
- package/dist/reader/slide.d.ts +2 -0
- package/dist/reader/slide.js +26 -0
- package/dist/reader/text-list-style.d.ts +2 -0
- package/dist/reader/text-list-style.js +9 -0
- package/dist/reader/text.d.ts +5 -0
- package/dist/reader/text.js +295 -0
- package/dist/reader/theme.d.ts +2 -0
- package/dist/reader/theme.js +109 -0
- package/dist/reader/transform.d.ts +2 -0
- package/dist/reader/transform.js +21 -0
- package/dist/render/image-renderer.d.ts +3 -0
- package/dist/render/image-renderer.js +33 -0
- package/dist/render/renderer.d.ts +9 -0
- package/dist/render/renderer.js +178 -0
- package/dist/render/shape-renderer.d.ts +3 -0
- package/dist/render/shape-renderer.js +175 -0
- package/dist/render/text-renderer.d.ts +3 -0
- package/dist/render/text-renderer.js +152 -0
- package/dist/resolve/color-resolver.d.ts +18 -0
- package/dist/resolve/color-resolver.js +321 -0
- package/dist/resolve/font-map.d.ts +2 -0
- package/dist/resolve/font-map.js +66 -0
- package/dist/resolve/inheritance.d.ts +5 -0
- package/dist/resolve/inheritance.js +106 -0
- package/package.json +74 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { readTheme } from "./theme.js";
|
|
2
|
+
import { readTextListStyle } from "./text-list-style.js";
|
|
3
|
+
export { readTextListStyle };
|
|
4
|
+
import { readSlideMaster } from "./slide-master.js";
|
|
5
|
+
import { readSlideLayout } from "./slide-layout.js";
|
|
6
|
+
import { readSlide } from "./slide.js";
|
|
7
|
+
function toArray(val) {
|
|
8
|
+
return val == null ? [] : Array.isArray(val) ? val : [val];
|
|
9
|
+
}
|
|
10
|
+
function readSize(node) {
|
|
11
|
+
return {
|
|
12
|
+
cx: Number(node?.["@_cx"] ?? 0),
|
|
13
|
+
cy: Number(node?.["@_cy"] ?? 0),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function relsByType(rels, type) {
|
|
17
|
+
return [...rels.values()].filter((r) => r.type === type);
|
|
18
|
+
}
|
|
19
|
+
const EMPTY_THEME = {
|
|
20
|
+
name: "",
|
|
21
|
+
colorScheme: { name: "", colors: {} },
|
|
22
|
+
fontScheme: {
|
|
23
|
+
name: "",
|
|
24
|
+
majorFont: { latin: "", eastAsian: "", complexScript: "", scriptFonts: {} },
|
|
25
|
+
minorFont: { latin: "", eastAsian: "", complexScript: "", scriptFonts: {} },
|
|
26
|
+
},
|
|
27
|
+
styleMatrix: { fillStyles: [], lineStyles: [], effectStyles: [], bgFillStyles: [] },
|
|
28
|
+
};
|
|
29
|
+
export async function readPresentation(pkg) {
|
|
30
|
+
const presXml = await pkg.getPartXml("ppt/presentation.xml");
|
|
31
|
+
const presNode = presXml?.Presentation ?? presXml?.presentation ?? presXml ?? {};
|
|
32
|
+
const slideSize = readSize(presNode.sldSz);
|
|
33
|
+
const notesSize = readSize(presNode.notesSz);
|
|
34
|
+
const defaultTextStyle = presNode.defaultTextStyle
|
|
35
|
+
? readTextListStyle(presNode.defaultTextStyle)
|
|
36
|
+
: undefined;
|
|
37
|
+
const presRels = await pkg.getRelationships("ppt/presentation.xml");
|
|
38
|
+
const layoutsByPath = new Map();
|
|
39
|
+
// --- Slide masters ---
|
|
40
|
+
const masterIds = toArray(presNode.sldMasterIdLst?.sldMasterId);
|
|
41
|
+
const masters = [];
|
|
42
|
+
for (const mid of masterIds) {
|
|
43
|
+
// r:id attribute becomes @_id after removeNSPrefix (collides with numeric id, but rId value wins)
|
|
44
|
+
const rId = mid["@_rId"] ?? mid["@_id"];
|
|
45
|
+
const rel = presRels.get(rId);
|
|
46
|
+
if (!rel)
|
|
47
|
+
continue;
|
|
48
|
+
const masterPath = pkg.resolveRelTarget("ppt/presentation.xml", rel.target);
|
|
49
|
+
const masterXml = await pkg.getPartXml(masterPath);
|
|
50
|
+
if (!masterXml)
|
|
51
|
+
continue;
|
|
52
|
+
const masterPartial = readSlideMaster(masterXml);
|
|
53
|
+
const masterRels = await pkg.getRelationships(masterPath);
|
|
54
|
+
// Read theme
|
|
55
|
+
const themeRel = relsByType(masterRels, "theme")[0];
|
|
56
|
+
let theme = EMPTY_THEME;
|
|
57
|
+
if (themeRel) {
|
|
58
|
+
const themePath = pkg.resolveRelTarget(masterPath, themeRel.target);
|
|
59
|
+
const themeXml = await pkg.getPartXml(themePath);
|
|
60
|
+
if (themeXml)
|
|
61
|
+
theme = readTheme(themeXml);
|
|
62
|
+
}
|
|
63
|
+
// Read slide layouts for this master
|
|
64
|
+
const layoutRels = relsByType(masterRels, "slideLayout");
|
|
65
|
+
const layouts = [];
|
|
66
|
+
for (const lr of layoutRels) {
|
|
67
|
+
const layoutPath = pkg.resolveRelTarget(masterPath, lr.target);
|
|
68
|
+
const layoutXml = await pkg.getPartXml(layoutPath);
|
|
69
|
+
if (!layoutXml)
|
|
70
|
+
continue;
|
|
71
|
+
const layoutPartial = readSlideLayout(layoutXml);
|
|
72
|
+
const layout = { ...layoutPartial, slideMaster: null };
|
|
73
|
+
layouts.push(layout);
|
|
74
|
+
layoutsByPath.set(layoutPath, layout);
|
|
75
|
+
}
|
|
76
|
+
const master = {
|
|
77
|
+
...masterPartial,
|
|
78
|
+
theme,
|
|
79
|
+
slideLayouts: layouts,
|
|
80
|
+
};
|
|
81
|
+
// Wire layout->master back-references
|
|
82
|
+
for (const layout of layouts) {
|
|
83
|
+
layout.slideMaster = master;
|
|
84
|
+
}
|
|
85
|
+
masters.push(master);
|
|
86
|
+
}
|
|
87
|
+
// --- Slides ---
|
|
88
|
+
const slideIds = toArray(presNode.sldIdLst?.sldId);
|
|
89
|
+
const slides = [];
|
|
90
|
+
for (const sid of slideIds) {
|
|
91
|
+
const rId = sid["@_rId"] ?? sid["@_id"];
|
|
92
|
+
const rel = presRels.get(rId);
|
|
93
|
+
if (!rel)
|
|
94
|
+
continue;
|
|
95
|
+
const slidePath = pkg.resolveRelTarget("ppt/presentation.xml", rel.target);
|
|
96
|
+
const slideXml = await pkg.getPartXml(slidePath);
|
|
97
|
+
if (!slideXml)
|
|
98
|
+
continue;
|
|
99
|
+
// Pass raw XML so readDrawables can recover document order
|
|
100
|
+
const rawBuf = await pkg.getPartBuffer(slidePath);
|
|
101
|
+
const rawXml = rawBuf?.toString("utf8");
|
|
102
|
+
const slidePartial = readSlide(slideXml, rawXml);
|
|
103
|
+
const slideRels = await pkg.getRelationships(slidePath);
|
|
104
|
+
// Find this slide's layout
|
|
105
|
+
const layoutRel = relsByType(slideRels, "slideLayout")[0];
|
|
106
|
+
let slideLayout;
|
|
107
|
+
if (layoutRel) {
|
|
108
|
+
const layoutPath = pkg.resolveRelTarget(slidePath, layoutRel.target);
|
|
109
|
+
slideLayout = layoutsByPath.get(layoutPath);
|
|
110
|
+
}
|
|
111
|
+
// Fallback: first layout of first master
|
|
112
|
+
if (!slideLayout && masters.length > 0 && masters[0].slideLayouts.length > 0) {
|
|
113
|
+
slideLayout = masters[0].slideLayouts[0];
|
|
114
|
+
}
|
|
115
|
+
slides.push({
|
|
116
|
+
...slidePartial,
|
|
117
|
+
slideLayout: slideLayout,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
slides,
|
|
122
|
+
slideMasters: masters,
|
|
123
|
+
slideSize,
|
|
124
|
+
notesSize,
|
|
125
|
+
defaultTextStyle,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { readDrawables } from "./drawing.js";
|
|
2
|
+
import { readBackground, readColorMap } from "./slide-master.js";
|
|
3
|
+
function readColorMapOverride(node) {
|
|
4
|
+
if (node == null)
|
|
5
|
+
return undefined;
|
|
6
|
+
if (node.overrideClrMapping)
|
|
7
|
+
return readColorMap(node.overrideClrMapping);
|
|
8
|
+
// masterClrMapping means "use master's map" — no override
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
export function readSlideLayout(xml) {
|
|
12
|
+
const root = xml.sldLayout ?? xml;
|
|
13
|
+
const cSld = root.cSld ?? {};
|
|
14
|
+
const spTree = cSld.spTree ?? {};
|
|
15
|
+
const result = {
|
|
16
|
+
drawables: readDrawables(spTree),
|
|
17
|
+
};
|
|
18
|
+
const bg = readBackground(cSld.bg);
|
|
19
|
+
if (bg)
|
|
20
|
+
result.background = bg;
|
|
21
|
+
const layoutType = root["@_type"];
|
|
22
|
+
if (layoutType)
|
|
23
|
+
result.layoutType = layoutType;
|
|
24
|
+
const colorMapOverride = readColorMapOverride(root.clrMapOvr);
|
|
25
|
+
if (colorMapOverride)
|
|
26
|
+
result.colorMapOverride = colorMapOverride;
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { SlideMaster, ColorMap, Background } from "../model/types.js";
|
|
2
|
+
export declare function readColorMap(node: any): ColorMap;
|
|
3
|
+
export declare function readBackground(node: any): Background | undefined;
|
|
4
|
+
export declare function readSlideMaster(xml: any): Omit<SlideMaster, "theme" | "slideLayouts">;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { readDrawables } from "./drawing.js";
|
|
2
|
+
import { readFill } from "./fill.js";
|
|
3
|
+
import { readTextListStyle } from "./text-list-style.js";
|
|
4
|
+
const COLOR_MAP_KEYS = [
|
|
5
|
+
"bg1", "tx1", "bg2", "tx2",
|
|
6
|
+
"accent1", "accent2", "accent3", "accent4", "accent5", "accent6",
|
|
7
|
+
"hlink", "folHlink",
|
|
8
|
+
];
|
|
9
|
+
export function readColorMap(node) {
|
|
10
|
+
const mappings = {};
|
|
11
|
+
for (const key of COLOR_MAP_KEYS) {
|
|
12
|
+
const val = node[`@_${key}`];
|
|
13
|
+
if (val)
|
|
14
|
+
mappings[key] = val;
|
|
15
|
+
}
|
|
16
|
+
return { mappings };
|
|
17
|
+
}
|
|
18
|
+
export function readBackground(node) {
|
|
19
|
+
if (node == null)
|
|
20
|
+
return undefined;
|
|
21
|
+
const bgPr = node.bgPr;
|
|
22
|
+
if (bgPr) {
|
|
23
|
+
const fill = readFill(bgPr);
|
|
24
|
+
return fill ? { fill } : undefined;
|
|
25
|
+
}
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
export function readSlideMaster(xml) {
|
|
29
|
+
const root = xml.sldMaster ?? xml;
|
|
30
|
+
const cSld = root.cSld ?? {};
|
|
31
|
+
const spTree = cSld.spTree ?? {};
|
|
32
|
+
const result = {
|
|
33
|
+
drawables: readDrawables(spTree),
|
|
34
|
+
colorMap: readColorMap(root.clrMap ?? {}),
|
|
35
|
+
};
|
|
36
|
+
const bg = readBackground(cSld.bg);
|
|
37
|
+
if (bg)
|
|
38
|
+
result.background = bg;
|
|
39
|
+
const txStyles = root.txStyles;
|
|
40
|
+
if (txStyles) {
|
|
41
|
+
if (txStyles.titleStyle)
|
|
42
|
+
result.titleTextStyle = readTextListStyle(txStyles.titleStyle);
|
|
43
|
+
if (txStyles.bodyStyle)
|
|
44
|
+
result.bodyTextStyle = readTextListStyle(txStyles.bodyStyle);
|
|
45
|
+
if (txStyles.otherStyle)
|
|
46
|
+
result.otherTextStyle = readTextListStyle(txStyles.otherStyle);
|
|
47
|
+
}
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { readDrawables } from "./drawing.js";
|
|
2
|
+
import { readBackground, readColorMap } from "./slide-master.js";
|
|
3
|
+
function readColorMapOverride(node) {
|
|
4
|
+
if (node == null)
|
|
5
|
+
return undefined;
|
|
6
|
+
if (node.overrideClrMapping)
|
|
7
|
+
return readColorMap(node.overrideClrMapping);
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
export function readSlide(xml, rawXml) {
|
|
11
|
+
const root = xml.sld ?? xml;
|
|
12
|
+
const cSld = root.cSld ?? {};
|
|
13
|
+
const spTree = cSld.spTree ?? {};
|
|
14
|
+
const result = {
|
|
15
|
+
drawables: readDrawables(spTree, rawXml),
|
|
16
|
+
};
|
|
17
|
+
if (cSld["@_name"])
|
|
18
|
+
result.name = cSld["@_name"];
|
|
19
|
+
const bg = readBackground(cSld.bg);
|
|
20
|
+
if (bg)
|
|
21
|
+
result.background = bg;
|
|
22
|
+
const colorMapOverride = readColorMapOverride(root.clrMapOvr);
|
|
23
|
+
if (colorMapOverride)
|
|
24
|
+
result.colorMapOverride = colorMapOverride;
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { readParagraphProperties } from "./text.js";
|
|
2
|
+
export function readTextListStyle(node) {
|
|
3
|
+
const levels = [];
|
|
4
|
+
for (let i = 1; i <= 9; i++) {
|
|
5
|
+
const lvl = node[`lvl${i}pPr`];
|
|
6
|
+
levels.push(lvl ? readParagraphProperties(lvl) : undefined);
|
|
7
|
+
}
|
|
8
|
+
return { levels };
|
|
9
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { TextBody, Paragraph, CharacterProperties, ParagraphProperties } from "../model/types.js";
|
|
2
|
+
export declare function readCharacterProperties(rPr: any): CharacterProperties;
|
|
3
|
+
export declare function readParagraphProperties(pPr: any): ParagraphProperties;
|
|
4
|
+
export declare function readParagraph(pNode: any): Paragraph;
|
|
5
|
+
export declare function readTextBody(node: any): TextBody;
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { readColor } from "./color.js";
|
|
2
|
+
import { readFill } from "./fill.js";
|
|
3
|
+
import { readEffects } from "./effects.js";
|
|
4
|
+
function toArray(val) {
|
|
5
|
+
return val == null ? [] : Array.isArray(val) ? val : [val];
|
|
6
|
+
}
|
|
7
|
+
function toNum(val) {
|
|
8
|
+
return val == null ? undefined : Number(val);
|
|
9
|
+
}
|
|
10
|
+
function toBool(val) {
|
|
11
|
+
return val === "1" || val === "true";
|
|
12
|
+
}
|
|
13
|
+
function readSpacing(node) {
|
|
14
|
+
if (node == null)
|
|
15
|
+
return undefined;
|
|
16
|
+
if (node.spcPct)
|
|
17
|
+
return { type: "pct", val: Number(node.spcPct["@_val"] ?? 0) };
|
|
18
|
+
if (node.spcPts)
|
|
19
|
+
return { type: "pts", val: Number(node.spcPts["@_val"] ?? 0) };
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
function readLineEnd(node) {
|
|
23
|
+
if (node == null)
|
|
24
|
+
return undefined;
|
|
25
|
+
const type = node["@_type"];
|
|
26
|
+
if (!type)
|
|
27
|
+
return undefined;
|
|
28
|
+
const end = { type };
|
|
29
|
+
if (node["@_w"])
|
|
30
|
+
end.width = node["@_w"];
|
|
31
|
+
if (node["@_len"])
|
|
32
|
+
end.length = node["@_len"];
|
|
33
|
+
return end;
|
|
34
|
+
}
|
|
35
|
+
function readStroke(node) {
|
|
36
|
+
if (node == null)
|
|
37
|
+
return undefined;
|
|
38
|
+
const s = {};
|
|
39
|
+
if (node["@_w"] != null)
|
|
40
|
+
s.width = Number(node["@_w"]);
|
|
41
|
+
if (node["@_cap"])
|
|
42
|
+
s.cap = node["@_cap"];
|
|
43
|
+
if (node["@_cmpd"])
|
|
44
|
+
s.compound = node["@_cmpd"];
|
|
45
|
+
const fill = readFill(node);
|
|
46
|
+
if (fill)
|
|
47
|
+
s.fill = fill;
|
|
48
|
+
if (node.prstDash?.["@_val"])
|
|
49
|
+
s.dash = node.prstDash["@_val"];
|
|
50
|
+
if (node.round != null)
|
|
51
|
+
s.join = "round";
|
|
52
|
+
else if (node.bevel != null)
|
|
53
|
+
s.join = "bevel";
|
|
54
|
+
else if (node.miter != null) {
|
|
55
|
+
s.join = "miter";
|
|
56
|
+
if (node.miter["@_lim"] != null)
|
|
57
|
+
s.miterLimit = Number(node.miter["@_lim"]);
|
|
58
|
+
}
|
|
59
|
+
const head = readLineEnd(node.headEnd);
|
|
60
|
+
const tail = readLineEnd(node.tailEnd);
|
|
61
|
+
if (head)
|
|
62
|
+
s.headEnd = head;
|
|
63
|
+
if (tail)
|
|
64
|
+
s.tailEnd = tail;
|
|
65
|
+
return s;
|
|
66
|
+
}
|
|
67
|
+
export function readCharacterProperties(rPr) {
|
|
68
|
+
const cp = {};
|
|
69
|
+
if (rPr == null)
|
|
70
|
+
return cp;
|
|
71
|
+
if (rPr.latin?.["@_typeface"])
|
|
72
|
+
cp.latinFont = rPr.latin["@_typeface"];
|
|
73
|
+
if (rPr.ea?.["@_typeface"])
|
|
74
|
+
cp.eastAsianFont = rPr.ea["@_typeface"];
|
|
75
|
+
if (rPr.cs?.["@_typeface"])
|
|
76
|
+
cp.complexScriptFont = rPr.cs["@_typeface"];
|
|
77
|
+
if (rPr.sym?.["@_typeface"])
|
|
78
|
+
cp.symbolFont = rPr.sym["@_typeface"];
|
|
79
|
+
cp.fontSize = toNum(rPr["@_sz"]);
|
|
80
|
+
if (rPr["@_b"] != null)
|
|
81
|
+
cp.bold = toBool(rPr["@_b"]);
|
|
82
|
+
if (rPr["@_i"] != null)
|
|
83
|
+
cp.italic = toBool(rPr["@_i"]);
|
|
84
|
+
if (rPr["@_u"] != null)
|
|
85
|
+
cp.underline = rPr["@_u"];
|
|
86
|
+
if (rPr["@_strike"] != null)
|
|
87
|
+
cp.strikethrough = rPr["@_strike"];
|
|
88
|
+
if (rPr["@_cap"] != null)
|
|
89
|
+
cp.caps = rPr["@_cap"];
|
|
90
|
+
cp.baseline = toNum(rPr["@_baseline"]);
|
|
91
|
+
cp.spacing = toNum(rPr["@_spc"]);
|
|
92
|
+
cp.kerning = toNum(rPr["@_kern"]);
|
|
93
|
+
if (rPr["@_lang"])
|
|
94
|
+
cp.lang = rPr["@_lang"];
|
|
95
|
+
if (rPr["@_dirty"] != null)
|
|
96
|
+
cp.dirty = toBool(rPr["@_dirty"]);
|
|
97
|
+
const fill = readFill(rPr);
|
|
98
|
+
if (fill)
|
|
99
|
+
cp.fill = fill;
|
|
100
|
+
const highlight = readColor(rPr.highlight ?? rPr.highlightClr);
|
|
101
|
+
if (highlight)
|
|
102
|
+
cp.highlight = highlight;
|
|
103
|
+
const stroke = readStroke(rPr.ln);
|
|
104
|
+
if (stroke)
|
|
105
|
+
cp.stroke = stroke;
|
|
106
|
+
const uFill = readFill(rPr.uFill);
|
|
107
|
+
if (uFill)
|
|
108
|
+
cp.underlineFill = uFill;
|
|
109
|
+
const effects = readEffects(rPr.effectLst);
|
|
110
|
+
if (effects.length)
|
|
111
|
+
cp.effects = effects;
|
|
112
|
+
if (rPr.hlinkClick?.["@_id"])
|
|
113
|
+
cp.hyperlink = rPr.hlinkClick["@_id"];
|
|
114
|
+
return cp;
|
|
115
|
+
}
|
|
116
|
+
function readBullet(pPr) {
|
|
117
|
+
if (pPr.buNone != null)
|
|
118
|
+
return { type: "none" };
|
|
119
|
+
if (pPr.buChar != null) {
|
|
120
|
+
// Decode XML character references (&#xHHHH;) that fast-xml-parser leaves as literal strings
|
|
121
|
+
const raw = pPr.buChar["@_char"] ?? "";
|
|
122
|
+
const decoded = raw.replace(/&#x([0-9a-fA-F]+);/g, (_, hex) => String.fromCodePoint(parseInt(hex, 16)))
|
|
123
|
+
.replace(/&#(\d+);/g, (_, dec) => String.fromCodePoint(parseInt(dec, 10)));
|
|
124
|
+
return { type: "char", char: decoded };
|
|
125
|
+
}
|
|
126
|
+
if (pPr.buAutoNum != null) {
|
|
127
|
+
const b = {
|
|
128
|
+
type: "autoNum",
|
|
129
|
+
autoNumScheme: pPr.buAutoNum["@_type"],
|
|
130
|
+
};
|
|
131
|
+
if (pPr.buAutoNum["@_startAt"] != null)
|
|
132
|
+
b.startAt = Number(pPr.buAutoNum["@_startAt"]);
|
|
133
|
+
return b;
|
|
134
|
+
}
|
|
135
|
+
if (pPr.buBlip != null) {
|
|
136
|
+
return { type: "blip", blipRId: pPr.buBlip.blip?.["@_embed"] };
|
|
137
|
+
}
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
export function readParagraphProperties(pPr) {
|
|
141
|
+
const pp = readCharacterProperties(pPr?.defRPr);
|
|
142
|
+
if (pPr == null)
|
|
143
|
+
return pp;
|
|
144
|
+
pp.level = toNum(pPr["@_lvl"]);
|
|
145
|
+
if (pPr["@_algn"])
|
|
146
|
+
pp.align = pPr["@_algn"];
|
|
147
|
+
pp.marL = toNum(pPr["@_marL"]);
|
|
148
|
+
pp.marR = toNum(pPr["@_marR"]);
|
|
149
|
+
pp.indent = toNum(pPr["@_indent"]);
|
|
150
|
+
pp.defTabSz = toNum(pPr["@_defTabSz"]);
|
|
151
|
+
if (pPr["@_rtl"] != null)
|
|
152
|
+
pp.rtl = toBool(pPr["@_rtl"]);
|
|
153
|
+
const lnSpc = readSpacing(pPr.lnSpc);
|
|
154
|
+
if (lnSpc)
|
|
155
|
+
pp.lineSpacing = lnSpc;
|
|
156
|
+
const spcBef = readSpacing(pPr.spcBef);
|
|
157
|
+
if (spcBef)
|
|
158
|
+
pp.spaceBefore = spcBef;
|
|
159
|
+
const spcAft = readSpacing(pPr.spcAft);
|
|
160
|
+
if (spcAft)
|
|
161
|
+
pp.spaceAfter = spcAft;
|
|
162
|
+
const bullet = readBullet(pPr);
|
|
163
|
+
if (bullet)
|
|
164
|
+
pp.bullet = bullet;
|
|
165
|
+
const bulletClr = readColor(pPr.buClr);
|
|
166
|
+
if (bulletClr)
|
|
167
|
+
pp.bulletColor = bulletClr;
|
|
168
|
+
if (pPr.buFont?.["@_typeface"])
|
|
169
|
+
pp.bulletFont = pPr.buFont["@_typeface"];
|
|
170
|
+
if (pPr.buSzPct?.["@_val"] != null)
|
|
171
|
+
pp.bulletSizePercent = Number(pPr.buSzPct["@_val"]);
|
|
172
|
+
if (pPr.buSzPts?.["@_val"] != null)
|
|
173
|
+
pp.bulletSizePoints = Number(pPr.buSzPts["@_val"]);
|
|
174
|
+
return pp;
|
|
175
|
+
}
|
|
176
|
+
function readRun(rNode) {
|
|
177
|
+
const run = {
|
|
178
|
+
type: "r",
|
|
179
|
+
text: rNode.t ?? "",
|
|
180
|
+
};
|
|
181
|
+
if (rNode.rPr) {
|
|
182
|
+
const props = readCharacterProperties(rNode.rPr);
|
|
183
|
+
if (Object.keys(props).length)
|
|
184
|
+
run.properties = props;
|
|
185
|
+
}
|
|
186
|
+
return run;
|
|
187
|
+
}
|
|
188
|
+
function readBreak(brNode) {
|
|
189
|
+
const br = { type: "br" };
|
|
190
|
+
if (brNode.rPr) {
|
|
191
|
+
const props = readCharacterProperties(brNode.rPr);
|
|
192
|
+
if (Object.keys(props).length)
|
|
193
|
+
br.properties = props;
|
|
194
|
+
}
|
|
195
|
+
return br;
|
|
196
|
+
}
|
|
197
|
+
function readField(fldNode) {
|
|
198
|
+
const fld = {
|
|
199
|
+
type: "fld",
|
|
200
|
+
text: fldNode.t ?? "",
|
|
201
|
+
};
|
|
202
|
+
if (fldNode["@_type"])
|
|
203
|
+
fld.fieldType = fldNode["@_type"];
|
|
204
|
+
if (fldNode.rPr) {
|
|
205
|
+
const props = readCharacterProperties(fldNode.rPr);
|
|
206
|
+
if (Object.keys(props).length)
|
|
207
|
+
fld.properties = props;
|
|
208
|
+
}
|
|
209
|
+
return fld;
|
|
210
|
+
}
|
|
211
|
+
export function readParagraph(pNode) {
|
|
212
|
+
const runs = [];
|
|
213
|
+
for (const r of toArray(pNode.r))
|
|
214
|
+
runs.push(readRun(r));
|
|
215
|
+
for (const br of toArray(pNode.br))
|
|
216
|
+
runs.push(readBreak(br));
|
|
217
|
+
for (const fld of toArray(pNode.fld))
|
|
218
|
+
runs.push(readField(fld));
|
|
219
|
+
const para = { runs };
|
|
220
|
+
if (pNode.pPr) {
|
|
221
|
+
const props = readParagraphProperties(pNode.pPr);
|
|
222
|
+
if (Object.keys(props).length)
|
|
223
|
+
para.properties = props;
|
|
224
|
+
}
|
|
225
|
+
if (pNode.endParaRPr) {
|
|
226
|
+
const ep = readCharacterProperties(pNode.endParaRPr);
|
|
227
|
+
if (Object.keys(ep).length)
|
|
228
|
+
para.endParaRPr = ep;
|
|
229
|
+
}
|
|
230
|
+
return para;
|
|
231
|
+
}
|
|
232
|
+
function readBodyProperties(bp) {
|
|
233
|
+
const props = {};
|
|
234
|
+
if (bp == null)
|
|
235
|
+
return props;
|
|
236
|
+
if (bp["@_wrap"])
|
|
237
|
+
props.wrap = bp["@_wrap"];
|
|
238
|
+
props.lIns = toNum(bp["@_lIns"]);
|
|
239
|
+
props.tIns = toNum(bp["@_tIns"]);
|
|
240
|
+
props.rIns = toNum(bp["@_rIns"]);
|
|
241
|
+
props.bIns = toNum(bp["@_bIns"]);
|
|
242
|
+
if (bp["@_anchor"])
|
|
243
|
+
props.anchor = bp["@_anchor"];
|
|
244
|
+
if (bp["@_anchorCtr"] != null)
|
|
245
|
+
props.anchorCtr = toBool(bp["@_anchorCtr"]);
|
|
246
|
+
if (bp["@_rtlCol"] != null)
|
|
247
|
+
props.rtlCol = toBool(bp["@_rtlCol"]);
|
|
248
|
+
if (bp["@_vert"])
|
|
249
|
+
props.vert = bp["@_vert"];
|
|
250
|
+
props.rot = toNum(bp["@_rot"]);
|
|
251
|
+
props.numCol = toNum(bp["@_numCol"]);
|
|
252
|
+
props.spcCol = toNum(bp["@_spcCol"]);
|
|
253
|
+
if (bp.noAutofit != null)
|
|
254
|
+
props.autoFit = "noAutoFit";
|
|
255
|
+
else if (bp.spAutoFit != null)
|
|
256
|
+
props.autoFit = "spAutoFit";
|
|
257
|
+
else if (bp.normAutofit != null) {
|
|
258
|
+
props.autoFit = "normAutoFit";
|
|
259
|
+
props.fontScale = toNum(bp.normAutofit["@_fontScale"]);
|
|
260
|
+
props.lnSpcReduction = toNum(bp.normAutofit["@_lnSpcReduction"]);
|
|
261
|
+
}
|
|
262
|
+
return props;
|
|
263
|
+
}
|
|
264
|
+
const LEVEL_TAGS = ["lvl1pPr", "lvl2pPr", "lvl3pPr", "lvl4pPr", "lvl5pPr", "lvl6pPr", "lvl7pPr", "lvl8pPr", "lvl9pPr"];
|
|
265
|
+
function readListStyle(node) {
|
|
266
|
+
if (node == null)
|
|
267
|
+
return undefined;
|
|
268
|
+
const levels = [];
|
|
269
|
+
let any = false;
|
|
270
|
+
for (let i = 0; i < LEVEL_TAGS.length; i++) {
|
|
271
|
+
const lvl = node[LEVEL_TAGS[i]];
|
|
272
|
+
if (lvl) {
|
|
273
|
+
levels[i] = readParagraphProperties(lvl);
|
|
274
|
+
any = true;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
// Also handle defPPr as level 0 fallback
|
|
278
|
+
if (node.defPPr) {
|
|
279
|
+
levels[0] = readParagraphProperties(node.defPPr);
|
|
280
|
+
any = true;
|
|
281
|
+
}
|
|
282
|
+
return any ? { levels } : undefined;
|
|
283
|
+
}
|
|
284
|
+
export function readTextBody(node) {
|
|
285
|
+
const body = {
|
|
286
|
+
paragraphs: toArray(node.p).map(readParagraph),
|
|
287
|
+
};
|
|
288
|
+
const bp = readBodyProperties(node.bodyPr);
|
|
289
|
+
if (Object.keys(bp).length)
|
|
290
|
+
body.properties = bp;
|
|
291
|
+
const ls = readListStyle(node.lstStyle);
|
|
292
|
+
if (ls)
|
|
293
|
+
body.listStyle = ls;
|
|
294
|
+
return body;
|
|
295
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { readColor } from "./color.js";
|
|
2
|
+
import { readFill } from "./fill.js";
|
|
3
|
+
import { readEffects } from "./effects.js";
|
|
4
|
+
function toArray(val) {
|
|
5
|
+
return val == null ? [] : Array.isArray(val) ? val : [val];
|
|
6
|
+
}
|
|
7
|
+
const SCHEME_COLOR_NAMES = [
|
|
8
|
+
"dk1", "lt1", "dk2", "lt2",
|
|
9
|
+
"accent1", "accent2", "accent3", "accent4", "accent5", "accent6",
|
|
10
|
+
"hlink", "folHlink",
|
|
11
|
+
];
|
|
12
|
+
function readColorScheme(node) {
|
|
13
|
+
const colors = {};
|
|
14
|
+
for (const name of SCHEME_COLOR_NAMES) {
|
|
15
|
+
const el = node[name];
|
|
16
|
+
if (el == null)
|
|
17
|
+
continue;
|
|
18
|
+
const c = readColor(el);
|
|
19
|
+
if (c)
|
|
20
|
+
colors[name] = c;
|
|
21
|
+
}
|
|
22
|
+
return { name: node["@_name"] ?? "", colors };
|
|
23
|
+
}
|
|
24
|
+
function readFontCollection(node) {
|
|
25
|
+
const scriptFonts = {};
|
|
26
|
+
for (const f of toArray(node.font)) {
|
|
27
|
+
if (f["@_script"] && f["@_typeface"]) {
|
|
28
|
+
scriptFonts[f["@_script"]] = f["@_typeface"];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
latin: node.latin?.["@_typeface"] ?? "",
|
|
33
|
+
eastAsian: node.ea?.["@_typeface"] ?? "",
|
|
34
|
+
complexScript: node.cs?.["@_typeface"] ?? "",
|
|
35
|
+
scriptFonts,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function readFontScheme(node) {
|
|
39
|
+
return {
|
|
40
|
+
name: node["@_name"] ?? "",
|
|
41
|
+
majorFont: readFontCollection(node.majorFont ?? {}),
|
|
42
|
+
minorFont: readFontCollection(node.minorFont ?? {}),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function readStroke(node) {
|
|
46
|
+
const s = {};
|
|
47
|
+
if (node["@_w"] != null)
|
|
48
|
+
s.width = Number(node["@_w"]);
|
|
49
|
+
if (node["@_cap"])
|
|
50
|
+
s.cap = node["@_cap"];
|
|
51
|
+
if (node["@_cmpd"])
|
|
52
|
+
s.compound = node["@_cmpd"];
|
|
53
|
+
const fill = readFill(node);
|
|
54
|
+
if (fill)
|
|
55
|
+
s.fill = fill;
|
|
56
|
+
if (node.prstDash?.["@_val"])
|
|
57
|
+
s.dash = node.prstDash["@_val"];
|
|
58
|
+
if (node.round != null)
|
|
59
|
+
s.join = "round";
|
|
60
|
+
else if (node.bevel != null)
|
|
61
|
+
s.join = "bevel";
|
|
62
|
+
else if (node.miter != null) {
|
|
63
|
+
s.join = "miter";
|
|
64
|
+
if (node.miter["@_lim"] != null)
|
|
65
|
+
s.miterLimit = Number(node.miter["@_lim"]);
|
|
66
|
+
}
|
|
67
|
+
return s;
|
|
68
|
+
}
|
|
69
|
+
function readStyleMatrix(node) {
|
|
70
|
+
const fillStyles = toArray(node.fillStyleLst?.solidFill)
|
|
71
|
+
.map((n) => readFill({ solidFill: n }))
|
|
72
|
+
.filter(Boolean);
|
|
73
|
+
// fillStyleLst can contain mixed fill types; read all children in order
|
|
74
|
+
const fillStyleLst = node.fillStyleLst ?? {};
|
|
75
|
+
const allFills = [];
|
|
76
|
+
for (const key of ["solidFill", "gradFill", "blipFill", "pattFill", "noFill", "grpFill"]) {
|
|
77
|
+
for (const el of toArray(fillStyleLst[key])) {
|
|
78
|
+
const f = readFill({ [key]: el });
|
|
79
|
+
if (f)
|
|
80
|
+
allFills.push(f);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const bgFillStyleLst = node.bgFillStyleLst ?? {};
|
|
84
|
+
const bgFills = [];
|
|
85
|
+
for (const key of ["solidFill", "gradFill", "blipFill", "pattFill", "noFill", "grpFill"]) {
|
|
86
|
+
for (const el of toArray(bgFillStyleLst[key])) {
|
|
87
|
+
const f = readFill({ [key]: el });
|
|
88
|
+
if (f)
|
|
89
|
+
bgFills.push(f);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const lineStyles = toArray(node.lnStyleLst?.ln).map(readStroke);
|
|
93
|
+
const effectStyles = toArray(node.effectStyleLst?.effectStyle).map((es) => readEffects(es.effectLst));
|
|
94
|
+
return {
|
|
95
|
+
fillStyles: allFills,
|
|
96
|
+
lineStyles: lineStyles,
|
|
97
|
+
effectStyles,
|
|
98
|
+
bgFillStyles: bgFills,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
export function readTheme(xml) {
|
|
102
|
+
const te = xml.theme?.themeElements ?? xml.themeElements ?? {};
|
|
103
|
+
return {
|
|
104
|
+
name: xml.theme?.["@_name"] ?? xml["@_name"] ?? "",
|
|
105
|
+
colorScheme: readColorScheme(te.clrScheme ?? {}),
|
|
106
|
+
fontScheme: readFontScheme(te.fontScheme ?? {}),
|
|
107
|
+
styleMatrix: readStyleMatrix(te.fmtScheme ?? {}),
|
|
108
|
+
};
|
|
109
|
+
}
|