pptx-glimpse 0.1.0 → 0.1.2
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 +132 -99
- package/dist/index.cjs +2304 -371
- package/dist/index.d.cts +23 -1
- package/dist/index.d.ts +23 -1
- package/dist/index.js +2301 -370
- package/package.json +22 -12
package/dist/index.js
CHANGED
|
@@ -29,27 +29,52 @@ async function readPptx(input) {
|
|
|
29
29
|
import { XMLParser } from "fast-xml-parser";
|
|
30
30
|
var ARRAY_TAGS = /* @__PURE__ */ new Set([
|
|
31
31
|
"sp",
|
|
32
|
+
// 図形 (Shape)
|
|
32
33
|
"pic",
|
|
34
|
+
// 画像 (Picture)
|
|
33
35
|
"cxnSp",
|
|
36
|
+
// コネクタ (Connector)
|
|
34
37
|
"grpSp",
|
|
38
|
+
// グループ (Group Shape)
|
|
35
39
|
"graphicFrame",
|
|
40
|
+
// テーブル・チャート等のフレーム
|
|
36
41
|
"p",
|
|
42
|
+
// テキスト段落 (Paragraph)
|
|
37
43
|
"r",
|
|
44
|
+
// テキストラン (Run)
|
|
38
45
|
"br",
|
|
46
|
+
// 改行 (Break)
|
|
47
|
+
"fld",
|
|
48
|
+
// フィールドコード (Field)
|
|
39
49
|
"Relationship",
|
|
50
|
+
// リレーションシップ
|
|
40
51
|
"sldId",
|
|
52
|
+
// スライド ID
|
|
41
53
|
"gs",
|
|
54
|
+
// グラデーションストップ (Gradient Stop)
|
|
42
55
|
"gridCol",
|
|
56
|
+
// テーブル列定義
|
|
43
57
|
"tr",
|
|
58
|
+
// テーブル行 (Table Row)
|
|
44
59
|
"tc",
|
|
60
|
+
// テーブルセル (Table Cell)
|
|
45
61
|
"ser",
|
|
46
|
-
|
|
62
|
+
// チャートデータ系列 (Series)
|
|
63
|
+
"pt",
|
|
64
|
+
// チャートデータポイント (Point)
|
|
65
|
+
"gd",
|
|
66
|
+
// ガイド定義 (Guide Definition)
|
|
67
|
+
"ds",
|
|
68
|
+
// カスタムダッシュセグメント (Custom Dash Segment)
|
|
69
|
+
"AlternateContent"
|
|
70
|
+
// mc:AlternateContent (SmartArt 等)
|
|
47
71
|
]);
|
|
48
72
|
function createXmlParser() {
|
|
49
73
|
return new XMLParser({
|
|
50
74
|
ignoreAttributes: false,
|
|
51
75
|
attributeNamePrefix: "@_",
|
|
52
76
|
removeNSPrefix: true,
|
|
77
|
+
htmlEntities: true,
|
|
53
78
|
isArray: (_name, jpath) => {
|
|
54
79
|
const tag = jpath.split(".").pop() ?? "";
|
|
55
80
|
return ARRAY_TAGS.has(tag);
|
|
@@ -60,16 +85,179 @@ function parseXml(xml) {
|
|
|
60
85
|
const parser = createXmlParser();
|
|
61
86
|
return parser.parse(xml);
|
|
62
87
|
}
|
|
88
|
+
function parseXmlOrdered(xml) {
|
|
89
|
+
const parser = new XMLParser({
|
|
90
|
+
preserveOrder: true,
|
|
91
|
+
removeNSPrefix: true,
|
|
92
|
+
ignoreAttributes: true
|
|
93
|
+
});
|
|
94
|
+
return parser.parse(xml);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/utils/constants.ts
|
|
98
|
+
var EMU_PER_INCH = 914400;
|
|
99
|
+
var DEFAULT_DPI = 96;
|
|
100
|
+
var DEFAULT_OUTPUT_WIDTH = 960;
|
|
101
|
+
|
|
102
|
+
// src/utils/emu.ts
|
|
103
|
+
function emuToPixels(emu, dpi = DEFAULT_DPI) {
|
|
104
|
+
return emu / EMU_PER_INCH * dpi;
|
|
105
|
+
}
|
|
106
|
+
function hundredthPointToPoint(value) {
|
|
107
|
+
return value / 100;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// src/parser/text-style-parser.ts
|
|
111
|
+
function parseDefaultRunProperties(defRPr, colorResolver) {
|
|
112
|
+
if (!defRPr) return void 0;
|
|
113
|
+
const result = {};
|
|
114
|
+
if (defRPr["@_sz"] !== void 0) {
|
|
115
|
+
result.fontSize = hundredthPointToPoint(Number(defRPr["@_sz"]));
|
|
116
|
+
}
|
|
117
|
+
const latin = defRPr.latin;
|
|
118
|
+
if (latin?.["@_typeface"] !== void 0) {
|
|
119
|
+
result.fontFamily = latin["@_typeface"];
|
|
120
|
+
}
|
|
121
|
+
const ea = defRPr.ea;
|
|
122
|
+
if (ea?.["@_typeface"] !== void 0) {
|
|
123
|
+
result.fontFamilyEa = ea["@_typeface"];
|
|
124
|
+
}
|
|
125
|
+
if (defRPr["@_b"] !== void 0) {
|
|
126
|
+
result.bold = defRPr["@_b"] === "1" || defRPr["@_b"] === "true";
|
|
127
|
+
}
|
|
128
|
+
if (defRPr["@_i"] !== void 0) {
|
|
129
|
+
result.italic = defRPr["@_i"] === "1" || defRPr["@_i"] === "true";
|
|
130
|
+
}
|
|
131
|
+
if (defRPr["@_u"] !== void 0) {
|
|
132
|
+
result.underline = defRPr["@_u"] !== "none";
|
|
133
|
+
}
|
|
134
|
+
if (defRPr["@_strike"] !== void 0) {
|
|
135
|
+
result.strikethrough = defRPr["@_strike"] !== "noStrike";
|
|
136
|
+
}
|
|
137
|
+
if (colorResolver) {
|
|
138
|
+
const solidFill = defRPr.solidFill;
|
|
139
|
+
if (solidFill) {
|
|
140
|
+
const color = colorResolver.resolve(solidFill);
|
|
141
|
+
if (color) {
|
|
142
|
+
result.color = color;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
147
|
+
}
|
|
148
|
+
function parseParagraphLevelProperties(node, colorResolver) {
|
|
149
|
+
if (!node) return void 0;
|
|
150
|
+
const result = {};
|
|
151
|
+
if (node["@_algn"] !== void 0) {
|
|
152
|
+
result.alignment = node["@_algn"];
|
|
153
|
+
}
|
|
154
|
+
if (node["@_marL"] !== void 0) {
|
|
155
|
+
result.marginLeft = Number(node["@_marL"]);
|
|
156
|
+
}
|
|
157
|
+
if (node["@_indent"] !== void 0) {
|
|
158
|
+
result.indent = Number(node["@_indent"]);
|
|
159
|
+
}
|
|
160
|
+
const defRPr = parseDefaultRunProperties(node.defRPr, colorResolver);
|
|
161
|
+
if (defRPr) {
|
|
162
|
+
result.defaultRunProperties = defRPr;
|
|
163
|
+
}
|
|
164
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
165
|
+
}
|
|
166
|
+
function parseListStyle(node, colorResolver) {
|
|
167
|
+
if (!node) return void 0;
|
|
168
|
+
const defaultParagraph = parseParagraphLevelProperties(node.defPPr, colorResolver);
|
|
169
|
+
const levels = [];
|
|
170
|
+
for (let i = 1; i <= 9; i++) {
|
|
171
|
+
levels.push(parseParagraphLevelProperties(node[`lvl${i}pPr`], colorResolver));
|
|
172
|
+
}
|
|
173
|
+
if (!defaultParagraph && levels.every((l) => l === void 0)) {
|
|
174
|
+
return void 0;
|
|
175
|
+
}
|
|
176
|
+
return { defaultParagraph, levels };
|
|
177
|
+
}
|
|
178
|
+
function resolveThemeFont(typeface, fontScheme) {
|
|
179
|
+
if (!typeface || !fontScheme) return typeface;
|
|
180
|
+
switch (typeface) {
|
|
181
|
+
case "+mj-lt":
|
|
182
|
+
return fontScheme.majorFont;
|
|
183
|
+
case "+mn-lt":
|
|
184
|
+
return fontScheme.minorFont;
|
|
185
|
+
case "+mj-ea":
|
|
186
|
+
return fontScheme.majorFontEa;
|
|
187
|
+
case "+mn-ea":
|
|
188
|
+
return fontScheme.minorFontEa;
|
|
189
|
+
default:
|
|
190
|
+
return typeface;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// src/warning-logger.ts
|
|
195
|
+
var PREFIX = "[pptx-glimpse]";
|
|
196
|
+
var currentLevel = "off";
|
|
197
|
+
var entries = [];
|
|
198
|
+
var featureCounts = /* @__PURE__ */ new Map();
|
|
199
|
+
function initWarningLogger(level) {
|
|
200
|
+
currentLevel = level;
|
|
201
|
+
entries = [];
|
|
202
|
+
featureCounts.clear();
|
|
203
|
+
}
|
|
204
|
+
function warn(feature, message, context) {
|
|
205
|
+
if (currentLevel === "off") return;
|
|
206
|
+
entries.push({ feature, message, ...context !== void 0 && { context } });
|
|
207
|
+
const existing = featureCounts.get(feature);
|
|
208
|
+
if (existing) {
|
|
209
|
+
existing.count++;
|
|
210
|
+
} else {
|
|
211
|
+
featureCounts.set(feature, { message, count: 1 });
|
|
212
|
+
}
|
|
213
|
+
if (currentLevel === "debug") {
|
|
214
|
+
const ctx = context ? ` (${context})` : "";
|
|
215
|
+
console.warn(`${PREFIX} SKIP: ${feature} - ${message}${ctx}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
function debug(feature, message, context) {
|
|
219
|
+
if (currentLevel !== "debug") return;
|
|
220
|
+
entries.push({ feature, message, ...context !== void 0 && { context } });
|
|
221
|
+
const existing = featureCounts.get(feature);
|
|
222
|
+
if (existing) {
|
|
223
|
+
existing.count++;
|
|
224
|
+
} else {
|
|
225
|
+
featureCounts.set(feature, { message, count: 1 });
|
|
226
|
+
}
|
|
227
|
+
const ctx = context ? ` (${context})` : "";
|
|
228
|
+
console.warn(`${PREFIX} DEBUG: ${feature} - ${message}${ctx}`);
|
|
229
|
+
}
|
|
230
|
+
function getWarningSummary() {
|
|
231
|
+
const features = [];
|
|
232
|
+
for (const [feature, { message, count }] of featureCounts) {
|
|
233
|
+
features.push({ feature, message, count });
|
|
234
|
+
}
|
|
235
|
+
return { totalCount: entries.length, features };
|
|
236
|
+
}
|
|
237
|
+
function flushWarnings() {
|
|
238
|
+
const summary = getWarningSummary();
|
|
239
|
+
if (currentLevel !== "off" && summary.features.length > 0) {
|
|
240
|
+
console.warn(`${PREFIX} Summary: ${summary.features.length} unsupported feature(s) detected`);
|
|
241
|
+
for (const { feature, count } of summary.features) {
|
|
242
|
+
console.warn(` - ${feature}: ${count} occurrence(s)`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
entries = [];
|
|
246
|
+
featureCounts.clear();
|
|
247
|
+
return summary;
|
|
248
|
+
}
|
|
249
|
+
function getWarningEntries() {
|
|
250
|
+
return entries;
|
|
251
|
+
}
|
|
63
252
|
|
|
64
253
|
// src/parser/presentation-parser.ts
|
|
65
|
-
var WARN_PREFIX = "[pptx-glimpse]";
|
|
66
254
|
var DEFAULT_SLIDE_WIDTH = 9144e3;
|
|
67
255
|
var DEFAULT_SLIDE_HEIGHT = 5143500;
|
|
68
256
|
function parsePresentation(xml) {
|
|
69
257
|
const parsed = parseXml(xml);
|
|
70
258
|
const pres = parsed.presentation;
|
|
71
259
|
if (!pres) {
|
|
72
|
-
|
|
260
|
+
debug("presentation.missing", `missing root element "presentation" in XML`);
|
|
73
261
|
return {
|
|
74
262
|
slideSize: { width: DEFAULT_SLIDE_WIDTH, height: DEFAULT_SLIDE_HEIGHT },
|
|
75
263
|
slideRIds: []
|
|
@@ -78,8 +266,9 @@ function parsePresentation(xml) {
|
|
|
78
266
|
const sldSz = pres.sldSz;
|
|
79
267
|
let slideSize;
|
|
80
268
|
if (!sldSz || sldSz["@_cx"] === void 0 || sldSz["@_cy"] === void 0) {
|
|
81
|
-
|
|
82
|
-
|
|
269
|
+
debug(
|
|
270
|
+
"presentation.sldSz",
|
|
271
|
+
`sldSz missing, using default ${DEFAULT_SLIDE_WIDTH}x${DEFAULT_SLIDE_HEIGHT} EMU`
|
|
83
272
|
);
|
|
84
273
|
slideSize = { width: DEFAULT_SLIDE_WIDTH, height: DEFAULT_SLIDE_HEIGHT };
|
|
85
274
|
} else {
|
|
@@ -88,25 +277,24 @@ function parsePresentation(xml) {
|
|
|
88
277
|
height: Number(sldSz["@_cy"])
|
|
89
278
|
};
|
|
90
279
|
}
|
|
91
|
-
const sldIdLst = pres.sldIdLst
|
|
92
|
-
const
|
|
280
|
+
const sldIdLst = pres.sldIdLst;
|
|
281
|
+
const sldIdArr = sldIdLst?.sldId ?? [];
|
|
282
|
+
const slideRIds = sldIdArr.map((s) => s["@_r:id"] ?? s["@_id"]).filter((id) => {
|
|
93
283
|
if (id === void 0) {
|
|
94
|
-
|
|
95
|
-
`${WARN_PREFIX} Presentation: undefined slide relationship ID found, skipping`
|
|
96
|
-
);
|
|
284
|
+
debug("presentation.slideRId", "undefined slide relationship ID found, skipping");
|
|
97
285
|
return false;
|
|
98
286
|
}
|
|
99
287
|
return true;
|
|
100
288
|
});
|
|
101
|
-
|
|
289
|
+
const defaultTextStyle = parseListStyle(pres.defaultTextStyle);
|
|
290
|
+
return { slideSize, slideRIds, defaultTextStyle };
|
|
102
291
|
}
|
|
103
292
|
|
|
104
293
|
// src/parser/theme-parser.ts
|
|
105
|
-
var WARN_PREFIX2 = "[pptx-glimpse]";
|
|
106
294
|
function parseTheme(xml) {
|
|
107
295
|
const parsed = parseXml(xml);
|
|
108
296
|
if (!parsed.theme) {
|
|
109
|
-
|
|
297
|
+
debug("theme.missing", `missing root element "theme" in XML`);
|
|
110
298
|
return {
|
|
111
299
|
colorScheme: defaultColorScheme(),
|
|
112
300
|
fontScheme: {
|
|
@@ -119,7 +307,7 @@ function parseTheme(xml) {
|
|
|
119
307
|
}
|
|
120
308
|
const themeElements = parsed.theme.themeElements;
|
|
121
309
|
if (!themeElements) {
|
|
122
|
-
|
|
310
|
+
debug("theme.themeElements", "themeElements not found, using defaults");
|
|
123
311
|
return {
|
|
124
312
|
colorScheme: defaultColorScheme(),
|
|
125
313
|
fontScheme: {
|
|
@@ -131,10 +319,10 @@ function parseTheme(xml) {
|
|
|
131
319
|
};
|
|
132
320
|
}
|
|
133
321
|
if (!themeElements.clrScheme) {
|
|
134
|
-
|
|
322
|
+
debug("theme.colorScheme", "colorScheme not found, using defaults");
|
|
135
323
|
}
|
|
136
324
|
if (!themeElements.fontScheme) {
|
|
137
|
-
|
|
325
|
+
debug("theme.fontScheme", "fontScheme not found, using defaults");
|
|
138
326
|
}
|
|
139
327
|
const colorScheme = parseColorScheme(themeElements.clrScheme);
|
|
140
328
|
const fontScheme = parseFontScheme(themeElements.fontScheme);
|
|
@@ -159,21 +347,25 @@ function parseColorScheme(clrScheme) {
|
|
|
159
347
|
}
|
|
160
348
|
function extractColor(colorNode) {
|
|
161
349
|
if (!colorNode) return "#000000";
|
|
162
|
-
|
|
163
|
-
|
|
350
|
+
const srgbClr = colorNode.srgbClr;
|
|
351
|
+
if (srgbClr) {
|
|
352
|
+
return `#${srgbClr["@_val"]}`;
|
|
164
353
|
}
|
|
165
|
-
|
|
166
|
-
|
|
354
|
+
const sysClr = colorNode.sysClr;
|
|
355
|
+
if (sysClr) {
|
|
356
|
+
return `#${sysClr["@_lastClr"] ?? "000000"}`;
|
|
167
357
|
}
|
|
168
358
|
return "#000000";
|
|
169
359
|
}
|
|
170
360
|
function parseFontScheme(fontScheme) {
|
|
171
361
|
if (!fontScheme)
|
|
172
362
|
return { majorFont: "Calibri", minorFont: "Calibri", majorFontEa: null, minorFontEa: null };
|
|
173
|
-
const
|
|
174
|
-
const
|
|
175
|
-
const
|
|
176
|
-
const
|
|
363
|
+
const majorFontNode = fontScheme.majorFont;
|
|
364
|
+
const minorFontNode = fontScheme.minorFont;
|
|
365
|
+
const majorFont = majorFontNode?.latin?.["@_typeface"] ?? "Calibri";
|
|
366
|
+
const minorFont = minorFontNode?.latin?.["@_typeface"] ?? "Calibri";
|
|
367
|
+
const majorFontEa = majorFontNode?.ea?.["@_typeface"] ?? null;
|
|
368
|
+
const minorFontEa = minorFontNode?.ea?.["@_typeface"] ?? null;
|
|
177
369
|
return { majorFont, minorFont, majorFontEa, minorFontEa };
|
|
178
370
|
}
|
|
179
371
|
function defaultColorScheme() {
|
|
@@ -194,26 +386,26 @@ function defaultColorScheme() {
|
|
|
194
386
|
}
|
|
195
387
|
|
|
196
388
|
// src/parser/relationship-parser.ts
|
|
197
|
-
var WARN_PREFIX3 = "[pptx-glimpse]";
|
|
198
389
|
function parseRelationships(xml) {
|
|
199
390
|
const parsed = parseXml(xml);
|
|
200
391
|
const rels = /* @__PURE__ */ new Map();
|
|
201
|
-
const root = parsed;
|
|
202
|
-
if (!root
|
|
203
|
-
|
|
392
|
+
const root = parsed.Relationships;
|
|
393
|
+
if (!root) {
|
|
394
|
+
debug("relationship.missing", `missing root element "Relationships" in XML`);
|
|
204
395
|
return rels;
|
|
205
396
|
}
|
|
206
|
-
const relationships = root.
|
|
397
|
+
const relationships = root.Relationship;
|
|
207
398
|
if (!relationships) return rels;
|
|
208
399
|
for (const rel of relationships) {
|
|
209
400
|
const id = rel["@_Id"];
|
|
210
401
|
const type = rel["@_Type"];
|
|
211
402
|
const target = rel["@_Target"];
|
|
403
|
+
const targetMode = rel["@_TargetMode"];
|
|
212
404
|
if (!id || !type || !target) {
|
|
213
|
-
|
|
405
|
+
debug("relationship.attribute", "entry missing required attribute, skipping");
|
|
214
406
|
continue;
|
|
215
407
|
}
|
|
216
|
-
rels.set(id, { id, type, target });
|
|
408
|
+
rels.set(id, { id, type, target, ...targetMode && { targetMode } });
|
|
217
409
|
}
|
|
218
410
|
return rels;
|
|
219
411
|
}
|
|
@@ -241,7 +433,6 @@ function resolveRelationshipTarget(basePath, relTarget) {
|
|
|
241
433
|
}
|
|
242
434
|
|
|
243
435
|
// src/parser/fill-parser.ts
|
|
244
|
-
var WARN_PREFIX4 = "[pptx-glimpse]";
|
|
245
436
|
function parseFillFromNode(node, colorResolver, context) {
|
|
246
437
|
if (!node) return null;
|
|
247
438
|
if (node.noFill !== void 0) {
|
|
@@ -259,10 +450,17 @@ function parseFillFromNode(node, colorResolver, context) {
|
|
|
259
450
|
if (node.blipFill && context) {
|
|
260
451
|
return parseBlipFill(node.blipFill, context);
|
|
261
452
|
}
|
|
453
|
+
if (node.pattFill) {
|
|
454
|
+
return parsePatternFill(node.pattFill, colorResolver);
|
|
455
|
+
}
|
|
456
|
+
if (node.grpFill !== void 0 && context?.groupFill) {
|
|
457
|
+
return context.groupFill;
|
|
458
|
+
}
|
|
262
459
|
return null;
|
|
263
460
|
}
|
|
264
461
|
function parseBlipFill(blipFillNode, context) {
|
|
265
|
-
const
|
|
462
|
+
const blip = blipFillNode?.blip;
|
|
463
|
+
const rId = blip?.["@_r:embed"] ?? blip?.["@_embed"];
|
|
266
464
|
if (!rId) return null;
|
|
267
465
|
const rel = context.rels.get(rId);
|
|
268
466
|
if (!rel) return null;
|
|
@@ -284,27 +482,54 @@ function parseBlipFill(blipFillNode, context) {
|
|
|
284
482
|
return { type: "image", imageData, mimeType };
|
|
285
483
|
}
|
|
286
484
|
function parseGradientFill(gradNode, colorResolver) {
|
|
287
|
-
const gsLst = gradNode.gsLst
|
|
288
|
-
|
|
289
|
-
|
|
485
|
+
const gsLst = gradNode.gsLst;
|
|
486
|
+
const gsArr = gsLst?.gs;
|
|
487
|
+
if (!gsArr) {
|
|
488
|
+
debug("gradientFill.gsLst", "GradientFill: gsLst not found, skipping gradient");
|
|
290
489
|
return null;
|
|
291
490
|
}
|
|
292
491
|
const stops = [];
|
|
293
|
-
for (const gs of
|
|
492
|
+
for (const gs of gsArr) {
|
|
294
493
|
const position = Number(gs["@_pos"] ?? 0) / 1e5;
|
|
295
494
|
const color = colorResolver.resolve(gs);
|
|
296
495
|
if (color) {
|
|
297
496
|
stops.push({ position, color });
|
|
298
497
|
}
|
|
299
498
|
}
|
|
499
|
+
const pathNode = gradNode.path;
|
|
500
|
+
if (pathNode) {
|
|
501
|
+
const fillToRect = pathNode.fillToRect;
|
|
502
|
+
let centerX = 0.5;
|
|
503
|
+
let centerY = 0.5;
|
|
504
|
+
if (fillToRect) {
|
|
505
|
+
const l = Number(fillToRect["@_l"] ?? 0);
|
|
506
|
+
const t = Number(fillToRect["@_t"] ?? 0);
|
|
507
|
+
const r = Number(fillToRect["@_r"] ?? 0);
|
|
508
|
+
const b = Number(fillToRect["@_b"] ?? 0);
|
|
509
|
+
centerX = (l + (1e5 - r)) / 2 / 1e5;
|
|
510
|
+
centerY = (t + (1e5 - b)) / 2 / 1e5;
|
|
511
|
+
}
|
|
512
|
+
return { type: "gradient", stops, angle: 0, gradientType: "radial", centerX, centerY };
|
|
513
|
+
}
|
|
300
514
|
let angle = 0;
|
|
301
|
-
|
|
302
|
-
|
|
515
|
+
const lin = gradNode.lin;
|
|
516
|
+
if (lin) {
|
|
517
|
+
angle = Number(lin["@_ang"] ?? 0) / 6e4;
|
|
303
518
|
}
|
|
304
|
-
return { type: "gradient", stops, angle };
|
|
519
|
+
return { type: "gradient", stops, angle, gradientType: "linear" };
|
|
520
|
+
}
|
|
521
|
+
function parsePatternFill(pattNode, colorResolver) {
|
|
522
|
+
const preset = pattNode["@_prst"] ?? "ltDnDiag";
|
|
523
|
+
const fgColor = pattNode.fgClr ? colorResolver.resolve(pattNode.fgClr) : null;
|
|
524
|
+
const bgColor = pattNode.bgClr ? colorResolver.resolve(pattNode.bgClr) : null;
|
|
525
|
+
if (!fgColor || !bgColor) return null;
|
|
526
|
+
return { type: "pattern", preset, foregroundColor: fgColor, backgroundColor: bgColor };
|
|
305
527
|
}
|
|
306
528
|
function parseOutline(lnNode, colorResolver) {
|
|
307
529
|
if (!lnNode) return null;
|
|
530
|
+
if (lnNode.pattFill) {
|
|
531
|
+
warn("ln.pattFill", "pattern line fill not implemented");
|
|
532
|
+
}
|
|
308
533
|
const width = Number(lnNode["@_w"] ?? 12700);
|
|
309
534
|
let fill = null;
|
|
310
535
|
if (lnNode.solidFill) {
|
|
@@ -312,12 +537,61 @@ function parseOutline(lnNode, colorResolver) {
|
|
|
312
537
|
if (color) {
|
|
313
538
|
fill = { type: "solid", color };
|
|
314
539
|
}
|
|
540
|
+
} else if (lnNode.gradFill) {
|
|
541
|
+
const gradFill = parseGradientFill(lnNode.gradFill, colorResolver);
|
|
542
|
+
if (gradFill && gradFill.type === "gradient") {
|
|
543
|
+
fill = gradFill;
|
|
544
|
+
}
|
|
315
545
|
}
|
|
316
546
|
if (lnNode.noFill !== void 0) {
|
|
317
547
|
return null;
|
|
318
548
|
}
|
|
319
|
-
const
|
|
320
|
-
|
|
549
|
+
const prstDash = lnNode.prstDash;
|
|
550
|
+
const dashStyle = prstDash?.["@_val"] ?? "solid";
|
|
551
|
+
const customDash = parseCustomDash(lnNode);
|
|
552
|
+
const lineCap = parseLineCap(lnNode["@_cap"]);
|
|
553
|
+
const lineJoin = parseLineJoin(lnNode);
|
|
554
|
+
const headEnd = parseArrowEndpoint(lnNode.headEnd);
|
|
555
|
+
const tailEnd = parseArrowEndpoint(lnNode.tailEnd);
|
|
556
|
+
return { width, fill, dashStyle, customDash, lineCap, lineJoin, headEnd, tailEnd };
|
|
557
|
+
}
|
|
558
|
+
function parseCustomDash(lnNode) {
|
|
559
|
+
const custDash = lnNode.custDash;
|
|
560
|
+
if (!custDash) return void 0;
|
|
561
|
+
const dsArr = custDash.ds;
|
|
562
|
+
if (!dsArr || dsArr.length === 0) return void 0;
|
|
563
|
+
const result = [];
|
|
564
|
+
for (const ds of dsArr) {
|
|
565
|
+
const d = Number(ds["@_d"] ?? 1e5) / 1e5;
|
|
566
|
+
const sp = Number(ds["@_sp"] ?? 1e5) / 1e5;
|
|
567
|
+
result.push(d, sp);
|
|
568
|
+
}
|
|
569
|
+
return result;
|
|
570
|
+
}
|
|
571
|
+
var LINE_CAP_MAP = {
|
|
572
|
+
flat: "butt",
|
|
573
|
+
sq: "square",
|
|
574
|
+
rnd: "round"
|
|
575
|
+
};
|
|
576
|
+
function parseLineCap(cap) {
|
|
577
|
+
if (!cap) return void 0;
|
|
578
|
+
return LINE_CAP_MAP[cap];
|
|
579
|
+
}
|
|
580
|
+
function parseLineJoin(lnNode) {
|
|
581
|
+
if (lnNode.round !== void 0) return "round";
|
|
582
|
+
if (lnNode.bevel !== void 0) return "bevel";
|
|
583
|
+
if (lnNode.miter !== void 0) return "miter";
|
|
584
|
+
return void 0;
|
|
585
|
+
}
|
|
586
|
+
function parseArrowEndpoint(node) {
|
|
587
|
+
if (!node) return null;
|
|
588
|
+
const type = node["@_type"] ?? "none";
|
|
589
|
+
if (type === "none") return null;
|
|
590
|
+
return {
|
|
591
|
+
type,
|
|
592
|
+
width: node["@_w"] ?? "med",
|
|
593
|
+
length: node["@_len"] ?? "med"
|
|
594
|
+
};
|
|
321
595
|
}
|
|
322
596
|
|
|
323
597
|
// src/parser/effect-parser.ts
|
|
@@ -381,8 +655,12 @@ var CHART_TYPE_MAP = [
|
|
|
381
655
|
["line3DChart", "line"],
|
|
382
656
|
["pieChart", "pie"],
|
|
383
657
|
["pie3DChart", "pie"],
|
|
384
|
-
["doughnutChart", "
|
|
385
|
-
["scatterChart", "scatter"]
|
|
658
|
+
["doughnutChart", "doughnut"],
|
|
659
|
+
["scatterChart", "scatter"],
|
|
660
|
+
["bubbleChart", "bubble"],
|
|
661
|
+
["areaChart", "area"],
|
|
662
|
+
["area3DChart", "area"],
|
|
663
|
+
["radarChart", "radar"]
|
|
386
664
|
];
|
|
387
665
|
function parseChart(chartXml, colorResolver) {
|
|
388
666
|
const parsed = parseXml(chartXml);
|
|
@@ -393,10 +671,7 @@ function parseChart(chartXml, colorResolver) {
|
|
|
393
671
|
const plotArea = chart.plotArea;
|
|
394
672
|
if (!plotArea) return null;
|
|
395
673
|
const title = parseChartTitle(chart.title);
|
|
396
|
-
const { chartType, series, categories, barDirection } = parseChartTypeAndData(
|
|
397
|
-
plotArea,
|
|
398
|
-
colorResolver
|
|
399
|
-
);
|
|
674
|
+
const { chartType, series, categories, barDirection, holeSize, radarStyle } = parseChartTypeAndData(plotArea, colorResolver);
|
|
400
675
|
if (!chartType) return null;
|
|
401
676
|
const legend = parseLegend(chart.legend);
|
|
402
677
|
return {
|
|
@@ -405,6 +680,8 @@ function parseChart(chartXml, colorResolver) {
|
|
|
405
680
|
series,
|
|
406
681
|
categories,
|
|
407
682
|
...barDirection !== void 0 && { barDirection },
|
|
683
|
+
...holeSize !== void 0 && { holeSize },
|
|
684
|
+
...radarStyle !== void 0 && { radarStyle },
|
|
408
685
|
legend
|
|
409
686
|
};
|
|
410
687
|
}
|
|
@@ -414,30 +691,46 @@ function parseChartTypeAndData(plotArea, colorResolver) {
|
|
|
414
691
|
if (!chartNode) continue;
|
|
415
692
|
const serList = chartNode.ser ?? [];
|
|
416
693
|
const series = serList.map(
|
|
417
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
418
694
|
(ser, index) => parseSeries(ser, chartType, index, colorResolver)
|
|
419
695
|
);
|
|
420
696
|
const categories = extractCategories(serList);
|
|
421
|
-
const
|
|
422
|
-
|
|
697
|
+
const barDirNode = chartNode.barDir;
|
|
698
|
+
const barDirection = chartType === "bar" ? barDirNode?.["@_val"] ?? "col" : void 0;
|
|
699
|
+
const holeSizeNode = chartNode.holeSize;
|
|
700
|
+
const holeSize = chartType === "doughnut" ? Number(holeSizeNode?.["@_val"] ?? 50) : void 0;
|
|
701
|
+
const radarStyleNode = chartNode.radarStyle;
|
|
702
|
+
const radarStyle = chartType === "radar" ? radarStyleNode?.["@_val"] ?? "standard" : void 0;
|
|
703
|
+
return { chartType, series, categories, barDirection, holeSize, radarStyle };
|
|
704
|
+
}
|
|
705
|
+
const knownTags = new Set(CHART_TYPE_MAP.map(([tag]) => tag));
|
|
706
|
+
const chartTags = ["surfaceChart", "surface3DChart", "stockChart", "ofPieChart"];
|
|
707
|
+
for (const tag of chartTags) {
|
|
708
|
+
if (!knownTags.has(tag) && plotArea[tag]) {
|
|
709
|
+
warn(`chart.${tag}`, `chart type "${tag}" not implemented`);
|
|
710
|
+
break;
|
|
711
|
+
}
|
|
423
712
|
}
|
|
424
713
|
return { chartType: null, series: [], categories: [] };
|
|
425
714
|
}
|
|
426
715
|
function parseSeries(ser, chartType, seriesIndex, colorResolver) {
|
|
427
716
|
const name = parseSeriesName(ser.tx);
|
|
428
|
-
const
|
|
429
|
-
const
|
|
717
|
+
const usesXY = chartType === "scatter" || chartType === "bubble";
|
|
718
|
+
const values = parseNumericData(usesXY ? ser.yVal : ser.val);
|
|
719
|
+
const xValues = usesXY ? parseNumericData(ser.xVal) : void 0;
|
|
720
|
+
const bubbleSizes = chartType === "bubble" ? parseNumericData(ser.bubbleSize) : void 0;
|
|
430
721
|
const color = resolveSeriesColor(ser.spPr, seriesIndex, colorResolver);
|
|
431
722
|
return {
|
|
432
723
|
name,
|
|
433
724
|
values,
|
|
434
725
|
...xValues !== void 0 && { xValues },
|
|
726
|
+
...bubbleSizes !== void 0 && { bubbleSizes },
|
|
435
727
|
color
|
|
436
728
|
};
|
|
437
729
|
}
|
|
438
730
|
function parseSeriesName(tx) {
|
|
439
731
|
if (!tx) return null;
|
|
440
|
-
const
|
|
732
|
+
const strRef = tx.strRef;
|
|
733
|
+
const strCache = strRef?.strCache;
|
|
441
734
|
if (strCache?.pt) {
|
|
442
735
|
const pts = strCache.pt;
|
|
443
736
|
return pts[0]?.v ?? null;
|
|
@@ -447,7 +740,8 @@ function parseSeriesName(tx) {
|
|
|
447
740
|
}
|
|
448
741
|
function parseNumericData(valNode) {
|
|
449
742
|
if (!valNode) return [];
|
|
450
|
-
const
|
|
743
|
+
const numRef = valNode.numRef;
|
|
744
|
+
const numCache = numRef?.numCache;
|
|
451
745
|
if (!numCache?.pt) return [];
|
|
452
746
|
const pts = numCache.pt;
|
|
453
747
|
return pts.slice().sort((a, b) => Number(a["@_idx"]) - Number(b["@_idx"])).map((pt) => Number(pt.v ?? 0));
|
|
@@ -456,7 +750,9 @@ function extractCategories(serList) {
|
|
|
456
750
|
for (const ser of serList) {
|
|
457
751
|
const cat = ser.cat;
|
|
458
752
|
if (!cat) continue;
|
|
459
|
-
const
|
|
753
|
+
const strRef = cat.strRef;
|
|
754
|
+
const numRef = cat.numRef;
|
|
755
|
+
const strCache = strRef?.strCache ?? numRef?.numCache;
|
|
460
756
|
if (strCache?.pt) {
|
|
461
757
|
return strCache.pt.slice().sort((a, b) => Number(a["@_idx"]) - Number(b["@_idx"])).map((pt) => String(pt.v ?? ""));
|
|
462
758
|
}
|
|
@@ -474,7 +770,8 @@ function resolveSeriesColor(spPr, seriesIndex, colorResolver) {
|
|
|
474
770
|
}
|
|
475
771
|
function parseChartTitle(titleNode) {
|
|
476
772
|
if (!titleNode) return null;
|
|
477
|
-
const
|
|
773
|
+
const tx = titleNode.tx;
|
|
774
|
+
const rich = tx?.rich;
|
|
478
775
|
if (!rich?.p) return null;
|
|
479
776
|
const pList = Array.isArray(rich.p) ? rich.p : [rich.p];
|
|
480
777
|
const texts = [];
|
|
@@ -483,23 +780,303 @@ function parseChartTitle(titleNode) {
|
|
|
483
780
|
for (const r of rList) {
|
|
484
781
|
const t = r.t;
|
|
485
782
|
if (typeof t === "string") texts.push(t);
|
|
486
|
-
else if (t
|
|
783
|
+
else if (t && t["#text"]) texts.push(String(t["#text"]));
|
|
487
784
|
}
|
|
488
785
|
}
|
|
489
786
|
return texts.length > 0 ? texts.join("") : null;
|
|
490
787
|
}
|
|
491
788
|
function parseLegend(legendNode) {
|
|
492
789
|
if (!legendNode) return null;
|
|
493
|
-
const
|
|
790
|
+
const legendPos = legendNode.legendPos;
|
|
791
|
+
const pos = legendPos?.["@_val"] ?? "b";
|
|
494
792
|
return { position: pos };
|
|
495
793
|
}
|
|
496
794
|
|
|
795
|
+
// src/parser/geometry-formula.ts
|
|
796
|
+
function createBuiltinVariables(w, h) {
|
|
797
|
+
return {
|
|
798
|
+
w,
|
|
799
|
+
h,
|
|
800
|
+
l: 0,
|
|
801
|
+
t: 0,
|
|
802
|
+
r: w,
|
|
803
|
+
b: h,
|
|
804
|
+
wd2: w / 2,
|
|
805
|
+
hd2: h / 2,
|
|
806
|
+
wd4: w / 4,
|
|
807
|
+
hd4: h / 4,
|
|
808
|
+
wd5: w / 5,
|
|
809
|
+
hd5: h / 5,
|
|
810
|
+
wd6: w / 6,
|
|
811
|
+
hd6: h / 6,
|
|
812
|
+
wd8: w / 8,
|
|
813
|
+
hd8: h / 8,
|
|
814
|
+
wd10: w / 10,
|
|
815
|
+
hd10: h / 10,
|
|
816
|
+
wd12: w / 12,
|
|
817
|
+
hd12: h / 12,
|
|
818
|
+
wd32: w / 32,
|
|
819
|
+
hd32: h / 32,
|
|
820
|
+
ss: Math.min(w, h),
|
|
821
|
+
ls: Math.max(w, h),
|
|
822
|
+
ssd2: Math.min(w, h) / 2,
|
|
823
|
+
ssd4: Math.min(w, h) / 4,
|
|
824
|
+
ssd6: Math.min(w, h) / 6,
|
|
825
|
+
ssd8: Math.min(w, h) / 8,
|
|
826
|
+
ssd16: Math.min(w, h) / 16,
|
|
827
|
+
ssd32: Math.min(w, h) / 32,
|
|
828
|
+
cd2: 108e5,
|
|
829
|
+
cd4: 54e5,
|
|
830
|
+
cd8: 27e5,
|
|
831
|
+
"3cd4": 162e5,
|
|
832
|
+
"3cd8": 81e5,
|
|
833
|
+
"5cd8": 135e5,
|
|
834
|
+
"7cd8": 189e5
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
function evaluateFormula(fmla, vars) {
|
|
838
|
+
const tokens = fmla.trim().split(/\s+/);
|
|
839
|
+
const op = tokens[0];
|
|
840
|
+
const resolve = (token) => {
|
|
841
|
+
if (token === void 0) return 0;
|
|
842
|
+
const num = Number(token);
|
|
843
|
+
return Number.isNaN(num) ? vars[token] ?? 0 : num;
|
|
844
|
+
};
|
|
845
|
+
switch (op) {
|
|
846
|
+
case "val":
|
|
847
|
+
return resolve(tokens[1]);
|
|
848
|
+
case "+-":
|
|
849
|
+
return resolve(tokens[1]) + resolve(tokens[2]) - resolve(tokens[3]);
|
|
850
|
+
case "*/":
|
|
851
|
+
return Math.round(resolve(tokens[1]) * resolve(tokens[2]) / (resolve(tokens[3]) || 1));
|
|
852
|
+
case "+/":
|
|
853
|
+
return Math.round((resolve(tokens[1]) + resolve(tokens[2])) / (resolve(tokens[3]) || 1));
|
|
854
|
+
case "sin": {
|
|
855
|
+
const a = resolve(tokens[1]);
|
|
856
|
+
const b = resolve(tokens[2]);
|
|
857
|
+
return Math.round(a * Math.sin(b / 6e4 * (Math.PI / 180)));
|
|
858
|
+
}
|
|
859
|
+
case "cos": {
|
|
860
|
+
const a = resolve(tokens[1]);
|
|
861
|
+
const b = resolve(tokens[2]);
|
|
862
|
+
return Math.round(a * Math.cos(b / 6e4 * (Math.PI / 180)));
|
|
863
|
+
}
|
|
864
|
+
case "tan": {
|
|
865
|
+
const a = resolve(tokens[1]);
|
|
866
|
+
const b = resolve(tokens[2]);
|
|
867
|
+
const tanVal = Math.tan(b / 6e4 * (Math.PI / 180));
|
|
868
|
+
return Math.round(a * tanVal);
|
|
869
|
+
}
|
|
870
|
+
case "at2": {
|
|
871
|
+
const x = resolve(tokens[1]);
|
|
872
|
+
const y = resolve(tokens[2]);
|
|
873
|
+
return Math.round(Math.atan2(y, x) * (180 / Math.PI) * 6e4);
|
|
874
|
+
}
|
|
875
|
+
case "sqrt":
|
|
876
|
+
return Math.round(Math.sqrt(resolve(tokens[1])));
|
|
877
|
+
case "min":
|
|
878
|
+
return Math.min(resolve(tokens[1]), resolve(tokens[2]));
|
|
879
|
+
case "max":
|
|
880
|
+
return Math.max(resolve(tokens[1]), resolve(tokens[2]));
|
|
881
|
+
case "abs":
|
|
882
|
+
return Math.abs(resolve(tokens[1]));
|
|
883
|
+
case "pin": {
|
|
884
|
+
const lo = resolve(tokens[1]);
|
|
885
|
+
const val = resolve(tokens[2]);
|
|
886
|
+
const hi = resolve(tokens[3]);
|
|
887
|
+
return Math.max(lo, Math.min(hi, val));
|
|
888
|
+
}
|
|
889
|
+
case "mod": {
|
|
890
|
+
const a = resolve(tokens[1]);
|
|
891
|
+
const b = resolve(tokens[2]);
|
|
892
|
+
const c = resolve(tokens[3]);
|
|
893
|
+
return Math.round(Math.sqrt(a * a + b * b + c * c));
|
|
894
|
+
}
|
|
895
|
+
case "cat2": {
|
|
896
|
+
const a = resolve(tokens[1]);
|
|
897
|
+
const b = resolve(tokens[2]);
|
|
898
|
+
const c = resolve(tokens[3]);
|
|
899
|
+
return Math.round(a * Math.cos(Math.atan2(c, b)));
|
|
900
|
+
}
|
|
901
|
+
case "sat2": {
|
|
902
|
+
const a = resolve(tokens[1]);
|
|
903
|
+
const b = resolve(tokens[2]);
|
|
904
|
+
const c = resolve(tokens[3]);
|
|
905
|
+
return Math.round(a * Math.sin(Math.atan2(c, b)));
|
|
906
|
+
}
|
|
907
|
+
case "?:":
|
|
908
|
+
return resolve(tokens[1]) > 0 ? resolve(tokens[2]) : resolve(tokens[3]);
|
|
909
|
+
default:
|
|
910
|
+
return 0;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
function evaluateGuides(avLst, gdLst, w, h) {
|
|
914
|
+
const vars = createBuiltinVariables(w, h);
|
|
915
|
+
for (const gd of avLst) {
|
|
916
|
+
vars[gd.name] = evaluateFormula(gd.fmla, vars);
|
|
917
|
+
}
|
|
918
|
+
for (const gd of gdLst) {
|
|
919
|
+
vars[gd.name] = evaluateFormula(gd.fmla, vars);
|
|
920
|
+
}
|
|
921
|
+
return vars;
|
|
922
|
+
}
|
|
923
|
+
function resolveValue(val, vars) {
|
|
924
|
+
if (typeof val === "number") return val;
|
|
925
|
+
const num = Number(val);
|
|
926
|
+
if (!Number.isNaN(num)) return num;
|
|
927
|
+
return vars[val] ?? 0;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
// src/parser/custom-geometry-parser.ts
|
|
931
|
+
function parseCustomGeometry(custGeom) {
|
|
932
|
+
const pathLst = custGeom.pathLst;
|
|
933
|
+
if (!pathLst?.path) return null;
|
|
934
|
+
const avLst = custGeom.avLst;
|
|
935
|
+
const gdLst = custGeom.gdLst;
|
|
936
|
+
const avGd = parseGuideList(avLst?.gd);
|
|
937
|
+
const gdGd = parseGuideList(gdLst?.gd);
|
|
938
|
+
const paths = Array.isArray(pathLst.path) ? pathLst.path : [pathLst.path];
|
|
939
|
+
const result = [];
|
|
940
|
+
for (const path of paths) {
|
|
941
|
+
const w = Number(path["@_w"] ?? 0);
|
|
942
|
+
const h = Number(path["@_h"] ?? 0);
|
|
943
|
+
if (w === 0 && h === 0) continue;
|
|
944
|
+
const vars = evaluateGuides(avGd, gdGd, w, h);
|
|
945
|
+
const commands = buildPathCommands(path, vars);
|
|
946
|
+
if (!commands) continue;
|
|
947
|
+
result.push({ width: w, height: h, commands });
|
|
948
|
+
}
|
|
949
|
+
return result.length > 0 ? result : null;
|
|
950
|
+
}
|
|
951
|
+
function parseGuideList(gd) {
|
|
952
|
+
if (!gd) return [];
|
|
953
|
+
const list = Array.isArray(gd) ? gd : [gd];
|
|
954
|
+
return list.map((g) => ({
|
|
955
|
+
name: g["@_name"] ?? "",
|
|
956
|
+
fmla: g["@_fmla"] ?? ""
|
|
957
|
+
})).filter((g) => g.name && g.fmla);
|
|
958
|
+
}
|
|
959
|
+
function buildPathCommands(path, vars) {
|
|
960
|
+
const parts = [];
|
|
961
|
+
let curX = 0;
|
|
962
|
+
let curY = 0;
|
|
963
|
+
let startX = 0;
|
|
964
|
+
let startY = 0;
|
|
965
|
+
for (const key of Object.keys(path)) {
|
|
966
|
+
if (key.startsWith("@_")) continue;
|
|
967
|
+
const value = path[key];
|
|
968
|
+
const items = Array.isArray(value) ? value : [value];
|
|
969
|
+
for (const item of items) {
|
|
970
|
+
switch (key) {
|
|
971
|
+
case "moveTo": {
|
|
972
|
+
const pt = resolveFirstPoint(item.pt, vars);
|
|
973
|
+
if (pt) {
|
|
974
|
+
parts.push(`M ${pt.x} ${pt.y}`);
|
|
975
|
+
curX = pt.x;
|
|
976
|
+
curY = pt.y;
|
|
977
|
+
startX = pt.x;
|
|
978
|
+
startY = pt.y;
|
|
979
|
+
}
|
|
980
|
+
break;
|
|
981
|
+
}
|
|
982
|
+
case "lnTo": {
|
|
983
|
+
const pt = resolveFirstPoint(item.pt, vars);
|
|
984
|
+
if (pt) {
|
|
985
|
+
parts.push(`L ${pt.x} ${pt.y}`);
|
|
986
|
+
curX = pt.x;
|
|
987
|
+
curY = pt.y;
|
|
988
|
+
}
|
|
989
|
+
break;
|
|
990
|
+
}
|
|
991
|
+
case "cubicBezTo": {
|
|
992
|
+
const pts = resolvePoints(item.pt, vars);
|
|
993
|
+
if (pts.length >= 3) {
|
|
994
|
+
parts.push(`C ${pts.map((p) => `${p.x} ${p.y}`).join(", ")}`);
|
|
995
|
+
curX = pts[pts.length - 1].x;
|
|
996
|
+
curY = pts[pts.length - 1].y;
|
|
997
|
+
}
|
|
998
|
+
break;
|
|
999
|
+
}
|
|
1000
|
+
case "quadBezTo": {
|
|
1001
|
+
const pts = resolvePoints(item.pt, vars);
|
|
1002
|
+
if (pts.length >= 2) {
|
|
1003
|
+
parts.push(`Q ${pts.map((p) => `${p.x} ${p.y}`).join(", ")}`);
|
|
1004
|
+
curX = pts[pts.length - 1].x;
|
|
1005
|
+
curY = pts[pts.length - 1].y;
|
|
1006
|
+
}
|
|
1007
|
+
break;
|
|
1008
|
+
}
|
|
1009
|
+
case "arcTo": {
|
|
1010
|
+
const arcResult = convertArcTo(item, curX, curY, vars);
|
|
1011
|
+
if (arcResult) {
|
|
1012
|
+
parts.push(arcResult.svg);
|
|
1013
|
+
curX = arcResult.endX;
|
|
1014
|
+
curY = arcResult.endY;
|
|
1015
|
+
}
|
|
1016
|
+
break;
|
|
1017
|
+
}
|
|
1018
|
+
case "close": {
|
|
1019
|
+
parts.push("Z");
|
|
1020
|
+
curX = startX;
|
|
1021
|
+
curY = startY;
|
|
1022
|
+
break;
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
return parts.length > 0 ? parts.join(" ") : null;
|
|
1028
|
+
}
|
|
1029
|
+
function resolveFirstPoint(pt, vars) {
|
|
1030
|
+
if (!pt) return null;
|
|
1031
|
+
const p = Array.isArray(pt) ? pt[0] : pt;
|
|
1032
|
+
if (!p) return null;
|
|
1033
|
+
return {
|
|
1034
|
+
x: resolveValue(p["@_x"], vars),
|
|
1035
|
+
y: resolveValue(p["@_y"], vars)
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
function resolvePoints(pt, vars) {
|
|
1039
|
+
if (!pt) return [];
|
|
1040
|
+
const pts = Array.isArray(pt) ? pt : [pt];
|
|
1041
|
+
return pts.map((p) => ({
|
|
1042
|
+
x: resolveValue(p["@_x"], vars),
|
|
1043
|
+
y: resolveValue(p["@_y"], vars)
|
|
1044
|
+
}));
|
|
1045
|
+
}
|
|
1046
|
+
function convertArcTo(arc, curX, curY, vars) {
|
|
1047
|
+
const wR = resolveValue(arc["@_wR"] ?? "0", vars);
|
|
1048
|
+
const hR = resolveValue(arc["@_hR"] ?? "0", vars);
|
|
1049
|
+
const stAng = resolveValue(arc["@_stAng"] ?? "0", vars);
|
|
1050
|
+
const swAng = resolveValue(arc["@_swAng"] ?? "0", vars);
|
|
1051
|
+
if (wR === 0 && hR === 0) return null;
|
|
1052
|
+
if (swAng === 0) return null;
|
|
1053
|
+
const stAngDeg = stAng / 6e4;
|
|
1054
|
+
const swAngDeg = swAng / 6e4;
|
|
1055
|
+
const stAngRad = stAngDeg * Math.PI / 180;
|
|
1056
|
+
const endAngRad = (stAngDeg + swAngDeg) * Math.PI / 180;
|
|
1057
|
+
const cx = curX - wR * Math.cos(stAngRad);
|
|
1058
|
+
const cy = curY - hR * Math.sin(stAngRad);
|
|
1059
|
+
const endX = cx + wR * Math.cos(endAngRad);
|
|
1060
|
+
const endY = cy + hR * Math.sin(endAngRad);
|
|
1061
|
+
const largeArcFlag = Math.abs(swAngDeg) > 180 ? 1 : 0;
|
|
1062
|
+
const sweepFlag = swAngDeg > 0 ? 1 : 0;
|
|
1063
|
+
const rx = Math.round(wR * 1e3) / 1e3;
|
|
1064
|
+
const ry = Math.round(hR * 1e3) / 1e3;
|
|
1065
|
+
const ex = Math.round(endX * 1e3) / 1e3;
|
|
1066
|
+
const ey = Math.round(endY * 1e3) / 1e3;
|
|
1067
|
+
return {
|
|
1068
|
+
svg: `A ${rx} ${ry} 0 ${largeArcFlag} ${sweepFlag} ${ex} ${ey}`,
|
|
1069
|
+
endX,
|
|
1070
|
+
endY
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
|
|
497
1074
|
// src/parser/table-parser.ts
|
|
498
|
-
function parseTable(tblNode, colorResolver) {
|
|
1075
|
+
function parseTable(tblNode, colorResolver, fontScheme) {
|
|
499
1076
|
if (!tblNode) return null;
|
|
500
1077
|
const columns = parseColumns(tblNode.tblGrid);
|
|
501
1078
|
if (columns.length === 0) return null;
|
|
502
|
-
const rows = parseRows(tblNode.tr, colorResolver);
|
|
1079
|
+
const rows = parseRows(tblNode.tr, colorResolver, fontScheme);
|
|
503
1080
|
return { rows, columns };
|
|
504
1081
|
}
|
|
505
1082
|
function parseColumns(tblGrid) {
|
|
@@ -509,21 +1086,23 @@ function parseColumns(tblGrid) {
|
|
|
509
1086
|
width: Number(col["@_w"] ?? 0)
|
|
510
1087
|
}));
|
|
511
1088
|
}
|
|
512
|
-
function parseRows(trList, colorResolver) {
|
|
1089
|
+
function parseRows(trList, colorResolver, fontScheme) {
|
|
513
1090
|
if (!trList) return [];
|
|
1091
|
+
const trArr = Array.isArray(trList) ? trList : [trList];
|
|
514
1092
|
const rows = [];
|
|
515
|
-
for (const tr of
|
|
1093
|
+
for (const tr of trArr) {
|
|
516
1094
|
const height = Number(tr["@_h"] ?? 0);
|
|
517
|
-
const cells = parseCells(tr.tc, colorResolver);
|
|
1095
|
+
const cells = parseCells(tr.tc, colorResolver, fontScheme);
|
|
518
1096
|
rows.push({ height, cells });
|
|
519
1097
|
}
|
|
520
1098
|
return rows;
|
|
521
1099
|
}
|
|
522
|
-
function parseCells(tcList, colorResolver) {
|
|
1100
|
+
function parseCells(tcList, colorResolver, fontScheme) {
|
|
523
1101
|
if (!tcList) return [];
|
|
1102
|
+
const tcArr = Array.isArray(tcList) ? tcList : [tcList];
|
|
524
1103
|
const cells = [];
|
|
525
|
-
for (const tc of
|
|
526
|
-
const textBody = parseTextBody(tc.txBody, colorResolver);
|
|
1104
|
+
for (const tc of tcArr) {
|
|
1105
|
+
const textBody = parseTextBody(tc.txBody, colorResolver, void 0, fontScheme);
|
|
527
1106
|
const tcPr = tc.tcPr;
|
|
528
1107
|
const fill = tcPr ? parseFillFromNode(tcPr, colorResolver) : null;
|
|
529
1108
|
const borders = tcPr ? parseCellBorders(tcPr, colorResolver) : null;
|
|
@@ -544,41 +1123,46 @@ function parseCellBorders(tcPr, colorResolver) {
|
|
|
544
1123
|
return { top, bottom, left, right };
|
|
545
1124
|
}
|
|
546
1125
|
|
|
547
|
-
// src/utils/constants.ts
|
|
548
|
-
var EMU_PER_INCH = 914400;
|
|
549
|
-
var DEFAULT_DPI = 96;
|
|
550
|
-
var DEFAULT_OUTPUT_WIDTH = 960;
|
|
551
|
-
|
|
552
|
-
// src/utils/emu.ts
|
|
553
|
-
function emuToPixels(emu, dpi = DEFAULT_DPI) {
|
|
554
|
-
return emu / EMU_PER_INCH * dpi;
|
|
555
|
-
}
|
|
556
|
-
function hundredthPointToPoint(value) {
|
|
557
|
-
return value / 100;
|
|
558
|
-
}
|
|
559
|
-
|
|
560
1126
|
// src/parser/slide-parser.ts
|
|
561
|
-
var
|
|
562
|
-
function
|
|
1127
|
+
var SHAPE_TAGS = /* @__PURE__ */ new Set(["sp", "pic", "cxnSp", "grpSp", "graphicFrame"]);
|
|
1128
|
+
function navigateOrdered(ordered, path) {
|
|
1129
|
+
let current = ordered;
|
|
1130
|
+
for (const key of path) {
|
|
1131
|
+
const entry = current.find((item) => key in item);
|
|
1132
|
+
if (!entry) return null;
|
|
1133
|
+
current = entry[key];
|
|
1134
|
+
if (!Array.isArray(current)) return null;
|
|
1135
|
+
}
|
|
1136
|
+
return current;
|
|
1137
|
+
}
|
|
1138
|
+
function parseSlide(slideXml, slidePath, slideNumber, archive, colorResolver, fontScheme) {
|
|
563
1139
|
const parsed = parseXml(slideXml);
|
|
564
1140
|
const sld = parsed.sld;
|
|
565
1141
|
if (!sld) {
|
|
566
|
-
|
|
1142
|
+
debug("slide.missing", `missing root element "sld" in XML`, `Slide ${slideNumber}`);
|
|
567
1143
|
}
|
|
568
1144
|
const relsPath = slidePath.replace("ppt/slides/", "ppt/slides/_rels/") + ".rels";
|
|
569
1145
|
const relsXml = archive.files.get(relsPath);
|
|
570
1146
|
const rels = relsXml ? parseRelationships(relsXml) : /* @__PURE__ */ new Map();
|
|
571
1147
|
const fillContext = { rels, archive, basePath: slidePath };
|
|
572
|
-
const
|
|
1148
|
+
const cSld = sld?.cSld ?? void 0;
|
|
1149
|
+
const background = parseBackground(cSld?.bg, colorResolver, fillContext);
|
|
1150
|
+
const orderedParsed = parseXmlOrdered(slideXml);
|
|
1151
|
+
const orderedSpTree = navigateOrdered(orderedParsed, ["sld", "cSld", "spTree"]);
|
|
573
1152
|
const elements = parseShapeTree(
|
|
574
|
-
|
|
1153
|
+
cSld?.spTree,
|
|
575
1154
|
rels,
|
|
576
1155
|
slidePath,
|
|
577
1156
|
archive,
|
|
578
1157
|
colorResolver,
|
|
579
|
-
`Slide ${slideNumber}
|
|
1158
|
+
`Slide ${slideNumber}`,
|
|
1159
|
+
fillContext,
|
|
1160
|
+
fontScheme,
|
|
1161
|
+
orderedSpTree
|
|
580
1162
|
);
|
|
581
|
-
|
|
1163
|
+
const showMasterSpAttr = sld?.["@_showMasterSp"];
|
|
1164
|
+
const showMasterSp = showMasterSpAttr !== "0" && showMasterSpAttr !== "false";
|
|
1165
|
+
return { slideNumber, background, elements, showMasterSp };
|
|
582
1166
|
}
|
|
583
1167
|
function parseBackground(bgNode, colorResolver, context) {
|
|
584
1168
|
if (!bgNode) return null;
|
|
@@ -587,17 +1171,51 @@ function parseBackground(bgNode, colorResolver, context) {
|
|
|
587
1171
|
const fill = parseFillFromNode(bgPr, colorResolver, context);
|
|
588
1172
|
return { fill };
|
|
589
1173
|
}
|
|
590
|
-
function
|
|
1174
|
+
function mergeChildElements(spTree, source) {
|
|
1175
|
+
const tags = ["sp", "pic", "cxnSp", "grpSp", "graphicFrame"];
|
|
1176
|
+
for (const tag of tags) {
|
|
1177
|
+
const items = source[tag];
|
|
1178
|
+
if (!items) continue;
|
|
1179
|
+
if (!spTree[tag]) {
|
|
1180
|
+
spTree[tag] = [];
|
|
1181
|
+
}
|
|
1182
|
+
const arr = Array.isArray(items) ? items : [items];
|
|
1183
|
+
for (const item of arr) {
|
|
1184
|
+
spTree[tag].push(item);
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
function parseShapeTree(spTree, rels, slidePath, archive, colorResolver, context, fillContext, fontScheme, orderedChildren) {
|
|
591
1189
|
if (!spTree) return [];
|
|
1190
|
+
if (orderedChildren) {
|
|
1191
|
+
return parseShapeTreeOrdered(
|
|
1192
|
+
spTree,
|
|
1193
|
+
orderedChildren,
|
|
1194
|
+
rels,
|
|
1195
|
+
slidePath,
|
|
1196
|
+
archive,
|
|
1197
|
+
colorResolver,
|
|
1198
|
+
context,
|
|
1199
|
+
fillContext,
|
|
1200
|
+
fontScheme
|
|
1201
|
+
);
|
|
1202
|
+
}
|
|
1203
|
+
const alternateContents = spTree.AlternateContent ?? [];
|
|
1204
|
+
for (const ac of alternateContents) {
|
|
1205
|
+
const choices = Array.isArray(ac.Choice) ? ac.Choice : ac.Choice ? [ac.Choice] : [];
|
|
1206
|
+
for (const choice of choices) {
|
|
1207
|
+
mergeChildElements(spTree, choice);
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
592
1210
|
const ctx = context ?? slidePath;
|
|
593
1211
|
const elements = [];
|
|
594
1212
|
const shapes = spTree.sp ?? [];
|
|
595
1213
|
for (const sp of shapes) {
|
|
596
|
-
const shape = parseShape(sp, colorResolver);
|
|
1214
|
+
const shape = parseShape(sp, colorResolver, rels, fillContext, fontScheme);
|
|
597
1215
|
if (shape) {
|
|
598
1216
|
elements.push(shape);
|
|
599
1217
|
} else {
|
|
600
|
-
|
|
1218
|
+
debug("shape.skipped", "shape skipped (parse returned null)", ctx);
|
|
601
1219
|
}
|
|
602
1220
|
}
|
|
603
1221
|
const pics = spTree.pic ?? [];
|
|
@@ -606,7 +1224,7 @@ function parseShapeTree(spTree, rels, slidePath, archive, colorResolver, context
|
|
|
606
1224
|
if (img) {
|
|
607
1225
|
elements.push(img);
|
|
608
1226
|
} else {
|
|
609
|
-
|
|
1227
|
+
debug("image.skipped", "image skipped (parse returned null)", ctx);
|
|
610
1228
|
}
|
|
611
1229
|
}
|
|
612
1230
|
const cxnSps = spTree.cxnSp ?? [];
|
|
@@ -615,40 +1233,201 @@ function parseShapeTree(spTree, rels, slidePath, archive, colorResolver, context
|
|
|
615
1233
|
if (connector) {
|
|
616
1234
|
elements.push(connector);
|
|
617
1235
|
} else {
|
|
618
|
-
|
|
1236
|
+
debug("connector.skipped", "connector skipped (parse returned null)", ctx);
|
|
619
1237
|
}
|
|
620
1238
|
}
|
|
621
1239
|
const grpSps = spTree.grpSp ?? [];
|
|
622
1240
|
for (const grp of grpSps) {
|
|
623
|
-
const group = parseGroup(grp, rels, slidePath, archive, colorResolver);
|
|
1241
|
+
const group = parseGroup(grp, rels, slidePath, archive, colorResolver, fillContext, fontScheme);
|
|
624
1242
|
if (group) {
|
|
625
1243
|
elements.push(group);
|
|
626
1244
|
} else {
|
|
627
|
-
|
|
1245
|
+
debug("group.skipped", "group skipped (parse returned null)", ctx);
|
|
628
1246
|
}
|
|
629
1247
|
}
|
|
630
1248
|
const graphicFrames = spTree.graphicFrame ?? [];
|
|
631
1249
|
for (const gf of graphicFrames) {
|
|
632
|
-
const chart = parseGraphicFrame(gf, rels, slidePath, archive, colorResolver);
|
|
1250
|
+
const chart = parseGraphicFrame(gf, rels, slidePath, archive, colorResolver, fontScheme);
|
|
633
1251
|
if (chart) {
|
|
634
1252
|
elements.push(chart);
|
|
635
1253
|
} else {
|
|
636
|
-
|
|
1254
|
+
debug("graphicFrame.skipped", "graphicFrame skipped (parse returned null)", ctx);
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
return elements;
|
|
1258
|
+
}
|
|
1259
|
+
function parseShapeTreeOrdered(spTree, orderedChildren, rels, slidePath, archive, colorResolver, context, fillContext, fontScheme) {
|
|
1260
|
+
const ctx = context ?? slidePath;
|
|
1261
|
+
const elements = [];
|
|
1262
|
+
const tagCounters = {};
|
|
1263
|
+
for (const child of orderedChildren) {
|
|
1264
|
+
const tag = Object.keys(child).find((k) => k !== ":@");
|
|
1265
|
+
if (!tag) continue;
|
|
1266
|
+
if (tag === "AlternateContent") {
|
|
1267
|
+
const acIndex = tagCounters["AlternateContent"] ?? 0;
|
|
1268
|
+
tagCounters["AlternateContent"] = acIndex + 1;
|
|
1269
|
+
const acList = spTree.AlternateContent ?? [];
|
|
1270
|
+
const acData = acList[acIndex];
|
|
1271
|
+
if (!acData) continue;
|
|
1272
|
+
const choices = Array.isArray(acData.Choice) ? acData.Choice : acData.Choice ? [acData.Choice] : [];
|
|
1273
|
+
const firstChoice = choices[0];
|
|
1274
|
+
if (!firstChoice) continue;
|
|
1275
|
+
const acOrderedChildren = child.AlternateContent;
|
|
1276
|
+
const choiceOrdered = Array.isArray(acOrderedChildren) ? acOrderedChildren.find((c) => "Choice" in c) : null;
|
|
1277
|
+
const choiceChildren = choiceOrdered?.Choice;
|
|
1278
|
+
if (Array.isArray(choiceChildren)) {
|
|
1279
|
+
const choiceTagCounters = {};
|
|
1280
|
+
for (const choiceChild of choiceChildren) {
|
|
1281
|
+
const choiceTag = Object.keys(choiceChild).find((k) => k !== ":@");
|
|
1282
|
+
if (!choiceTag || !SHAPE_TAGS.has(choiceTag)) continue;
|
|
1283
|
+
const choiceIdx = choiceTagCounters[choiceTag] ?? 0;
|
|
1284
|
+
choiceTagCounters[choiceTag] = choiceIdx + 1;
|
|
1285
|
+
const items = firstChoice[choiceTag];
|
|
1286
|
+
const arr = Array.isArray(items) ? items : items ? [items] : [];
|
|
1287
|
+
const element2 = arr[choiceIdx];
|
|
1288
|
+
if (!element2) continue;
|
|
1289
|
+
parseAndPushElement(
|
|
1290
|
+
choiceTag,
|
|
1291
|
+
element2,
|
|
1292
|
+
choiceChild,
|
|
1293
|
+
elements,
|
|
1294
|
+
rels,
|
|
1295
|
+
slidePath,
|
|
1296
|
+
archive,
|
|
1297
|
+
colorResolver,
|
|
1298
|
+
ctx,
|
|
1299
|
+
fillContext,
|
|
1300
|
+
fontScheme
|
|
1301
|
+
);
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
continue;
|
|
637
1305
|
}
|
|
1306
|
+
if (!SHAPE_TAGS.has(tag)) continue;
|
|
1307
|
+
const index = tagCounters[tag] ?? 0;
|
|
1308
|
+
tagCounters[tag] = index + 1;
|
|
1309
|
+
const tagArray = spTree[tag];
|
|
1310
|
+
const element = tagArray?.[index];
|
|
1311
|
+
if (!element) continue;
|
|
1312
|
+
parseAndPushElement(
|
|
1313
|
+
tag,
|
|
1314
|
+
element,
|
|
1315
|
+
child,
|
|
1316
|
+
elements,
|
|
1317
|
+
rels,
|
|
1318
|
+
slidePath,
|
|
1319
|
+
archive,
|
|
1320
|
+
colorResolver,
|
|
1321
|
+
ctx,
|
|
1322
|
+
fillContext,
|
|
1323
|
+
fontScheme
|
|
1324
|
+
);
|
|
638
1325
|
}
|
|
639
1326
|
return elements;
|
|
640
1327
|
}
|
|
641
|
-
function
|
|
1328
|
+
function parseAndPushElement(tag, element, orderedNode, elements, rels, slidePath, archive, colorResolver, ctx, fillContext, fontScheme) {
|
|
1329
|
+
switch (tag) {
|
|
1330
|
+
case "sp": {
|
|
1331
|
+
const shape = parseShape(element, colorResolver, rels, fillContext, fontScheme, orderedNode);
|
|
1332
|
+
if (shape) {
|
|
1333
|
+
elements.push(shape);
|
|
1334
|
+
} else {
|
|
1335
|
+
debug("shape.skipped", "shape skipped (parse returned null)", ctx);
|
|
1336
|
+
}
|
|
1337
|
+
break;
|
|
1338
|
+
}
|
|
1339
|
+
case "pic": {
|
|
1340
|
+
const img = parseImage(element, rels, slidePath, archive, colorResolver);
|
|
1341
|
+
if (img) {
|
|
1342
|
+
elements.push(img);
|
|
1343
|
+
} else {
|
|
1344
|
+
debug("image.skipped", "image skipped (parse returned null)", ctx);
|
|
1345
|
+
}
|
|
1346
|
+
break;
|
|
1347
|
+
}
|
|
1348
|
+
case "cxnSp": {
|
|
1349
|
+
const connector = parseConnector(element, colorResolver);
|
|
1350
|
+
if (connector) {
|
|
1351
|
+
elements.push(connector);
|
|
1352
|
+
} else {
|
|
1353
|
+
debug("connector.skipped", "connector skipped (parse returned null)", ctx);
|
|
1354
|
+
}
|
|
1355
|
+
break;
|
|
1356
|
+
}
|
|
1357
|
+
case "grpSp": {
|
|
1358
|
+
const grpOrderedChildren = orderedNode[tag] ?? null;
|
|
1359
|
+
const group = parseGroup(
|
|
1360
|
+
element,
|
|
1361
|
+
rels,
|
|
1362
|
+
slidePath,
|
|
1363
|
+
archive,
|
|
1364
|
+
colorResolver,
|
|
1365
|
+
fillContext,
|
|
1366
|
+
fontScheme,
|
|
1367
|
+
grpOrderedChildren
|
|
1368
|
+
);
|
|
1369
|
+
if (group) {
|
|
1370
|
+
elements.push(group);
|
|
1371
|
+
} else {
|
|
1372
|
+
debug("group.skipped", "group skipped (parse returned null)", ctx);
|
|
1373
|
+
}
|
|
1374
|
+
break;
|
|
1375
|
+
}
|
|
1376
|
+
case "graphicFrame": {
|
|
1377
|
+
const gfResult = parseGraphicFrame(
|
|
1378
|
+
element,
|
|
1379
|
+
rels,
|
|
1380
|
+
slidePath,
|
|
1381
|
+
archive,
|
|
1382
|
+
colorResolver,
|
|
1383
|
+
fontScheme
|
|
1384
|
+
);
|
|
1385
|
+
if (gfResult) {
|
|
1386
|
+
elements.push(gfResult);
|
|
1387
|
+
} else {
|
|
1388
|
+
debug("graphicFrame.skipped", "graphicFrame skipped (parse returned null)", ctx);
|
|
1389
|
+
}
|
|
1390
|
+
break;
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
function parseShape(sp, colorResolver, rels, fillContext, fontScheme, orderedNode) {
|
|
642
1395
|
const spPr = sp.spPr;
|
|
643
1396
|
if (!spPr) return null;
|
|
644
1397
|
const transform = parseTransform(spPr.xfrm);
|
|
645
1398
|
if (!transform) return null;
|
|
1399
|
+
if (sp.style) {
|
|
1400
|
+
warn("sp.style", "shape style references (lnRef/fillRef/effectRef) not implemented");
|
|
1401
|
+
}
|
|
1402
|
+
if (spPr.scene3d) {
|
|
1403
|
+
warn("spPr.scene3d", "3D scene/camera not implemented");
|
|
1404
|
+
}
|
|
1405
|
+
if (spPr.sp3d) {
|
|
1406
|
+
warn("spPr.sp3d", "3D extrusion/bevel not implemented");
|
|
1407
|
+
}
|
|
646
1408
|
const geometry = parseGeometry(spPr);
|
|
647
|
-
const fill = parseFillFromNode(spPr, colorResolver);
|
|
1409
|
+
const fill = parseFillFromNode(spPr, colorResolver, fillContext);
|
|
648
1410
|
const outline = parseOutline(spPr.ln, colorResolver);
|
|
649
|
-
|
|
1411
|
+
let orderedTxBody;
|
|
1412
|
+
if (orderedNode) {
|
|
1413
|
+
const spChildren = orderedNode.sp;
|
|
1414
|
+
if (Array.isArray(spChildren)) {
|
|
1415
|
+
const txBodyEntry = spChildren.find((c) => "txBody" in c);
|
|
1416
|
+
orderedTxBody = txBodyEntry?.txBody;
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
const textBody = parseTextBody(
|
|
1420
|
+
sp.txBody,
|
|
1421
|
+
colorResolver,
|
|
1422
|
+
rels,
|
|
1423
|
+
fontScheme,
|
|
1424
|
+
void 0,
|
|
1425
|
+
orderedTxBody
|
|
1426
|
+
);
|
|
650
1427
|
const effects = parseEffectList(spPr.effectLst, colorResolver);
|
|
651
|
-
const
|
|
1428
|
+
const nvSpPr = sp.nvSpPr;
|
|
1429
|
+
const nvPr = nvSpPr?.nvPr;
|
|
1430
|
+
const ph = nvPr?.ph;
|
|
652
1431
|
const placeholderType = ph ? ph["@_type"] ?? "body" : void 0;
|
|
653
1432
|
const placeholderIdx = ph?.["@_idx"] !== void 0 ? Number(ph["@_idx"]) : void 0;
|
|
654
1433
|
return {
|
|
@@ -669,7 +1448,8 @@ function parseImage(pic, rels, slidePath, archive, colorResolver) {
|
|
|
669
1448
|
const transform = parseTransform(spPr.xfrm);
|
|
670
1449
|
if (!transform) return null;
|
|
671
1450
|
const blipFill = pic.blipFill;
|
|
672
|
-
const
|
|
1451
|
+
const blip = blipFill?.blip;
|
|
1452
|
+
const rId = blip?.["@_r:embed"] ?? blip?.["@_embed"];
|
|
673
1453
|
if (!rId) return null;
|
|
674
1454
|
const rel = rels.get(rId);
|
|
675
1455
|
if (!rel) return null;
|
|
@@ -689,30 +1469,43 @@ function parseImage(pic, rels, slidePath, archive, colorResolver) {
|
|
|
689
1469
|
const mimeType = mimeMap[ext] ?? "image/png";
|
|
690
1470
|
const imageData = mediaData.toString("base64");
|
|
691
1471
|
const effects = parseEffectList(spPr.effectLst, colorResolver);
|
|
1472
|
+
const srcRect = parseSrcRect(blipFill?.srcRect);
|
|
692
1473
|
return {
|
|
693
1474
|
type: "image",
|
|
694
1475
|
transform,
|
|
695
1476
|
imageData,
|
|
696
1477
|
mimeType,
|
|
697
|
-
effects
|
|
1478
|
+
effects,
|
|
1479
|
+
srcRect
|
|
698
1480
|
};
|
|
699
1481
|
}
|
|
1482
|
+
function parseSrcRect(node) {
|
|
1483
|
+
if (!node) return null;
|
|
1484
|
+
const l = Number(node["@_l"] ?? 0) / 1e5;
|
|
1485
|
+
const t = Number(node["@_t"] ?? 0) / 1e5;
|
|
1486
|
+
const r = Number(node["@_r"] ?? 0) / 1e5;
|
|
1487
|
+
const b = Number(node["@_b"] ?? 0) / 1e5;
|
|
1488
|
+
if (l === 0 && t === 0 && r === 0 && b === 0) return null;
|
|
1489
|
+
return { left: l, top: t, right: r, bottom: b };
|
|
1490
|
+
}
|
|
700
1491
|
function parseConnector(cxn, colorResolver) {
|
|
701
1492
|
const spPr = cxn.spPr;
|
|
702
1493
|
if (!spPr) return null;
|
|
703
1494
|
const transform = parseTransform(spPr.xfrm);
|
|
704
1495
|
if (!transform) return null;
|
|
1496
|
+
const geometry = parseGeometry(spPr);
|
|
705
1497
|
const outline = parseOutline(spPr.ln, colorResolver);
|
|
706
1498
|
const effects = parseEffectList(spPr.effectLst, colorResolver);
|
|
707
|
-
return { type: "connector", transform, outline, effects };
|
|
1499
|
+
return { type: "connector", transform, geometry, outline, effects };
|
|
708
1500
|
}
|
|
709
|
-
function parseGroup(grp, rels, slidePath, archive, colorResolver) {
|
|
1501
|
+
function parseGroup(grp, rels, slidePath, archive, colorResolver, parentFillContext, fontScheme, orderedChildren) {
|
|
710
1502
|
const grpSpPr = grp.grpSpPr;
|
|
711
1503
|
if (!grpSpPr) return null;
|
|
712
|
-
const
|
|
1504
|
+
const xfrm = grpSpPr.xfrm;
|
|
1505
|
+
const transform = parseTransform(xfrm);
|
|
713
1506
|
if (!transform) return null;
|
|
714
|
-
const childOff =
|
|
715
|
-
const childExt =
|
|
1507
|
+
const childOff = xfrm?.chOff;
|
|
1508
|
+
const childExt = xfrm?.chExt;
|
|
716
1509
|
const childTransform = {
|
|
717
1510
|
offsetX: Number(childOff?.["@_x"] ?? 0),
|
|
718
1511
|
offsetY: Number(childOff?.["@_y"] ?? 0),
|
|
@@ -722,15 +1515,33 @@ function parseGroup(grp, rels, slidePath, archive, colorResolver) {
|
|
|
722
1515
|
flipH: false,
|
|
723
1516
|
flipV: false
|
|
724
1517
|
};
|
|
725
|
-
const
|
|
1518
|
+
const groupFill = parseFillFromNode(grpSpPr, colorResolver, parentFillContext);
|
|
1519
|
+
const childFillContext = {
|
|
1520
|
+
rels: parentFillContext?.rels ?? rels,
|
|
1521
|
+
archive: parentFillContext?.archive ?? { files: /* @__PURE__ */ new Map(), media: /* @__PURE__ */ new Map() },
|
|
1522
|
+
basePath: parentFillContext?.basePath ?? slidePath,
|
|
1523
|
+
...groupFill ? { groupFill } : {}
|
|
1524
|
+
};
|
|
1525
|
+
const children = parseShapeTree(
|
|
1526
|
+
grp,
|
|
1527
|
+
rels,
|
|
1528
|
+
slidePath,
|
|
1529
|
+
archive,
|
|
1530
|
+
colorResolver,
|
|
1531
|
+
void 0,
|
|
1532
|
+
childFillContext,
|
|
1533
|
+
fontScheme,
|
|
1534
|
+
orderedChildren
|
|
1535
|
+
);
|
|
726
1536
|
const effects = parseEffectList(grpSpPr.effectLst, colorResolver);
|
|
727
1537
|
return { type: "group", transform, childTransform, children, effects };
|
|
728
1538
|
}
|
|
729
|
-
function parseGraphicFrame(gf, rels, slidePath, archive, colorResolver) {
|
|
1539
|
+
function parseGraphicFrame(gf, rels, slidePath, archive, colorResolver, fontScheme) {
|
|
730
1540
|
const xfrm = gf.xfrm;
|
|
731
1541
|
const transform = parseTransform(xfrm);
|
|
732
1542
|
if (!transform) return null;
|
|
733
|
-
const
|
|
1543
|
+
const graphic = gf.graphic;
|
|
1544
|
+
const graphicData = graphic?.graphicData;
|
|
734
1545
|
if (!graphicData) return null;
|
|
735
1546
|
const chartRef = graphicData.chart;
|
|
736
1547
|
if (chartRef) {
|
|
@@ -747,12 +1558,103 @@ function parseGraphicFrame(gf, rels, slidePath, archive, colorResolver) {
|
|
|
747
1558
|
}
|
|
748
1559
|
const tblNode = graphicData.tbl;
|
|
749
1560
|
if (tblNode) {
|
|
750
|
-
const tableData = parseTable(tblNode, colorResolver);
|
|
1561
|
+
const tableData = parseTable(tblNode, colorResolver, fontScheme);
|
|
751
1562
|
if (!tableData) return null;
|
|
752
1563
|
return { type: "table", transform, table: tableData };
|
|
753
1564
|
}
|
|
1565
|
+
if (graphicData["@_uri"] === "http://schemas.openxmlformats.org/drawingml/2006/diagram") {
|
|
1566
|
+
return parseSmartArt(
|
|
1567
|
+
graphicData,
|
|
1568
|
+
transform,
|
|
1569
|
+
rels,
|
|
1570
|
+
slidePath,
|
|
1571
|
+
archive,
|
|
1572
|
+
colorResolver,
|
|
1573
|
+
fontScheme
|
|
1574
|
+
);
|
|
1575
|
+
}
|
|
1576
|
+
const uri = graphicData["@_uri"] ?? "unknown";
|
|
1577
|
+
warn("graphicFrame.unsupported", `unsupported graphicFrame content (uri: ${uri})`);
|
|
754
1578
|
return null;
|
|
755
1579
|
}
|
|
1580
|
+
function parseSmartArt(graphicData, transform, rels, slidePath, archive, colorResolver, fontScheme) {
|
|
1581
|
+
const relIds = graphicData.relIds;
|
|
1582
|
+
if (!relIds) return null;
|
|
1583
|
+
const dmRId = relIds["@_r:dm"] ?? relIds["@_dm"];
|
|
1584
|
+
if (!dmRId) return null;
|
|
1585
|
+
const dmRel = rels.get(dmRId);
|
|
1586
|
+
if (!dmRel) return null;
|
|
1587
|
+
const dataPath = resolveRelationshipTarget(slidePath, dmRel.target);
|
|
1588
|
+
const dataRelsPath = buildRelsPath(dataPath);
|
|
1589
|
+
const dataRelsXml = archive.files.get(dataRelsPath);
|
|
1590
|
+
let drawingPath = null;
|
|
1591
|
+
if (dataRelsXml) {
|
|
1592
|
+
const dataRels = parseRelationships(dataRelsXml);
|
|
1593
|
+
for (const [, rel] of dataRels) {
|
|
1594
|
+
if (rel.type.includes("diagramDrawing")) {
|
|
1595
|
+
drawingPath = resolveRelationshipTarget(dataPath, rel.target);
|
|
1596
|
+
break;
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
if (!drawingPath) {
|
|
1601
|
+
for (const [, rel] of rels) {
|
|
1602
|
+
if (rel.type.includes("diagramDrawing")) {
|
|
1603
|
+
drawingPath = resolveRelationshipTarget(slidePath, rel.target);
|
|
1604
|
+
break;
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
if (!drawingPath) return null;
|
|
1609
|
+
const drawingXml = archive.files.get(drawingPath);
|
|
1610
|
+
if (!drawingXml) return null;
|
|
1611
|
+
const parsed = parseXml(drawingXml);
|
|
1612
|
+
const drawing = parsed.drawing;
|
|
1613
|
+
const spTree = drawing?.spTree;
|
|
1614
|
+
if (!spTree) return null;
|
|
1615
|
+
const drawingRelsPath = buildRelsPath(drawingPath);
|
|
1616
|
+
const drawingRelsXml = archive.files.get(drawingRelsPath);
|
|
1617
|
+
const drawingRels = drawingRelsXml ? parseRelationships(drawingRelsXml) : /* @__PURE__ */ new Map();
|
|
1618
|
+
const grpSpPr = spTree.grpSpPr;
|
|
1619
|
+
const grpXfrm = grpSpPr?.xfrm;
|
|
1620
|
+
const childOff = grpXfrm?.chOff;
|
|
1621
|
+
const childExt = grpXfrm?.chExt;
|
|
1622
|
+
const childTransform = {
|
|
1623
|
+
offsetX: Number(childOff?.["@_x"] ?? 0),
|
|
1624
|
+
offsetY: Number(childOff?.["@_y"] ?? 0),
|
|
1625
|
+
extentWidth: Number(childExt?.["@_cx"] ?? transform.extentWidth),
|
|
1626
|
+
extentHeight: Number(childExt?.["@_cy"] ?? transform.extentHeight),
|
|
1627
|
+
rotation: 0,
|
|
1628
|
+
flipH: false,
|
|
1629
|
+
flipV: false
|
|
1630
|
+
};
|
|
1631
|
+
const fillContext = {
|
|
1632
|
+
rels: drawingRels,
|
|
1633
|
+
archive,
|
|
1634
|
+
basePath: drawingPath
|
|
1635
|
+
};
|
|
1636
|
+
const orderedParsed = parseXmlOrdered(drawingXml);
|
|
1637
|
+
const orderedSpTree = navigateOrdered(orderedParsed, ["drawing", "spTree"]);
|
|
1638
|
+
const children = parseShapeTree(
|
|
1639
|
+
spTree,
|
|
1640
|
+
drawingRels,
|
|
1641
|
+
drawingPath,
|
|
1642
|
+
archive,
|
|
1643
|
+
colorResolver,
|
|
1644
|
+
void 0,
|
|
1645
|
+
fillContext,
|
|
1646
|
+
fontScheme,
|
|
1647
|
+
orderedSpTree
|
|
1648
|
+
);
|
|
1649
|
+
if (children.length === 0) return null;
|
|
1650
|
+
return {
|
|
1651
|
+
type: "group",
|
|
1652
|
+
transform,
|
|
1653
|
+
childTransform,
|
|
1654
|
+
children,
|
|
1655
|
+
effects: null
|
|
1656
|
+
};
|
|
1657
|
+
}
|
|
756
1658
|
function parseTransform(xfrm) {
|
|
757
1659
|
if (!xfrm) return null;
|
|
758
1660
|
const off = xfrm.off;
|
|
@@ -764,23 +1666,23 @@ function parseTransform(xfrm) {
|
|
|
764
1666
|
let extentHeight = Number(ext["@_cy"] ?? 0);
|
|
765
1667
|
let rotation = Number(xfrm["@_rot"] ?? 0);
|
|
766
1668
|
if (Number.isNaN(offsetX)) {
|
|
767
|
-
|
|
1669
|
+
debug("transform.nan", "NaN detected in transform offsetX, defaulting to 0");
|
|
768
1670
|
offsetX = 0;
|
|
769
1671
|
}
|
|
770
1672
|
if (Number.isNaN(offsetY)) {
|
|
771
|
-
|
|
1673
|
+
debug("transform.nan", "NaN detected in transform offsetY, defaulting to 0");
|
|
772
1674
|
offsetY = 0;
|
|
773
1675
|
}
|
|
774
1676
|
if (Number.isNaN(extentWidth)) {
|
|
775
|
-
|
|
1677
|
+
debug("transform.nan", "NaN detected in transform extentWidth, defaulting to 0");
|
|
776
1678
|
extentWidth = 0;
|
|
777
1679
|
}
|
|
778
1680
|
if (Number.isNaN(extentHeight)) {
|
|
779
|
-
|
|
1681
|
+
debug("transform.nan", "NaN detected in transform extentHeight, defaulting to 0");
|
|
780
1682
|
extentHeight = 0;
|
|
781
1683
|
}
|
|
782
1684
|
if (Number.isNaN(rotation)) {
|
|
783
|
-
|
|
1685
|
+
debug("transform.nan", "NaN detected in transform rotation, defaulting to 0");
|
|
784
1686
|
rotation = 0;
|
|
785
1687
|
}
|
|
786
1688
|
return {
|
|
@@ -795,14 +1697,16 @@ function parseTransform(xfrm) {
|
|
|
795
1697
|
}
|
|
796
1698
|
function parseGeometry(spPr) {
|
|
797
1699
|
if (spPr.prstGeom) {
|
|
798
|
-
const
|
|
799
|
-
const
|
|
1700
|
+
const prstGeom = spPr.prstGeom;
|
|
1701
|
+
const preset = prstGeom["@_prst"] ?? "rect";
|
|
1702
|
+
const avLst = prstGeom.avLst;
|
|
800
1703
|
const adjustValues = {};
|
|
801
1704
|
if (avLst?.gd) {
|
|
802
1705
|
const guides = Array.isArray(avLst.gd) ? avLst.gd : [avLst.gd];
|
|
803
1706
|
for (const gd of guides) {
|
|
804
|
-
const
|
|
805
|
-
const
|
|
1707
|
+
const gdNode = gd;
|
|
1708
|
+
const name = gdNode["@_name"];
|
|
1709
|
+
const fmla = gdNode["@_fmla"];
|
|
806
1710
|
const match = fmla?.match(/val\s+(\d+)/);
|
|
807
1711
|
if (name && match) {
|
|
808
1712
|
adjustValues[name] = Number(match[1]);
|
|
@@ -812,59 +1716,21 @@ function parseGeometry(spPr) {
|
|
|
812
1716
|
return { type: "preset", preset, adjustValues };
|
|
813
1717
|
}
|
|
814
1718
|
if (spPr.custGeom) {
|
|
815
|
-
const
|
|
816
|
-
if (
|
|
817
|
-
return { type: "custom",
|
|
1719
|
+
const paths = parseCustomGeometry(spPr.custGeom);
|
|
1720
|
+
if (paths) {
|
|
1721
|
+
return { type: "custom", paths };
|
|
818
1722
|
}
|
|
819
1723
|
}
|
|
820
1724
|
return { type: "preset", preset: "rect", adjustValues: {} };
|
|
821
1725
|
}
|
|
822
|
-
function
|
|
823
|
-
const pathLst = custGeom.pathLst;
|
|
824
|
-
if (!pathLst?.path) return null;
|
|
825
|
-
const paths = Array.isArray(pathLst.path) ? pathLst.path : [pathLst.path];
|
|
826
|
-
const svgParts = [];
|
|
827
|
-
for (const path of paths) {
|
|
828
|
-
const w = Number(path["@_w"] ?? 0);
|
|
829
|
-
const h = Number(path["@_h"] ?? 0);
|
|
830
|
-
if (w === 0 && h === 0) continue;
|
|
831
|
-
const processCommands = (commands, prefix) => {
|
|
832
|
-
if (!commands) return;
|
|
833
|
-
const list = Array.isArray(commands) ? commands : [commands];
|
|
834
|
-
for (const cmd of list) {
|
|
835
|
-
if (prefix === "M" || prefix === "L") {
|
|
836
|
-
const pt = cmd.pt;
|
|
837
|
-
if (pt) {
|
|
838
|
-
const pts = Array.isArray(pt) ? pt : [pt];
|
|
839
|
-
svgParts.push(
|
|
840
|
-
`${prefix} ${pts.map((p) => `${p["@_x"]} ${p["@_y"]}`).join(" ")}`
|
|
841
|
-
);
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
};
|
|
846
|
-
if (path.moveTo) processCommands(Array.isArray(path.moveTo) ? path.moveTo : [path.moveTo], "M");
|
|
847
|
-
if (path.lnTo) processCommands(Array.isArray(path.lnTo) ? path.lnTo : [path.lnTo], "L");
|
|
848
|
-
if (path.cubicBezTo) {
|
|
849
|
-
const bezList = Array.isArray(path.cubicBezTo) ? path.cubicBezTo : [path.cubicBezTo];
|
|
850
|
-
for (const bez of bezList) {
|
|
851
|
-
const pts = Array.isArray(bez.pt) ? bez.pt : [bez.pt];
|
|
852
|
-
if (pts.length >= 3) {
|
|
853
|
-
svgParts.push(
|
|
854
|
-
`C ${pts.map((p) => `${p["@_x"]} ${p["@_y"]}`).join(", ")}`
|
|
855
|
-
);
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
if (path.close !== void 0) {
|
|
860
|
-
svgParts.push("Z");
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
return svgParts.length > 0 ? svgParts.join(" ") : null;
|
|
864
|
-
}
|
|
865
|
-
function parseTextBody(txBody, colorResolver) {
|
|
1726
|
+
function parseTextBody(txBody, colorResolver, rels, fontScheme, lstStyleOverride, orderedTxBody) {
|
|
866
1727
|
if (!txBody) return null;
|
|
867
1728
|
const bodyPr = txBody.bodyPr;
|
|
1729
|
+
const vert = bodyPr?.["@_vert"];
|
|
1730
|
+
if (vert && vert !== "horz") {
|
|
1731
|
+
warn("bodyPr@vert", `vertical text (vert="${vert}") not implemented`);
|
|
1732
|
+
}
|
|
1733
|
+
const numCol = bodyPr?.["@_numCol"] ? Math.max(1, Number(bodyPr["@_numCol"])) : 1;
|
|
868
1734
|
let autoFit = "noAutofit";
|
|
869
1735
|
let fontScale = 1;
|
|
870
1736
|
let lnSpcReduction = 0;
|
|
@@ -872,8 +1738,9 @@ function parseTextBody(txBody, colorResolver) {
|
|
|
872
1738
|
autoFit = "normAutofit";
|
|
873
1739
|
const normAutofit = bodyPr.normAutofit;
|
|
874
1740
|
if (typeof normAutofit === "object" && normAutofit !== null) {
|
|
875
|
-
|
|
876
|
-
|
|
1741
|
+
const normNode = normAutofit;
|
|
1742
|
+
fontScale = normNode["@_fontScale"] ? Number(normNode["@_fontScale"]) / 1e5 : 1;
|
|
1743
|
+
lnSpcReduction = normNode["@_lnSpcReduction"] ? Number(normNode["@_lnSpcReduction"]) / 1e5 : 0;
|
|
877
1744
|
}
|
|
878
1745
|
} else if (bodyPr?.spAutoFit !== void 0) {
|
|
879
1746
|
autoFit = "spAutofit";
|
|
@@ -887,12 +1754,24 @@ function parseTextBody(txBody, colorResolver) {
|
|
|
887
1754
|
wrap: bodyPr?.["@_wrap"] ?? "square",
|
|
888
1755
|
autoFit,
|
|
889
1756
|
fontScale,
|
|
890
|
-
lnSpcReduction
|
|
1757
|
+
lnSpcReduction,
|
|
1758
|
+
numCol
|
|
891
1759
|
};
|
|
1760
|
+
const lstStyle = lstStyleOverride ?? parseListStyle(txBody.lstStyle);
|
|
1761
|
+
const orderedParagraphs = [];
|
|
1762
|
+
if (orderedTxBody) {
|
|
1763
|
+
for (const child of orderedTxBody) {
|
|
1764
|
+
if ("p" in child) {
|
|
1765
|
+
orderedParagraphs.push(child.p);
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
892
1769
|
const paragraphs = [];
|
|
893
1770
|
const pList = txBody.p ?? [];
|
|
894
|
-
for (
|
|
895
|
-
paragraphs.push(
|
|
1771
|
+
for (let i = 0; i < pList.length; i++) {
|
|
1772
|
+
paragraphs.push(
|
|
1773
|
+
parseParagraph(pList[i], colorResolver, rels, fontScheme, lstStyle, orderedParagraphs[i])
|
|
1774
|
+
);
|
|
896
1775
|
}
|
|
897
1776
|
if (paragraphs.length === 0) return null;
|
|
898
1777
|
return { paragraphs, bodyProperties };
|
|
@@ -911,79 +1790,256 @@ var VALID_AUTO_NUM_SCHEMES = /* @__PURE__ */ new Set([
|
|
|
911
1790
|
function parseBullet(pPr, colorResolver) {
|
|
912
1791
|
let bullet = null;
|
|
913
1792
|
let bulletFont = null;
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
1793
|
+
const buClr = pPr?.buClr;
|
|
1794
|
+
let bulletColor = colorResolver.resolve(buClr);
|
|
1795
|
+
if (!buClr) bulletColor = null;
|
|
1796
|
+
const buSzPct = pPr?.buSzPct;
|
|
1797
|
+
const bulletSizePct = buSzPct ? Number(buSzPct["@_val"]) : null;
|
|
917
1798
|
if (pPr?.buNone !== void 0) {
|
|
918
1799
|
bullet = { type: "none" };
|
|
919
1800
|
} else if (pPr?.buChar) {
|
|
920
|
-
|
|
1801
|
+
const buChar = pPr.buChar;
|
|
1802
|
+
bullet = { type: "char", char: buChar["@_char"] ?? "\u2022" };
|
|
921
1803
|
} else if (pPr?.buAutoNum) {
|
|
922
|
-
const
|
|
1804
|
+
const buAutoNum = pPr.buAutoNum;
|
|
1805
|
+
const scheme = buAutoNum["@_type"] ?? "arabicPeriod";
|
|
923
1806
|
bullet = {
|
|
924
1807
|
type: "autoNum",
|
|
925
1808
|
scheme: VALID_AUTO_NUM_SCHEMES.has(scheme) ? scheme : "arabicPeriod",
|
|
926
|
-
startAt: Number(
|
|
1809
|
+
startAt: Number(buAutoNum["@_startAt"] ?? 1)
|
|
927
1810
|
};
|
|
928
1811
|
}
|
|
929
1812
|
if (pPr?.buFont) {
|
|
930
|
-
|
|
1813
|
+
const buFont = pPr.buFont;
|
|
1814
|
+
bulletFont = buFont["@_typeface"] ?? null;
|
|
931
1815
|
}
|
|
932
1816
|
return { bullet, bulletFont, bulletColor, bulletSizePct };
|
|
933
1817
|
}
|
|
934
|
-
function
|
|
1818
|
+
function parseSpacing(spc) {
|
|
1819
|
+
if (spc?.spcPts) {
|
|
1820
|
+
const spcPts = spc.spcPts;
|
|
1821
|
+
return { type: "pts", value: Number(spcPts["@_val"]) };
|
|
1822
|
+
}
|
|
1823
|
+
if (spc?.spcPct) {
|
|
1824
|
+
const spcPct = spc.spcPct;
|
|
1825
|
+
return { type: "pct", value: Number(spcPct["@_val"]) };
|
|
1826
|
+
}
|
|
1827
|
+
return { type: "pts", value: 0 };
|
|
1828
|
+
}
|
|
1829
|
+
function parseTabStops(pPr) {
|
|
1830
|
+
const tabLst = pPr?.tabLst;
|
|
1831
|
+
if (!tabLst) return [];
|
|
1832
|
+
const tabs = tabLst.tab;
|
|
1833
|
+
if (!tabs) return [];
|
|
1834
|
+
const tabArr = Array.isArray(tabs) ? tabs : [tabs];
|
|
1835
|
+
return tabArr.map((tab) => ({
|
|
1836
|
+
position: Number(tab["@_pos"] ?? 0),
|
|
1837
|
+
alignment: tab["@_algn"] ?? "l"
|
|
1838
|
+
}));
|
|
1839
|
+
}
|
|
1840
|
+
function extractMathText(node) {
|
|
1841
|
+
let text = "";
|
|
1842
|
+
for (const [key, value] of Object.entries(node)) {
|
|
1843
|
+
if (key.startsWith("@_")) continue;
|
|
1844
|
+
if (key === "t") {
|
|
1845
|
+
if (typeof value === "object" && value !== null) {
|
|
1846
|
+
text += value["#text"] ?? "";
|
|
1847
|
+
} else if (value !== void 0 && value !== null) {
|
|
1848
|
+
text += String(value);
|
|
1849
|
+
}
|
|
1850
|
+
} else if (typeof value === "object" && value !== null) {
|
|
1851
|
+
const items = Array.isArray(value) ? value : [value];
|
|
1852
|
+
for (const item of items) {
|
|
1853
|
+
if (typeof item === "object" && item !== null) {
|
|
1854
|
+
text += extractMathText(item);
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
return text;
|
|
1860
|
+
}
|
|
1861
|
+
function extractTextContent(node) {
|
|
1862
|
+
const text = node.t;
|
|
1863
|
+
if (typeof text === "object") {
|
|
1864
|
+
return text["#text"] ?? "";
|
|
1865
|
+
}
|
|
1866
|
+
return text !== void 0 && text !== null ? String(text) : "";
|
|
1867
|
+
}
|
|
1868
|
+
function parseParagraph(p, colorResolver, rels, fontScheme, lstStyle, orderedPChildren) {
|
|
935
1869
|
const pPr = p.pPr;
|
|
1870
|
+
const level = Number(pPr?.["@_lvl"] ?? 0);
|
|
1871
|
+
const lstLevelProps = lstStyle?.levels[level];
|
|
936
1872
|
const { bullet, bulletFont, bulletColor, bulletSizePct } = parseBullet(pPr, colorResolver);
|
|
1873
|
+
const lnSpc = pPr?.lnSpc;
|
|
1874
|
+
const lnSpcSpcPct = lnSpc?.spcPct;
|
|
1875
|
+
const tabStops = parseTabStops(pPr);
|
|
937
1876
|
const properties = {
|
|
938
|
-
alignment: pPr?.["@_algn"] ?? "l",
|
|
939
|
-
lineSpacing:
|
|
940
|
-
spaceBefore: pPr?.spcBef
|
|
941
|
-
spaceAfter: pPr?.spcAft
|
|
942
|
-
level
|
|
1877
|
+
alignment: pPr?.["@_algn"] ?? lstLevelProps?.alignment ?? "l",
|
|
1878
|
+
lineSpacing: lnSpcSpcPct ? Number(lnSpcSpcPct["@_val"]) : null,
|
|
1879
|
+
spaceBefore: parseSpacing(pPr?.spcBef),
|
|
1880
|
+
spaceAfter: parseSpacing(pPr?.spcAft),
|
|
1881
|
+
level,
|
|
943
1882
|
bullet,
|
|
944
1883
|
bulletFont,
|
|
945
1884
|
bulletColor,
|
|
946
1885
|
bulletSizePct,
|
|
947
|
-
marginLeft: Number(pPr
|
|
948
|
-
indent: Number(pPr
|
|
1886
|
+
marginLeft: pPr?.["@_marL"] !== void 0 ? Number(pPr["@_marL"]) : lstLevelProps?.marginLeft ?? 0,
|
|
1887
|
+
indent: pPr?.["@_indent"] !== void 0 ? Number(pPr["@_indent"]) : lstLevelProps?.indent ?? 0,
|
|
1888
|
+
tabStops
|
|
949
1889
|
};
|
|
1890
|
+
const pPrDefRPr = parseDefaultRunProperties(pPr?.defRPr);
|
|
1891
|
+
const lstDefRPr = lstLevelProps?.defaultRunProperties;
|
|
1892
|
+
const mergedDefaults = mergeDefaultRunProperties(pPrDefRPr, lstDefRPr);
|
|
950
1893
|
const runs = [];
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
const
|
|
954
|
-
const
|
|
955
|
-
const
|
|
956
|
-
const
|
|
957
|
-
|
|
1894
|
+
if (orderedPChildren) {
|
|
1895
|
+
const tagCounters = {};
|
|
1896
|
+
const rList = p.r ?? [];
|
|
1897
|
+
const fldList = p.fld ?? [];
|
|
1898
|
+
const brList = p.br ?? [];
|
|
1899
|
+
for (const child of orderedPChildren) {
|
|
1900
|
+
const tag = Object.keys(child).find((k) => k !== ":@");
|
|
1901
|
+
if (!tag) continue;
|
|
1902
|
+
const idx = tagCounters[tag] ?? 0;
|
|
1903
|
+
tagCounters[tag] = idx + 1;
|
|
1904
|
+
if (tag === "r") {
|
|
1905
|
+
const r = rList[idx];
|
|
1906
|
+
if (r) {
|
|
1907
|
+
const textContent = extractTextContent(r);
|
|
1908
|
+
const rPr = r.rPr;
|
|
1909
|
+
const runProps = parseRunProperties(rPr, colorResolver, rels, fontScheme, mergedDefaults);
|
|
1910
|
+
runs.push({ text: textContent, properties: runProps });
|
|
1911
|
+
}
|
|
1912
|
+
} else if (tag === "fld") {
|
|
1913
|
+
const fld = fldList[idx];
|
|
1914
|
+
if (fld) {
|
|
1915
|
+
const textContent = extractTextContent(fld);
|
|
1916
|
+
const rPr = fld.rPr;
|
|
1917
|
+
const runProps = parseRunProperties(rPr, colorResolver, rels, fontScheme, mergedDefaults);
|
|
1918
|
+
runs.push({ text: textContent, properties: runProps });
|
|
1919
|
+
}
|
|
1920
|
+
} else if (tag === "br") {
|
|
1921
|
+
const br = brList[idx];
|
|
1922
|
+
const rPr = br?.rPr;
|
|
1923
|
+
const runProps = parseRunProperties(rPr, colorResolver, rels, fontScheme, mergedDefaults);
|
|
1924
|
+
runs.push({ text: "\n", properties: runProps });
|
|
1925
|
+
} else if (tag === "m") {
|
|
1926
|
+
const mNode = p.m;
|
|
1927
|
+
if (mNode) {
|
|
1928
|
+
const mData = Array.isArray(mNode) ? mNode[idx] : idx === 0 ? mNode : void 0;
|
|
1929
|
+
if (mData) {
|
|
1930
|
+
const mathText = extractMathText(mData);
|
|
1931
|
+
if (mathText) {
|
|
1932
|
+
const runProps = parseRunProperties(
|
|
1933
|
+
void 0,
|
|
1934
|
+
colorResolver,
|
|
1935
|
+
rels,
|
|
1936
|
+
fontScheme,
|
|
1937
|
+
mergedDefaults
|
|
1938
|
+
);
|
|
1939
|
+
runs.push({ text: mathText, properties: runProps });
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
} else {
|
|
1946
|
+
const rList = p.r ?? [];
|
|
1947
|
+
for (const r of rList) {
|
|
1948
|
+
const textContent = extractTextContent(r);
|
|
1949
|
+
const rPr = r.rPr;
|
|
1950
|
+
const runProps = parseRunProperties(rPr, colorResolver, rels, fontScheme, mergedDefaults);
|
|
1951
|
+
runs.push({ text: textContent, properties: runProps });
|
|
1952
|
+
}
|
|
1953
|
+
const fldList = p.fld ?? [];
|
|
1954
|
+
for (const fld of fldList) {
|
|
1955
|
+
const textContent = extractTextContent(fld);
|
|
1956
|
+
const rPr = fld.rPr;
|
|
1957
|
+
const runProps = parseRunProperties(rPr, colorResolver, rels, fontScheme, mergedDefaults);
|
|
1958
|
+
runs.push({ text: textContent, properties: runProps });
|
|
1959
|
+
}
|
|
1960
|
+
const brList = p.br ?? [];
|
|
1961
|
+
for (const _br of brList) {
|
|
1962
|
+
const rPr = _br?.rPr;
|
|
1963
|
+
const runProps = parseRunProperties(rPr, colorResolver, rels, fontScheme, mergedDefaults);
|
|
1964
|
+
runs.push({ text: "\n", properties: runProps });
|
|
1965
|
+
}
|
|
1966
|
+
const mNode = p.m;
|
|
1967
|
+
if (mNode) {
|
|
1968
|
+
const mArr = Array.isArray(mNode) ? mNode : [mNode];
|
|
1969
|
+
for (const m of mArr) {
|
|
1970
|
+
const mathText = extractMathText(m);
|
|
1971
|
+
if (mathText) {
|
|
1972
|
+
const runProps = parseRunProperties(
|
|
1973
|
+
void 0,
|
|
1974
|
+
colorResolver,
|
|
1975
|
+
rels,
|
|
1976
|
+
fontScheme,
|
|
1977
|
+
mergedDefaults
|
|
1978
|
+
);
|
|
1979
|
+
runs.push({ text: mathText, properties: runProps });
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
958
1983
|
}
|
|
959
1984
|
return { runs, properties };
|
|
960
1985
|
}
|
|
961
|
-
function
|
|
1986
|
+
function mergeDefaultRunProperties(primary, secondary) {
|
|
1987
|
+
if (!primary && !secondary) return void 0;
|
|
1988
|
+
if (!primary) return secondary;
|
|
1989
|
+
if (!secondary) return primary;
|
|
1990
|
+
return {
|
|
1991
|
+
fontSize: primary.fontSize ?? secondary.fontSize,
|
|
1992
|
+
fontFamily: primary.fontFamily ?? secondary.fontFamily,
|
|
1993
|
+
fontFamilyEa: primary.fontFamilyEa ?? secondary.fontFamilyEa,
|
|
1994
|
+
bold: primary.bold ?? secondary.bold,
|
|
1995
|
+
italic: primary.italic ?? secondary.italic,
|
|
1996
|
+
underline: primary.underline ?? secondary.underline,
|
|
1997
|
+
strikethrough: primary.strikethrough ?? secondary.strikethrough
|
|
1998
|
+
};
|
|
1999
|
+
}
|
|
2000
|
+
function parseRunProperties(rPr, colorResolver, rels, fontScheme, defaults) {
|
|
962
2001
|
if (!rPr) {
|
|
963
2002
|
return {
|
|
964
|
-
fontSize: null,
|
|
965
|
-
fontFamily: null,
|
|
966
|
-
fontFamilyEa: null,
|
|
967
|
-
bold: false,
|
|
968
|
-
italic: false,
|
|
969
|
-
underline: false,
|
|
970
|
-
strikethrough: false,
|
|
2003
|
+
fontSize: defaults?.fontSize ?? null,
|
|
2004
|
+
fontFamily: resolveThemeFont(defaults?.fontFamily ?? null, fontScheme),
|
|
2005
|
+
fontFamilyEa: resolveThemeFont(defaults?.fontFamilyEa ?? null, fontScheme),
|
|
2006
|
+
bold: defaults?.bold ?? false,
|
|
2007
|
+
italic: defaults?.italic ?? false,
|
|
2008
|
+
underline: defaults?.underline ?? false,
|
|
2009
|
+
strikethrough: defaults?.strikethrough ?? false,
|
|
971
2010
|
color: null,
|
|
972
|
-
baseline: 0
|
|
2011
|
+
baseline: 0,
|
|
2012
|
+
hyperlink: null
|
|
973
2013
|
};
|
|
974
2014
|
}
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
const
|
|
2015
|
+
if (rPr.effectLst) {
|
|
2016
|
+
warn("rPr.effectLst", "text run effects not implemented");
|
|
2017
|
+
}
|
|
2018
|
+
if (rPr.highlight) {
|
|
2019
|
+
warn("rPr.highlight", "text highlighting not implemented");
|
|
2020
|
+
}
|
|
2021
|
+
const latin = rPr.latin;
|
|
2022
|
+
const ea = rPr.ea;
|
|
2023
|
+
const fontSize = rPr["@_sz"] ? hundredthPointToPoint(Number(rPr["@_sz"])) : defaults?.fontSize ?? null;
|
|
2024
|
+
const fontFamily = resolveThemeFont(
|
|
2025
|
+
latin?.["@_typeface"] ?? defaults?.fontFamily ?? null,
|
|
2026
|
+
fontScheme
|
|
2027
|
+
);
|
|
2028
|
+
const fontFamilyEa = resolveThemeFont(
|
|
2029
|
+
ea?.["@_typeface"] ?? defaults?.fontFamilyEa ?? null,
|
|
2030
|
+
fontScheme
|
|
2031
|
+
);
|
|
2032
|
+
const bold = rPr["@_b"] !== void 0 ? rPr["@_b"] === "1" || rPr["@_b"] === "true" : defaults?.bold ?? false;
|
|
2033
|
+
const italic = rPr["@_i"] !== void 0 ? rPr["@_i"] === "1" || rPr["@_i"] === "true" : defaults?.italic ?? false;
|
|
2034
|
+
const underline = rPr["@_u"] !== void 0 ? rPr["@_u"] !== "none" : defaults?.underline ?? false;
|
|
2035
|
+
const strikethrough = rPr["@_strike"] !== void 0 ? rPr["@_strike"] !== "noStrike" : defaults?.strikethrough ?? false;
|
|
982
2036
|
const baseline = rPr["@_baseline"] ? Number(rPr["@_baseline"]) / 1e3 : 0;
|
|
983
|
-
|
|
984
|
-
|
|
2037
|
+
const solidFill = rPr.solidFill;
|
|
2038
|
+
let color = colorResolver.resolve(solidFill ?? rPr);
|
|
2039
|
+
if (!solidFill && !rPr.srgbClr && !rPr.schemeClr && !rPr.sysClr) {
|
|
985
2040
|
color = null;
|
|
986
2041
|
}
|
|
2042
|
+
const hyperlink = parseHyperlink(rPr.hlinkClick, rels);
|
|
987
2043
|
return {
|
|
988
2044
|
fontSize,
|
|
989
2045
|
fontFamily,
|
|
@@ -993,12 +2049,21 @@ function parseRunProperties(rPr, colorResolver) {
|
|
|
993
2049
|
underline,
|
|
994
2050
|
strikethrough,
|
|
995
2051
|
color,
|
|
996
|
-
baseline
|
|
2052
|
+
baseline,
|
|
2053
|
+
hyperlink
|
|
997
2054
|
};
|
|
998
2055
|
}
|
|
2056
|
+
function parseHyperlink(hlinkClick, rels) {
|
|
2057
|
+
if (!hlinkClick) return null;
|
|
2058
|
+
const rId = hlinkClick["@_r:id"] ?? hlinkClick["@_id"];
|
|
2059
|
+
if (!rId || !rels) return null;
|
|
2060
|
+
const rel = rels.get(rId);
|
|
2061
|
+
if (!rel) return null;
|
|
2062
|
+
const tooltip = hlinkClick["@_tooltip"];
|
|
2063
|
+
return { url: rel.target, ...tooltip && { tooltip } };
|
|
2064
|
+
}
|
|
999
2065
|
|
|
1000
2066
|
// src/parser/slide-master-parser.ts
|
|
1001
|
-
var WARN_PREFIX6 = "[pptx-glimpse]";
|
|
1002
2067
|
var DEFAULT_COLOR_MAP = {
|
|
1003
2068
|
bg1: "lt1",
|
|
1004
2069
|
tx1: "dk1",
|
|
@@ -1015,11 +2080,12 @@ var DEFAULT_COLOR_MAP = {
|
|
|
1015
2080
|
};
|
|
1016
2081
|
function parseSlideMasterColorMap(xml) {
|
|
1017
2082
|
const parsed = parseXml(xml);
|
|
1018
|
-
|
|
1019
|
-
|
|
2083
|
+
const sldMaster = parsed.sldMaster;
|
|
2084
|
+
if (!sldMaster) {
|
|
2085
|
+
debug("slideMaster.missing", `missing root element "sldMaster" in XML`);
|
|
1020
2086
|
return { ...DEFAULT_COLOR_MAP };
|
|
1021
2087
|
}
|
|
1022
|
-
const clrMap =
|
|
2088
|
+
const clrMap = sldMaster.clrMap;
|
|
1023
2089
|
if (!clrMap) return { ...DEFAULT_COLOR_MAP };
|
|
1024
2090
|
const result = {};
|
|
1025
2091
|
for (const key of Object.keys(DEFAULT_COLOR_MAP)) {
|
|
@@ -1030,74 +2096,192 @@ function parseSlideMasterColorMap(xml) {
|
|
|
1030
2096
|
}
|
|
1031
2097
|
function parseSlideMasterBackground(xml, colorResolver, context) {
|
|
1032
2098
|
const parsed = parseXml(xml);
|
|
1033
|
-
|
|
1034
|
-
|
|
2099
|
+
const sldMaster = parsed.sldMaster;
|
|
2100
|
+
if (!sldMaster) {
|
|
2101
|
+
debug("slideMaster.missing", `missing root element "sldMaster" in XML`);
|
|
1035
2102
|
return null;
|
|
1036
2103
|
}
|
|
1037
|
-
const
|
|
2104
|
+
const cSld = sldMaster.cSld;
|
|
2105
|
+
const bg = cSld?.bg;
|
|
1038
2106
|
if (!bg) return null;
|
|
1039
2107
|
const bgPr = bg.bgPr;
|
|
1040
2108
|
if (!bgPr) return null;
|
|
1041
2109
|
const fill = parseFillFromNode(bgPr, colorResolver, context);
|
|
1042
2110
|
return { fill };
|
|
1043
2111
|
}
|
|
1044
|
-
function parseSlideMasterElements(xml, masterPath, archive, colorResolver) {
|
|
2112
|
+
function parseSlideMasterElements(xml, masterPath, archive, colorResolver, fontScheme) {
|
|
1045
2113
|
const parsed = parseXml(xml);
|
|
1046
|
-
|
|
1047
|
-
|
|
2114
|
+
const sldMaster = parsed.sldMaster;
|
|
2115
|
+
if (!sldMaster) {
|
|
2116
|
+
debug("slideMaster.missing", `missing root element "sldMaster" in XML`);
|
|
1048
2117
|
return [];
|
|
1049
2118
|
}
|
|
1050
|
-
const
|
|
2119
|
+
const cSld = sldMaster.cSld;
|
|
2120
|
+
const spTree = cSld?.spTree;
|
|
1051
2121
|
if (!spTree) return [];
|
|
1052
2122
|
const relsPath = buildRelsPath(masterPath);
|
|
1053
2123
|
const relsXml = archive.files.get(relsPath);
|
|
1054
2124
|
const rels = relsXml ? parseRelationships(relsXml) : /* @__PURE__ */ new Map();
|
|
1055
|
-
|
|
2125
|
+
const orderedParsed = parseXmlOrdered(xml);
|
|
2126
|
+
const orderedSpTree = navigateOrdered(orderedParsed, ["sldMaster", "cSld", "spTree"]);
|
|
2127
|
+
return parseShapeTree(
|
|
2128
|
+
spTree,
|
|
2129
|
+
rels,
|
|
2130
|
+
masterPath,
|
|
2131
|
+
archive,
|
|
2132
|
+
colorResolver,
|
|
2133
|
+
void 0,
|
|
2134
|
+
void 0,
|
|
2135
|
+
fontScheme,
|
|
2136
|
+
orderedSpTree
|
|
2137
|
+
);
|
|
2138
|
+
}
|
|
2139
|
+
function parseSlideMasterTxStyles(xml, colorResolver) {
|
|
2140
|
+
const parsed = parseXml(xml);
|
|
2141
|
+
const sldMaster = parsed.sldMaster;
|
|
2142
|
+
if (!sldMaster) {
|
|
2143
|
+
debug("slideMaster.missing", `missing root element "sldMaster" in XML`);
|
|
2144
|
+
return void 0;
|
|
2145
|
+
}
|
|
2146
|
+
const txStyles = sldMaster.txStyles;
|
|
2147
|
+
if (!txStyles) return void 0;
|
|
2148
|
+
const titleStyleNode = txStyles.titleStyle;
|
|
2149
|
+
const bodyStyleNode = txStyles.bodyStyle;
|
|
2150
|
+
const otherStyleNode = txStyles.otherStyle;
|
|
2151
|
+
const titleStyle = titleStyleNode ? parseListStyle(titleStyleNode, colorResolver) : void 0;
|
|
2152
|
+
const bodyStyle = bodyStyleNode ? parseListStyle(bodyStyleNode, colorResolver) : void 0;
|
|
2153
|
+
const otherStyle = otherStyleNode ? parseListStyle(otherStyleNode, colorResolver) : void 0;
|
|
2154
|
+
if (!titleStyle && !bodyStyle && !otherStyle) return void 0;
|
|
2155
|
+
return { titleStyle, bodyStyle, otherStyle };
|
|
2156
|
+
}
|
|
2157
|
+
function parseSlideMasterPlaceholderStyles(xml, colorResolver) {
|
|
2158
|
+
const parsed = parseXml(xml);
|
|
2159
|
+
const sldMaster = parsed.sldMaster;
|
|
2160
|
+
if (!sldMaster) return [];
|
|
2161
|
+
const cSld = sldMaster.cSld;
|
|
2162
|
+
const spTree = cSld?.spTree;
|
|
2163
|
+
if (!spTree) return [];
|
|
2164
|
+
const results = [];
|
|
2165
|
+
const shapes = spTree.sp ?? [];
|
|
2166
|
+
for (const sp of shapes) {
|
|
2167
|
+
const nvSpPr = sp.nvSpPr;
|
|
2168
|
+
const nvPr = nvSpPr?.nvPr;
|
|
2169
|
+
const ph = nvPr?.ph;
|
|
2170
|
+
if (!ph) continue;
|
|
2171
|
+
const placeholderType = ph["@_type"] ?? "body";
|
|
2172
|
+
const placeholderIdx = ph["@_idx"] !== void 0 ? Number(ph["@_idx"]) : void 0;
|
|
2173
|
+
const txBody = sp.txBody;
|
|
2174
|
+
const lstStyleNode = txBody?.lstStyle;
|
|
2175
|
+
const lstStyle = lstStyleNode ? parseListStyle(lstStyleNode, colorResolver) : void 0;
|
|
2176
|
+
results.push({
|
|
2177
|
+
placeholderType,
|
|
2178
|
+
...placeholderIdx !== void 0 && { placeholderIdx },
|
|
2179
|
+
...lstStyle && { lstStyle }
|
|
2180
|
+
});
|
|
2181
|
+
}
|
|
2182
|
+
return results;
|
|
1056
2183
|
}
|
|
1057
2184
|
|
|
1058
2185
|
// src/parser/slide-layout-parser.ts
|
|
1059
|
-
var WARN_PREFIX7 = "[pptx-glimpse]";
|
|
1060
2186
|
function parseSlideLayoutBackground(xml, colorResolver, context) {
|
|
1061
2187
|
const parsed = parseXml(xml);
|
|
1062
|
-
|
|
1063
|
-
|
|
2188
|
+
const sldLayout = parsed.sldLayout;
|
|
2189
|
+
if (!sldLayout) {
|
|
2190
|
+
debug("slideLayout.missing", `missing root element "sldLayout" in XML`);
|
|
1064
2191
|
return null;
|
|
1065
2192
|
}
|
|
1066
|
-
const
|
|
2193
|
+
const cSld = sldLayout.cSld;
|
|
2194
|
+
const bg = cSld?.bg;
|
|
1067
2195
|
if (!bg) return null;
|
|
1068
2196
|
const bgPr = bg.bgPr;
|
|
1069
2197
|
if (!bgPr) return null;
|
|
1070
2198
|
const fill = parseFillFromNode(bgPr, colorResolver, context);
|
|
1071
2199
|
return { fill };
|
|
1072
2200
|
}
|
|
1073
|
-
function parseSlideLayoutElements(xml, layoutPath, archive, colorResolver) {
|
|
2201
|
+
function parseSlideLayoutElements(xml, layoutPath, archive, colorResolver, fontScheme) {
|
|
1074
2202
|
const parsed = parseXml(xml);
|
|
1075
|
-
|
|
1076
|
-
|
|
2203
|
+
const sldLayout = parsed.sldLayout;
|
|
2204
|
+
if (!sldLayout) {
|
|
2205
|
+
debug("slideLayout.missing", `missing root element "sldLayout" in XML`);
|
|
1077
2206
|
return [];
|
|
1078
2207
|
}
|
|
1079
|
-
const
|
|
2208
|
+
const cSld = sldLayout.cSld;
|
|
2209
|
+
const spTree = cSld?.spTree;
|
|
1080
2210
|
if (!spTree) return [];
|
|
1081
2211
|
const relsPath = buildRelsPath(layoutPath);
|
|
1082
2212
|
const relsXml = archive.files.get(relsPath);
|
|
1083
2213
|
const rels = relsXml ? parseRelationships(relsXml) : /* @__PURE__ */ new Map();
|
|
1084
|
-
|
|
2214
|
+
const orderedParsed = parseXmlOrdered(xml);
|
|
2215
|
+
const orderedSpTree = navigateOrdered(orderedParsed, ["sldLayout", "cSld", "spTree"]);
|
|
2216
|
+
return parseShapeTree(
|
|
2217
|
+
spTree,
|
|
2218
|
+
rels,
|
|
2219
|
+
layoutPath,
|
|
2220
|
+
archive,
|
|
2221
|
+
colorResolver,
|
|
2222
|
+
void 0,
|
|
2223
|
+
void 0,
|
|
2224
|
+
fontScheme,
|
|
2225
|
+
orderedSpTree
|
|
2226
|
+
);
|
|
2227
|
+
}
|
|
2228
|
+
function parseSlideLayoutShowMasterSp(xml) {
|
|
2229
|
+
const parsed = parseXml(xml);
|
|
2230
|
+
const sldLayout = parsed.sldLayout;
|
|
2231
|
+
const attr = sldLayout?.["@_showMasterSp"];
|
|
2232
|
+
return attr !== "0" && attr !== "false";
|
|
2233
|
+
}
|
|
2234
|
+
function parseSlideLayoutPlaceholderStyles(xml, colorResolver) {
|
|
2235
|
+
const parsed = parseXml(xml);
|
|
2236
|
+
const sldLayout = parsed.sldLayout;
|
|
2237
|
+
if (!sldLayout) return [];
|
|
2238
|
+
const cSld = sldLayout.cSld;
|
|
2239
|
+
const spTree = cSld?.spTree;
|
|
2240
|
+
if (!spTree) return [];
|
|
2241
|
+
const results = [];
|
|
2242
|
+
const shapes = spTree.sp ?? [];
|
|
2243
|
+
for (const sp of shapes) {
|
|
2244
|
+
const nvSpPr = sp.nvSpPr;
|
|
2245
|
+
const nvPr = nvSpPr?.nvPr;
|
|
2246
|
+
const ph = nvPr?.ph;
|
|
2247
|
+
if (!ph) continue;
|
|
2248
|
+
const placeholderType = ph["@_type"] ?? "body";
|
|
2249
|
+
const placeholderIdx = ph["@_idx"] !== void 0 ? Number(ph["@_idx"]) : void 0;
|
|
2250
|
+
const txBody = sp.txBody;
|
|
2251
|
+
const lstStyleNode = txBody?.lstStyle;
|
|
2252
|
+
const lstStyle = lstStyleNode ? parseListStyle(lstStyleNode, colorResolver) : void 0;
|
|
2253
|
+
results.push({
|
|
2254
|
+
placeholderType,
|
|
2255
|
+
...placeholderIdx !== void 0 && { placeholderIdx },
|
|
2256
|
+
...lstStyle && { lstStyle }
|
|
2257
|
+
});
|
|
2258
|
+
}
|
|
2259
|
+
return results;
|
|
1085
2260
|
}
|
|
1086
2261
|
|
|
1087
2262
|
// src/color/color-transforms.ts
|
|
1088
2263
|
function applyColorTransforms(color, node) {
|
|
1089
2264
|
let { hex, alpha } = color;
|
|
1090
|
-
|
|
1091
|
-
|
|
2265
|
+
const lumMod = node.lumMod;
|
|
2266
|
+
const lumOff = node.lumOff;
|
|
2267
|
+
if (lumMod || lumOff) {
|
|
2268
|
+
hex = applyLuminance(
|
|
2269
|
+
hex,
|
|
2270
|
+
lumMod?.["@_val"],
|
|
2271
|
+
lumOff?.["@_val"]
|
|
2272
|
+
);
|
|
1092
2273
|
}
|
|
1093
|
-
|
|
1094
|
-
|
|
2274
|
+
const tintNode = node.tint;
|
|
2275
|
+
if (tintNode) {
|
|
2276
|
+
hex = applyTint(hex, Number(tintNode["@_val"]) / 1e5);
|
|
1095
2277
|
}
|
|
1096
|
-
|
|
1097
|
-
|
|
2278
|
+
const shadeNode = node.shade;
|
|
2279
|
+
if (shadeNode) {
|
|
2280
|
+
hex = applyShade(hex, Number(shadeNode["@_val"]) / 1e5);
|
|
1098
2281
|
}
|
|
1099
|
-
|
|
1100
|
-
|
|
2282
|
+
const alphaNode = node.alpha;
|
|
2283
|
+
if (alphaNode) {
|
|
2284
|
+
alpha = Number(alphaNode["@_val"]) / 1e5;
|
|
1101
2285
|
}
|
|
1102
2286
|
return { hex, alpha };
|
|
1103
2287
|
}
|
|
@@ -1180,13 +2364,11 @@ function hslToHex(h, s, l) {
|
|
|
1180
2364
|
}
|
|
1181
2365
|
|
|
1182
2366
|
// src/color/color-resolver.ts
|
|
1183
|
-
var WARN_PREFIX8 = "[pptx-glimpse]";
|
|
1184
2367
|
var ColorResolver = class {
|
|
1185
2368
|
constructor(colorScheme, colorMap) {
|
|
1186
2369
|
this.colorScheme = colorScheme;
|
|
1187
2370
|
this.colorMap = colorMap;
|
|
1188
2371
|
}
|
|
1189
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1190
2372
|
resolve(colorNode) {
|
|
1191
2373
|
if (!colorNode) return null;
|
|
1192
2374
|
if (colorNode.srgbClr) {
|
|
@@ -1200,26 +2382,21 @@ var ColorResolver = class {
|
|
|
1200
2382
|
}
|
|
1201
2383
|
const keys = Object.keys(colorNode).filter((k) => !k.startsWith("@_"));
|
|
1202
2384
|
if (keys.length > 0) {
|
|
1203
|
-
|
|
1204
|
-
`${WARN_PREFIX8} ColorResolver: unknown color node structure [${keys.join(", ")}]`
|
|
1205
|
-
);
|
|
2385
|
+
debug("colorResolver.unknown", `unknown color node structure [${keys.join(", ")}]`);
|
|
1206
2386
|
}
|
|
1207
2387
|
return null;
|
|
1208
2388
|
}
|
|
1209
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1210
2389
|
resolveSrgbClr(node) {
|
|
1211
2390
|
const hex = `#${node["@_val"]}`;
|
|
1212
2391
|
const alpha = extractAlpha(node);
|
|
1213
2392
|
return applyColorTransforms({ hex, alpha }, node);
|
|
1214
2393
|
}
|
|
1215
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1216
2394
|
resolveSchemeClr(node) {
|
|
1217
2395
|
const schemeName = node["@_val"];
|
|
1218
2396
|
const hex = this.resolveSchemeColorName(schemeName);
|
|
1219
2397
|
const alpha = extractAlpha(node);
|
|
1220
2398
|
return applyColorTransforms({ hex, alpha }, node);
|
|
1221
2399
|
}
|
|
1222
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1223
2400
|
resolveSysClr(node) {
|
|
1224
2401
|
const hex = `#${node["@_lastClr"] ?? "000000"}`;
|
|
1225
2402
|
const alpha = extractAlpha(node);
|
|
@@ -1338,6 +2515,42 @@ var presetGeometries = {
|
|
|
1338
2515
|
return `<polygon points="${bodyLeft},0 ${bodyRight},0 ${bodyRight},${shaftEnd} ${w},${shaftEnd} ${w / 2},${h} 0,${shaftEnd} ${bodyLeft},${shaftEnd}"/>`;
|
|
1339
2516
|
},
|
|
1340
2517
|
line: () => "",
|
|
2518
|
+
// =====================
|
|
2519
|
+
// Connector shapes
|
|
2520
|
+
// =====================
|
|
2521
|
+
straightConnector1: (w, h) => `<path d="M 0 0 L ${w} ${h}"/>`,
|
|
2522
|
+
bentConnector2: (w, h) => `<path d="M 0 0 L ${w} 0 L ${w} ${h}"/>`,
|
|
2523
|
+
bentConnector3: (w, h, adj) => {
|
|
2524
|
+
const midX = (adj["adj1"] ?? 5e4) / 1e5 * w;
|
|
2525
|
+
return `<path d="M 0 0 L ${midX} 0 L ${midX} ${h} L ${w} ${h}"/>`;
|
|
2526
|
+
},
|
|
2527
|
+
bentConnector4: (w, h, adj) => {
|
|
2528
|
+
const midX = (adj["adj1"] ?? 5e4) / 1e5 * w;
|
|
2529
|
+
const midY = (adj["adj2"] ?? 5e4) / 1e5 * h;
|
|
2530
|
+
return `<path d="M 0 0 L ${midX} 0 L ${midX} ${midY} L ${w} ${midY} L ${w} ${h}"/>`;
|
|
2531
|
+
},
|
|
2532
|
+
bentConnector5: (w, h, adj) => {
|
|
2533
|
+
const midX1 = (adj["adj1"] ?? 5e4) / 1e5 * w;
|
|
2534
|
+
const midY = (adj["adj2"] ?? 5e4) / 1e5 * h;
|
|
2535
|
+
const midX2 = (adj["adj3"] ?? 5e4) / 1e5 * w;
|
|
2536
|
+
return `<path d="M 0 0 L ${midX1} 0 L ${midX1} ${midY} L ${midX2} ${midY} L ${midX2} ${h} L ${w} ${h}"/>`;
|
|
2537
|
+
},
|
|
2538
|
+
curvedConnector2: (w, h) => `<path d="M 0 0 C ${w} 0 0 ${h} ${w} ${h}"/>`,
|
|
2539
|
+
curvedConnector3: (w, h, adj) => {
|
|
2540
|
+
const midX = (adj["adj1"] ?? 5e4) / 1e5 * w;
|
|
2541
|
+
return `<path d="M 0 0 C ${midX} 0 ${midX} ${h} ${w} ${h}"/>`;
|
|
2542
|
+
},
|
|
2543
|
+
curvedConnector4: (w, h, adj) => {
|
|
2544
|
+
const midX = (adj["adj1"] ?? 5e4) / 1e5 * w;
|
|
2545
|
+
const midY = (adj["adj2"] ?? 5e4) / 1e5 * h;
|
|
2546
|
+
return `<path d="M 0 0 C ${midX} 0 ${midX} ${midY} ${midX} ${midY} S ${w} ${midY} ${w} ${h}"/>`;
|
|
2547
|
+
},
|
|
2548
|
+
curvedConnector5: (w, h, adj) => {
|
|
2549
|
+
const midX1 = (adj["adj1"] ?? 5e4) / 1e5 * w;
|
|
2550
|
+
const midY = (adj["adj2"] ?? 5e4) / 1e5 * h;
|
|
2551
|
+
const midX2 = (adj["adj3"] ?? 5e4) / 1e5 * w;
|
|
2552
|
+
return `<path d="M 0 0 C ${midX1} 0 ${midX1} ${midY} ${midX1} ${midY} S ${midX2} ${midY} ${midX2} ${h} S ${w} ${h} ${w} ${h}"/>`;
|
|
2553
|
+
},
|
|
1341
2554
|
cloud: (w, h) => `<rect width="${w}" height="${h}" rx="${Math.min(w, h) * 0.15}"/>`,
|
|
1342
2555
|
heart: (w, h) => {
|
|
1343
2556
|
const cx = w / 2;
|
|
@@ -1402,7 +2615,7 @@ var presetGeometries = {
|
|
|
1402
2615
|
return `<polygon points="0,0 ${w - offset},0 ${w},${h / 2} ${w - offset},${h} 0,${h} ${offset},${h / 2}"/>`;
|
|
1403
2616
|
},
|
|
1404
2617
|
homePlate: (w, h, adj) => {
|
|
1405
|
-
const offset = (adj["adj"] ?? 5e4) / 1e5 * w;
|
|
2618
|
+
const offset = (adj["adj"] ?? 5e4) / 1e5 * Math.min(w, h);
|
|
1406
2619
|
return `<polygon points="0,0 ${w - offset},0 ${w},${h / 2} ${w - offset},${h} 0,${h}"/>`;
|
|
1407
2620
|
},
|
|
1408
2621
|
leftRightUpArrow: (w, h, adj) => {
|
|
@@ -1993,11 +3206,27 @@ function renderGeometry(geometry, width, height) {
|
|
|
1993
3206
|
if (geometry.type === "preset") {
|
|
1994
3207
|
return getPresetGeometrySvg(geometry.preset, width, height, geometry.adjustValues);
|
|
1995
3208
|
}
|
|
1996
|
-
if (geometry.type === "custom" && geometry.
|
|
1997
|
-
return
|
|
3209
|
+
if (geometry.type === "custom" && geometry.paths.length > 0) {
|
|
3210
|
+
return renderCustomGeometry(geometry.paths, width, height);
|
|
1998
3211
|
}
|
|
1999
3212
|
return `<rect width="${width}" height="${height}"/>`;
|
|
2000
3213
|
}
|
|
3214
|
+
function renderCustomGeometry(paths, shapeWidth, shapeHeight) {
|
|
3215
|
+
if (paths.length === 1) {
|
|
3216
|
+
return renderCustomPath(paths[0], shapeWidth, shapeHeight);
|
|
3217
|
+
}
|
|
3218
|
+
const parts = ["<g>"];
|
|
3219
|
+
for (const path of paths) {
|
|
3220
|
+
parts.push(renderCustomPath(path, shapeWidth, shapeHeight));
|
|
3221
|
+
}
|
|
3222
|
+
parts.push("</g>");
|
|
3223
|
+
return parts.join("");
|
|
3224
|
+
}
|
|
3225
|
+
function renderCustomPath(path, shapeWidth, shapeHeight) {
|
|
3226
|
+
const scaleX = path.width > 0 ? shapeWidth / path.width : 1;
|
|
3227
|
+
const scaleY = path.height > 0 ? shapeHeight / path.height : 1;
|
|
3228
|
+
return `<path d="${path.commands}" transform="scale(${scaleX}, ${scaleY})"/>`;
|
|
3229
|
+
}
|
|
2001
3230
|
|
|
2002
3231
|
// src/renderer/fill-renderer.ts
|
|
2003
3232
|
function renderFillAttrs(fill) {
|
|
@@ -2009,41 +3238,78 @@ function renderFillAttrs(fill) {
|
|
|
2009
3238
|
return { attrs: `fill="${fill.color.hex}"${alphaAttr}`, defs: "" };
|
|
2010
3239
|
}
|
|
2011
3240
|
if (fill.type === "gradient") {
|
|
2012
|
-
const
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
const
|
|
2017
|
-
const
|
|
2018
|
-
const y2 = 50 + Math.sin(rad) * 50;
|
|
2019
|
-
const stops = fill.stops.map((s) => {
|
|
2020
|
-
const opacityAttr = s.color.alpha < 1 ? ` stop-opacity="${s.color.alpha}"` : "";
|
|
2021
|
-
return `<stop offset="${s.position * 100}%" stop-color="${s.color.hex}"${opacityAttr}/>`;
|
|
2022
|
-
}).join("");
|
|
2023
|
-
const defs = `<linearGradient id="${id}" x1="${x1}%" y1="${y1}%" x2="${x2}%" y2="${y2}%">${stops}</linearGradient>`;
|
|
3241
|
+
const result = renderGradientDefs(fill);
|
|
3242
|
+
return { attrs: `fill="${result.ref}"`, defs: result.defs };
|
|
3243
|
+
}
|
|
3244
|
+
if (fill.type === "image") {
|
|
3245
|
+
const id = `imgfill-${crypto.randomUUID()}`;
|
|
3246
|
+
const defs = `<pattern id="${id}" patternContentUnits="objectBoundingBox" width="1" height="1"><image href="data:${fill.mimeType};base64,${fill.imageData}" width="1" height="1" preserveAspectRatio="none"/></pattern>`;
|
|
2024
3247
|
return { attrs: `fill="url(#${id})"`, defs };
|
|
2025
3248
|
}
|
|
3249
|
+
if (fill.type === "pattern") {
|
|
3250
|
+
return renderPatternFill(fill);
|
|
3251
|
+
}
|
|
2026
3252
|
return { attrs: `fill="none"`, defs: "" };
|
|
2027
3253
|
}
|
|
2028
3254
|
function renderOutlineAttrs(outline) {
|
|
2029
|
-
if (!outline) return `stroke="none"
|
|
3255
|
+
if (!outline) return { attrs: `stroke="none"`, defs: "" };
|
|
2030
3256
|
const widthPx = emuToPixels(outline.width);
|
|
2031
3257
|
const parts = [`stroke-width="${widthPx}"`];
|
|
3258
|
+
let defs = "";
|
|
2032
3259
|
if (outline.fill) {
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
3260
|
+
if (outline.fill.type === "solid") {
|
|
3261
|
+
parts.push(`stroke="${outline.fill.color.hex}"`);
|
|
3262
|
+
if (outline.fill.color.alpha < 1) {
|
|
3263
|
+
parts.push(`stroke-opacity="${outline.fill.color.alpha}"`);
|
|
3264
|
+
}
|
|
3265
|
+
} else if (outline.fill.type === "gradient") {
|
|
3266
|
+
const gradResult = renderGradientDefs(outline.fill);
|
|
3267
|
+
parts.push(`stroke="${gradResult.ref}"`);
|
|
3268
|
+
defs = gradResult.defs;
|
|
2036
3269
|
}
|
|
2037
3270
|
} else {
|
|
2038
3271
|
parts.push(`stroke="none"`);
|
|
2039
3272
|
}
|
|
2040
|
-
if (outline.
|
|
3273
|
+
if (outline.customDash && outline.customDash.length > 0) {
|
|
3274
|
+
const dashArray = outline.customDash.map((v) => v * widthPx).join(" ");
|
|
3275
|
+
parts.push(`stroke-dasharray="${dashArray}"`);
|
|
3276
|
+
} else if (outline.dashStyle !== "solid") {
|
|
2041
3277
|
const dashArray = getDashArray(outline.dashStyle, widthPx);
|
|
2042
3278
|
if (dashArray) {
|
|
2043
3279
|
parts.push(`stroke-dasharray="${dashArray}"`);
|
|
2044
3280
|
}
|
|
2045
3281
|
}
|
|
2046
|
-
|
|
3282
|
+
if (outline.lineCap) {
|
|
3283
|
+
parts.push(`stroke-linecap="${outline.lineCap}"`);
|
|
3284
|
+
}
|
|
3285
|
+
if (outline.lineJoin) {
|
|
3286
|
+
parts.push(`stroke-linejoin="${outline.lineJoin}"`);
|
|
3287
|
+
}
|
|
3288
|
+
return { attrs: parts.join(" "), defs };
|
|
3289
|
+
}
|
|
3290
|
+
function renderGradientDefs(fill) {
|
|
3291
|
+
const id = `grad-${crypto.randomUUID()}`;
|
|
3292
|
+
const stops = fill.stops.map((s) => {
|
|
3293
|
+
const opacityAttr = s.color.alpha < 1 ? ` stop-opacity="${s.color.alpha}"` : "";
|
|
3294
|
+
return `<stop offset="${s.position * 100}%" stop-color="${s.color.hex}"${opacityAttr}/>`;
|
|
3295
|
+
}).join("");
|
|
3296
|
+
if (fill.gradientType === "radial") {
|
|
3297
|
+
const cx = (fill.centerX ?? 0.5) * 100;
|
|
3298
|
+
const cy = (fill.centerY ?? 0.5) * 100;
|
|
3299
|
+
const dx = Math.max(cx, 100 - cx);
|
|
3300
|
+
const dy = Math.max(cy, 100 - cy);
|
|
3301
|
+
const r = Math.sqrt(dx * dx + dy * dy);
|
|
3302
|
+
const defs2 = `<radialGradient id="${id}" cx="${cx}%" cy="${cy}%" r="${r}%">${stops}</radialGradient>`;
|
|
3303
|
+
return { ref: `url(#${id})`, defs: defs2 };
|
|
3304
|
+
}
|
|
3305
|
+
const angle = fill.angle;
|
|
3306
|
+
const rad = angle * Math.PI / 180;
|
|
3307
|
+
const x1 = 50 - Math.cos(rad) * 50;
|
|
3308
|
+
const y1 = 50 - Math.sin(rad) * 50;
|
|
3309
|
+
const x2 = 50 + Math.cos(rad) * 50;
|
|
3310
|
+
const y2 = 50 + Math.sin(rad) * 50;
|
|
3311
|
+
const defs = `<linearGradient id="${id}" x1="${x1}%" y1="${y1}%" x2="${x2}%" y2="${y2}%">${stops}</linearGradient>`;
|
|
3312
|
+
return { ref: `url(#${id})`, defs };
|
|
2047
3313
|
}
|
|
2048
3314
|
function getDashArray(style, w) {
|
|
2049
3315
|
const patterns = {
|
|
@@ -2059,6 +3325,185 @@ function getDashArray(style, w) {
|
|
|
2059
3325
|
if (!pattern) return null;
|
|
2060
3326
|
return pattern.map((v) => v * w).join(" ");
|
|
2061
3327
|
}
|
|
3328
|
+
function renderPatternFill(fill) {
|
|
3329
|
+
const id = `patt-${crypto.randomUUID()}`;
|
|
3330
|
+
const fg = fill.foregroundColor.hex;
|
|
3331
|
+
const bg = fill.backgroundColor.hex;
|
|
3332
|
+
const fgAlpha = fill.foregroundColor.alpha < 1 ? ` opacity="${fill.foregroundColor.alpha}"` : "";
|
|
3333
|
+
const content = getPatternContent(fill.preset, fg, fgAlpha);
|
|
3334
|
+
if (!content) {
|
|
3335
|
+
const alphaAttr = fill.foregroundColor.alpha < 1 ? ` fill-opacity="${fill.foregroundColor.alpha}"` : "";
|
|
3336
|
+
return { attrs: `fill="${fg}"${alphaAttr}`, defs: "" };
|
|
3337
|
+
}
|
|
3338
|
+
const bgAlpha = fill.backgroundColor.alpha < 1 ? ` fill-opacity="${fill.backgroundColor.alpha}"` : "";
|
|
3339
|
+
const defs = `<pattern id="${id}" patternUnits="userSpaceOnUse" width="${content.size}" height="${content.size}"><rect width="${content.size}" height="${content.size}" fill="${bg}"${bgAlpha}/>${content.svg}</pattern>`;
|
|
3340
|
+
return { attrs: `fill="url(#${id})"`, defs };
|
|
3341
|
+
}
|
|
3342
|
+
function getPatternContent(preset, fg, fgAlpha) {
|
|
3343
|
+
const s = 8;
|
|
3344
|
+
const sw = 1;
|
|
3345
|
+
const line = (x1, y1, x2, y2) => `<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="${fg}" stroke-width="${sw}"${fgAlpha}/>`;
|
|
3346
|
+
switch (preset) {
|
|
3347
|
+
case "ltHorz":
|
|
3348
|
+
return { svg: line(0, 4, 8, 4), size: s };
|
|
3349
|
+
case "ltVert":
|
|
3350
|
+
return { svg: line(4, 0, 4, 8), size: s };
|
|
3351
|
+
case "ltDnDiag":
|
|
3352
|
+
return { svg: line(0, 0, 8, 8), size: s };
|
|
3353
|
+
case "ltUpDiag":
|
|
3354
|
+
return { svg: line(0, 8, 8, 0), size: s };
|
|
3355
|
+
case "dkHorz":
|
|
3356
|
+
return {
|
|
3357
|
+
svg: line(0, 2, 8, 2) + line(0, 6, 8, 6),
|
|
3358
|
+
size: s
|
|
3359
|
+
};
|
|
3360
|
+
case "dkVert":
|
|
3361
|
+
return {
|
|
3362
|
+
svg: line(2, 0, 2, 8) + line(6, 0, 6, 8),
|
|
3363
|
+
size: s
|
|
3364
|
+
};
|
|
3365
|
+
case "dkDnDiag":
|
|
3366
|
+
return {
|
|
3367
|
+
svg: line(0, 0, 8, 8) + line(-4, 0, 4, 8),
|
|
3368
|
+
size: s
|
|
3369
|
+
};
|
|
3370
|
+
case "dkUpDiag":
|
|
3371
|
+
return {
|
|
3372
|
+
svg: line(0, 8, 8, 0) + line(4, 8, 12, 0),
|
|
3373
|
+
size: s
|
|
3374
|
+
};
|
|
3375
|
+
case "horz":
|
|
3376
|
+
return { svg: line(0, 4, 8, 4), size: s };
|
|
3377
|
+
case "vert":
|
|
3378
|
+
return { svg: line(4, 0, 4, 8), size: s };
|
|
3379
|
+
case "dnDiag":
|
|
3380
|
+
return { svg: line(0, 0, 8, 8), size: s };
|
|
3381
|
+
case "upDiag":
|
|
3382
|
+
return { svg: line(0, 8, 8, 0), size: s };
|
|
3383
|
+
case "cross":
|
|
3384
|
+
case "smGrid":
|
|
3385
|
+
return {
|
|
3386
|
+
svg: line(0, 4, 8, 4) + line(4, 0, 4, 8),
|
|
3387
|
+
size: s
|
|
3388
|
+
};
|
|
3389
|
+
case "lgGrid":
|
|
3390
|
+
return {
|
|
3391
|
+
svg: line(0, 0, 16, 0) + line(0, 0, 0, 16),
|
|
3392
|
+
size: 16
|
|
3393
|
+
};
|
|
3394
|
+
case "diagCross":
|
|
3395
|
+
return {
|
|
3396
|
+
svg: line(0, 0, 8, 8) + line(0, 8, 8, 0),
|
|
3397
|
+
size: s
|
|
3398
|
+
};
|
|
3399
|
+
case "pct5":
|
|
3400
|
+
return {
|
|
3401
|
+
svg: `<rect x="0" y="0" width="1" height="1" fill="${fg}"${fgAlpha}/>`,
|
|
3402
|
+
size: s
|
|
3403
|
+
};
|
|
3404
|
+
case "pct10":
|
|
3405
|
+
return {
|
|
3406
|
+
svg: `<rect x="0" y="0" width="1" height="1" fill="${fg}"${fgAlpha}/><rect x="4" y="4" width="1" height="1" fill="${fg}"${fgAlpha}/>`,
|
|
3407
|
+
size: s
|
|
3408
|
+
};
|
|
3409
|
+
case "pct20":
|
|
3410
|
+
return {
|
|
3411
|
+
svg: `<rect x="0" y="0" width="2" height="2" fill="${fg}"${fgAlpha}/><rect x="4" y="4" width="2" height="2" fill="${fg}"${fgAlpha}/>`,
|
|
3412
|
+
size: s
|
|
3413
|
+
};
|
|
3414
|
+
case "pct25":
|
|
3415
|
+
return {
|
|
3416
|
+
svg: `<rect x="0" y="0" width="2" height="2" fill="${fg}"${fgAlpha}/><rect x="4" y="0" width="2" height="2" fill="${fg}"${fgAlpha}/><rect x="2" y="4" width="2" height="2" fill="${fg}"${fgAlpha}/><rect x="6" y="4" width="2" height="2" fill="${fg}"${fgAlpha}/>`,
|
|
3417
|
+
size: s
|
|
3418
|
+
};
|
|
3419
|
+
case "pct30":
|
|
3420
|
+
case "pct40":
|
|
3421
|
+
case "pct50":
|
|
3422
|
+
case "pct60":
|
|
3423
|
+
case "pct70":
|
|
3424
|
+
case "pct75":
|
|
3425
|
+
case "pct80":
|
|
3426
|
+
case "pct90": {
|
|
3427
|
+
const pctVal = parseInt(preset.replace("pct", ""), 10);
|
|
3428
|
+
const alpha = pctVal / 100;
|
|
3429
|
+
return {
|
|
3430
|
+
svg: `<rect width="${s}" height="${s}" fill="${fg}" opacity="${alpha}"${fgAlpha}/>`,
|
|
3431
|
+
size: s
|
|
3432
|
+
};
|
|
3433
|
+
}
|
|
3434
|
+
default:
|
|
3435
|
+
return null;
|
|
3436
|
+
}
|
|
3437
|
+
}
|
|
3438
|
+
var ARROW_SIZE_MAP = {
|
|
3439
|
+
sm: 5,
|
|
3440
|
+
med: 8,
|
|
3441
|
+
lg: 12
|
|
3442
|
+
};
|
|
3443
|
+
function renderMarkers(outline) {
|
|
3444
|
+
const empty = { defs: "", startAttr: "", endAttr: "" };
|
|
3445
|
+
if (!outline) return empty;
|
|
3446
|
+
if (!outline.headEnd && !outline.tailEnd) return empty;
|
|
3447
|
+
let color = "#000000";
|
|
3448
|
+
let alpha = 1;
|
|
3449
|
+
if (outline.fill?.type === "solid") {
|
|
3450
|
+
color = outline.fill.color.hex;
|
|
3451
|
+
alpha = outline.fill.color.alpha;
|
|
3452
|
+
} else if (outline.fill?.type === "gradient" && outline.fill.stops.length > 0) {
|
|
3453
|
+
color = outline.fill.stops[0].color.hex;
|
|
3454
|
+
alpha = outline.fill.stops[0].color.alpha;
|
|
3455
|
+
}
|
|
3456
|
+
const defs = [];
|
|
3457
|
+
let startAttr = "";
|
|
3458
|
+
let endAttr = "";
|
|
3459
|
+
if (outline.headEnd) {
|
|
3460
|
+
const id = `marker-${crypto.randomUUID()}`;
|
|
3461
|
+
const markerDef = buildMarkerDef(id, outline.headEnd, color, alpha);
|
|
3462
|
+
if (markerDef) {
|
|
3463
|
+
defs.push(markerDef);
|
|
3464
|
+
startAttr = `marker-start="url(#${id})"`;
|
|
3465
|
+
}
|
|
3466
|
+
}
|
|
3467
|
+
if (outline.tailEnd) {
|
|
3468
|
+
const id = `marker-${crypto.randomUUID()}`;
|
|
3469
|
+
const markerDef = buildMarkerDef(id, outline.tailEnd, color, alpha);
|
|
3470
|
+
if (markerDef) {
|
|
3471
|
+
defs.push(markerDef);
|
|
3472
|
+
endAttr = `marker-end="url(#${id})"`;
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3475
|
+
return { defs: defs.join(""), startAttr, endAttr };
|
|
3476
|
+
}
|
|
3477
|
+
function buildMarkerDef(id, endpoint, color, alpha) {
|
|
3478
|
+
const mw = ARROW_SIZE_MAP[endpoint.length];
|
|
3479
|
+
const mh = ARROW_SIZE_MAP[endpoint.width];
|
|
3480
|
+
const alphaAttr = alpha < 1 ? ` opacity="${alpha}"` : "";
|
|
3481
|
+
let path;
|
|
3482
|
+
let fillAttr2;
|
|
3483
|
+
switch (endpoint.type) {
|
|
3484
|
+
case "triangle":
|
|
3485
|
+
path = `M 0 0 L ${mw} ${mh / 2} L 0 ${mh} Z`;
|
|
3486
|
+
fillAttr2 = `fill="${color}"`;
|
|
3487
|
+
break;
|
|
3488
|
+
case "stealth":
|
|
3489
|
+
path = `M 0 0 L ${mw} ${mh / 2} L 0 ${mh} L ${mw * 0.3} ${mh / 2} Z`;
|
|
3490
|
+
fillAttr2 = `fill="${color}"`;
|
|
3491
|
+
break;
|
|
3492
|
+
case "diamond":
|
|
3493
|
+
path = `M 0 ${mh / 2} L ${mw / 2} 0 L ${mw} ${mh / 2} L ${mw / 2} ${mh} Z`;
|
|
3494
|
+
fillAttr2 = `fill="${color}"`;
|
|
3495
|
+
break;
|
|
3496
|
+
case "oval":
|
|
3497
|
+
return `<marker id="${id}" markerWidth="${mw}" markerHeight="${mh}" refX="${mw / 2}" refY="${mh / 2}" orient="auto" markerUnits="userSpaceOnUse"><ellipse cx="${mw / 2}" cy="${mh / 2}" rx="${mw / 2}" ry="${mh / 2}" ${`fill="${color}"`}${alphaAttr}/></marker>`;
|
|
3498
|
+
case "arrow":
|
|
3499
|
+
path = `M 0 0 L ${mw} ${mh / 2} L 0 ${mh}`;
|
|
3500
|
+
fillAttr2 = `fill="none" stroke="${color}" stroke-width="1"`;
|
|
3501
|
+
break;
|
|
3502
|
+
default:
|
|
3503
|
+
return null;
|
|
3504
|
+
}
|
|
3505
|
+
return `<marker id="${id}" markerWidth="${mw}" markerHeight="${mh}" refX="${mw}" refY="${mh / 2}" orient="auto" markerUnits="userSpaceOnUse"><path d="${path}" ${fillAttr2}${alphaAttr}/></marker>`;
|
|
3506
|
+
}
|
|
2062
3507
|
|
|
2063
3508
|
// src/data/font-metrics.ts
|
|
2064
3509
|
var metricsData = {
|
|
@@ -2912,8 +4357,11 @@ function getMetricsFallbackFont(fontFamily) {
|
|
|
2912
4357
|
// src/utils/text-measure.ts
|
|
2913
4358
|
var WIDTH_RATIO = {
|
|
2914
4359
|
narrow: 0.3,
|
|
4360
|
+
// i, l, 1, 句読点等
|
|
2915
4361
|
normal: 0.6,
|
|
4362
|
+
// ラテン文字の平均的な幅
|
|
2916
4363
|
wide: 1
|
|
4364
|
+
// CJK 文字 (全角)
|
|
2917
4365
|
};
|
|
2918
4366
|
var BOLD_FACTOR = 1.05;
|
|
2919
4367
|
var PX_PER_PT = 96 / 72;
|
|
@@ -2924,7 +4372,10 @@ function getLineHeightRatio(fontFamily, fontFamilyEa) {
|
|
|
2924
4372
|
return (metrics.ascender + Math.abs(metrics.descender)) / metrics.unitsPerEm;
|
|
2925
4373
|
}
|
|
2926
4374
|
function isCjkCodePoint(codePoint) {
|
|
2927
|
-
return codePoint >= 12288 && codePoint <= 40959 ||
|
|
4375
|
+
return codePoint >= 12288 && codePoint <= 40959 || // CJK 記号・ひらがな・カタカナ・統合漢字
|
|
4376
|
+
codePoint >= 63744 && codePoint <= 64255 || // CJK 互換漢字
|
|
4377
|
+
codePoint >= 65281 && codePoint <= 65376 || // 全角英数・記号
|
|
4378
|
+
codePoint >= 131072 && codePoint <= 173791;
|
|
2928
4379
|
}
|
|
2929
4380
|
function categorizeChar(codePoint) {
|
|
2930
4381
|
if (isCjkCodePoint(codePoint)) {
|
|
@@ -2988,6 +4439,7 @@ function measureTextWidth(text, fontSizePt, bold, fontFamily, fontFamilyEa) {
|
|
|
2988
4439
|
|
|
2989
4440
|
// src/utils/text-wrap.ts
|
|
2990
4441
|
var DEFAULT_FONT_SIZE = 18;
|
|
4442
|
+
var WRAP_TOLERANCE_RATIO = 0.02;
|
|
2991
4443
|
function isCjk(codePoint) {
|
|
2992
4444
|
return codePoint >= 12288 && codePoint <= 40959 || codePoint >= 63744 && codePoint <= 64255 || codePoint >= 65281 && codePoint <= 65376 || codePoint >= 131072 && codePoint <= 173791;
|
|
2993
4445
|
}
|
|
@@ -3040,6 +4492,39 @@ function tokenizeRuns(runs, defaultFontSize, fontScale) {
|
|
|
3040
4492
|
let isFirst = true;
|
|
3041
4493
|
for (const run of runs) {
|
|
3042
4494
|
if (run.text.length === 0) continue;
|
|
4495
|
+
if (run.text.includes("\n")) {
|
|
4496
|
+
const parts = run.text.split("\n");
|
|
4497
|
+
for (let pi = 0; pi < parts.length; pi++) {
|
|
4498
|
+
if (pi > 0) {
|
|
4499
|
+
tokens.push({
|
|
4500
|
+
text: "",
|
|
4501
|
+
properties: run.properties,
|
|
4502
|
+
width: 0,
|
|
4503
|
+
breakable: true,
|
|
4504
|
+
forceBreak: true
|
|
4505
|
+
});
|
|
4506
|
+
isFirst = false;
|
|
4507
|
+
}
|
|
4508
|
+
const part = parts[pi];
|
|
4509
|
+
if (part.length === 0) continue;
|
|
4510
|
+
const fontSize2 = run.properties.fontSize ? run.properties.fontSize * fontScale : defaultFontSize;
|
|
4511
|
+
const bold2 = run.properties.bold;
|
|
4512
|
+
const fontFamily2 = run.properties.fontFamily;
|
|
4513
|
+
const fontFamilyEa2 = run.properties.fontFamilyEa;
|
|
4514
|
+
const fragments2 = splitTextIntoFragments(part);
|
|
4515
|
+
for (const { fragment, breakable } of fragments2) {
|
|
4516
|
+
const width = measureTextWidth(fragment, fontSize2, bold2, fontFamily2, fontFamilyEa2);
|
|
4517
|
+
tokens.push({
|
|
4518
|
+
text: fragment,
|
|
4519
|
+
properties: run.properties,
|
|
4520
|
+
width,
|
|
4521
|
+
breakable: isFirst ? false : breakable
|
|
4522
|
+
});
|
|
4523
|
+
isFirst = false;
|
|
4524
|
+
}
|
|
4525
|
+
}
|
|
4526
|
+
continue;
|
|
4527
|
+
}
|
|
3043
4528
|
const fontSize = run.properties.fontSize ? run.properties.fontSize * fontScale : defaultFontSize;
|
|
3044
4529
|
const bold = run.properties.bold;
|
|
3045
4530
|
const fontFamily = run.properties.fontFamily;
|
|
@@ -3124,9 +4609,17 @@ function layoutTokensIntoLines(tokens, availableWidth, defaultFontSize, fontScal
|
|
|
3124
4609
|
const lines = [];
|
|
3125
4610
|
let currentLine = [];
|
|
3126
4611
|
let currentWidth = 0;
|
|
4612
|
+
const tolerance = availableWidth * WRAP_TOLERANCE_RATIO;
|
|
3127
4613
|
for (let i = 0; i < tokens.length; i++) {
|
|
3128
4614
|
const token = tokens[i];
|
|
3129
|
-
if (
|
|
4615
|
+
if (token.forceBreak) {
|
|
4616
|
+
const segments = trimTrailingSpaces(mergeSegments(currentLine));
|
|
4617
|
+
lines.push({ segments: segments.length > 0 ? segments : [] });
|
|
4618
|
+
currentLine = [];
|
|
4619
|
+
currentWidth = 0;
|
|
4620
|
+
continue;
|
|
4621
|
+
}
|
|
4622
|
+
if (currentWidth + token.width <= availableWidth + tolerance) {
|
|
3130
4623
|
currentLine.push(token);
|
|
3131
4624
|
currentWidth += token.width;
|
|
3132
4625
|
} else if (currentLine.length === 0) {
|
|
@@ -3190,17 +4683,31 @@ function renderTextBody(textBody, transform) {
|
|
|
3190
4683
|
const marginBottom = emuToPixels(bodyProperties.marginBottom);
|
|
3191
4684
|
const hasText = paragraphs.some((p) => p.runs.some((r) => r.text.length > 0));
|
|
3192
4685
|
if (!hasText) return "";
|
|
3193
|
-
const
|
|
4686
|
+
const fullTextWidth = width - marginLeft - marginRight;
|
|
4687
|
+
const numCol = bodyProperties.numCol ?? 1;
|
|
4688
|
+
const textWidth = numCol > 1 ? fullTextWidth / numCol : fullTextWidth;
|
|
3194
4689
|
const defaultFontSize = getDefaultFontSize(paragraphs);
|
|
3195
4690
|
const shouldWrap = bodyProperties.wrap !== "none";
|
|
3196
|
-
|
|
4691
|
+
let fontScale = bodyProperties.fontScale;
|
|
3197
4692
|
const lnSpcReduction = bodyProperties.lnSpcReduction;
|
|
4693
|
+
if (bodyProperties.autoFit === "normAutofit" && shouldWrap) {
|
|
4694
|
+
const availableHeight = height - marginTop - marginBottom;
|
|
4695
|
+
fontScale = computeShrinkToFitScale(
|
|
4696
|
+
paragraphs,
|
|
4697
|
+
defaultFontSize,
|
|
4698
|
+
fontScale,
|
|
4699
|
+
lnSpcReduction,
|
|
4700
|
+
textWidth,
|
|
4701
|
+
availableHeight
|
|
4702
|
+
);
|
|
4703
|
+
}
|
|
3198
4704
|
const scaledDefaultFontSize = defaultFontSize * fontScale;
|
|
3199
4705
|
const defaultLineHeightRatio = getDefaultLineHeightRatio(paragraphs);
|
|
3200
4706
|
const defaultNaturalHeight = scaledDefaultFontSize * defaultLineHeightRatio;
|
|
3201
4707
|
const tspans = [];
|
|
3202
4708
|
let isFirstLine = true;
|
|
3203
4709
|
const autoNumCounters = /* @__PURE__ */ new Map();
|
|
4710
|
+
let prevSpaceAfterPx = 0;
|
|
3204
4711
|
for (const para of paragraphs) {
|
|
3205
4712
|
const paraMarginLeft = emuToPixels(para.properties.marginLeft);
|
|
3206
4713
|
const paraIndent = emuToPixels(para.properties.indent);
|
|
@@ -3215,10 +4722,14 @@ function renderTextBody(textBody, transform) {
|
|
|
3215
4722
|
width,
|
|
3216
4723
|
marginRight
|
|
3217
4724
|
);
|
|
4725
|
+
const paraFontSize = getParagraphFontSize(para, defaultFontSize) * fontScale;
|
|
4726
|
+
const spaceBeforePx = resolveSpacingPx(para.properties.spaceBefore, paraFontSize);
|
|
4727
|
+
const paragraphGap = Math.max(prevSpaceAfterPx, spaceBeforePx);
|
|
3218
4728
|
if (para.runs.length === 0 || !para.runs.some((r) => r.text.length > 0)) {
|
|
3219
|
-
const dy = computeDy(isFirstLine, defaultNaturalHeight, DEFAULT_LINE_SPACING,
|
|
4729
|
+
const dy = computeDy(isFirstLine, defaultNaturalHeight, DEFAULT_LINE_SPACING, paragraphGap);
|
|
3220
4730
|
tspans.push(`<tspan x="${xPos}" dy="${dy}" text-anchor="${anchorValue}"> </tspan>`);
|
|
3221
4731
|
isFirstLine = false;
|
|
4732
|
+
prevSpaceAfterPx = resolveSpacingPx(para.properties.spaceAfter, paraFontSize);
|
|
3222
4733
|
continue;
|
|
3223
4734
|
}
|
|
3224
4735
|
if (shouldWrap) {
|
|
@@ -3230,13 +4741,13 @@ function renderTextBody(textBody, transform) {
|
|
|
3230
4741
|
);
|
|
3231
4742
|
for (let lineIdx = 0; lineIdx < wrappedLines.length; lineIdx++) {
|
|
3232
4743
|
const line = wrappedLines[lineIdx];
|
|
4744
|
+
const lineGap = lineIdx === 0 ? paragraphGap : 0;
|
|
3233
4745
|
if (line.segments.length === 0) {
|
|
3234
4746
|
const dy = computeDy(
|
|
3235
4747
|
isFirstLine,
|
|
3236
4748
|
defaultNaturalHeight,
|
|
3237
4749
|
getLineSpacing(para, lnSpcReduction),
|
|
3238
|
-
|
|
3239
|
-
lineIdx === 0
|
|
4750
|
+
lineGap
|
|
3240
4751
|
);
|
|
3241
4752
|
tspans.push(`<tspan x="${xPos}" dy="${dy}" text-anchor="${anchorValue}"> </tspan>`);
|
|
3242
4753
|
isFirstLine = false;
|
|
@@ -3253,8 +4764,7 @@ function renderTextBody(textBody, transform) {
|
|
|
3253
4764
|
isFirstLine,
|
|
3254
4765
|
lineNaturalHeight,
|
|
3255
4766
|
getLineSpacing(para, lnSpcReduction),
|
|
3256
|
-
|
|
3257
|
-
true
|
|
4767
|
+
paragraphGap
|
|
3258
4768
|
);
|
|
3259
4769
|
const bulletStyles = buildBulletStyleAttrs(para.properties, lineFontSize, fontScale);
|
|
3260
4770
|
tspans.push(
|
|
@@ -3278,8 +4788,7 @@ function renderTextBody(textBody, transform) {
|
|
|
3278
4788
|
isFirstLine,
|
|
3279
4789
|
lineNaturalHeight,
|
|
3280
4790
|
getLineSpacing(para, lnSpcReduction),
|
|
3281
|
-
|
|
3282
|
-
lineIdx === 0
|
|
4791
|
+
lineGap
|
|
3283
4792
|
);
|
|
3284
4793
|
const prefix = `x="${xPos}" dy="${dy}" text-anchor="${anchorValue}" `;
|
|
3285
4794
|
tspans.push(renderSegment(seg.text, seg.properties, fontScale, prefix));
|
|
@@ -3300,8 +4809,7 @@ function renderTextBody(textBody, transform) {
|
|
|
3300
4809
|
isFirstLine,
|
|
3301
4810
|
naturalHeight,
|
|
3302
4811
|
getLineSpacing(para, lnSpcReduction),
|
|
3303
|
-
|
|
3304
|
-
true
|
|
4812
|
+
paragraphGap
|
|
3305
4813
|
);
|
|
3306
4814
|
const bulletStyles = buildBulletStyleAttrs(para.properties, fontSize, fontScale);
|
|
3307
4815
|
tspans.push(
|
|
@@ -3321,8 +4829,7 @@ function renderTextBody(textBody, transform) {
|
|
|
3321
4829
|
isFirstLine,
|
|
3322
4830
|
naturalHeight,
|
|
3323
4831
|
getLineSpacing(para, lnSpcReduction),
|
|
3324
|
-
|
|
3325
|
-
true
|
|
4832
|
+
paragraphGap
|
|
3326
4833
|
);
|
|
3327
4834
|
const prefix = `x="${xPos}" dy="${dy}" text-anchor="${anchorValue}" `;
|
|
3328
4835
|
tspans.push(renderSegment(run.text, run.properties, fontScale, prefix));
|
|
@@ -3334,6 +4841,7 @@ function renderTextBody(textBody, transform) {
|
|
|
3334
4841
|
}
|
|
3335
4842
|
isFirstLine = false;
|
|
3336
4843
|
}
|
|
4844
|
+
prevSpaceAfterPx = resolveSpacingPx(para.properties.spaceAfter, paraFontSize);
|
|
3337
4845
|
}
|
|
3338
4846
|
let yStart = marginTop;
|
|
3339
4847
|
const totalTextHeight = estimateTextHeight(
|
|
@@ -3467,13 +4975,24 @@ function getLineSpacing(para, lnSpcReduction = 0) {
|
|
|
3467
4975
|
}
|
|
3468
4976
|
return spacing * (1 - lnSpcReduction);
|
|
3469
4977
|
}
|
|
3470
|
-
function
|
|
4978
|
+
function resolveSpacingPx(spacing, fontSizePt) {
|
|
4979
|
+
if (spacing.type === "pts") {
|
|
4980
|
+
return spacing.value / 100 * PX_PER_PT2;
|
|
4981
|
+
}
|
|
4982
|
+
return fontSizePt * (spacing.value / 1e5) * PX_PER_PT2;
|
|
4983
|
+
}
|
|
4984
|
+
function getParagraphFontSize(para, defaultFontSize) {
|
|
4985
|
+
for (const run of para.runs) {
|
|
4986
|
+
if (run.text.length > 0 && run.properties.fontSize) {
|
|
4987
|
+
return run.properties.fontSize;
|
|
4988
|
+
}
|
|
4989
|
+
}
|
|
4990
|
+
return defaultFontSize;
|
|
4991
|
+
}
|
|
4992
|
+
function computeDy(isFirstLine, fontSizePt, lineSpacingFactor, paragraphGap) {
|
|
3471
4993
|
if (isFirstLine) return "0";
|
|
3472
4994
|
const lineHeight = fontSizePt * PX_PER_PT2 * lineSpacingFactor;
|
|
3473
|
-
|
|
3474
|
-
if (isFirstLineOfParagraph && para.properties.spaceBefore > 0) {
|
|
3475
|
-
dy += para.properties.spaceBefore / 100 * PX_PER_PT2;
|
|
3476
|
-
}
|
|
4995
|
+
const dy = lineHeight + paragraphGap;
|
|
3477
4996
|
return dy.toFixed(2);
|
|
3478
4997
|
}
|
|
3479
4998
|
function getLineFontSize(segments, defaultFontSize) {
|
|
@@ -3586,23 +5105,30 @@ function buildStyleAttrs(props, fontScale = 1, fontFamilies) {
|
|
|
3586
5105
|
return styles.join(" ");
|
|
3587
5106
|
}
|
|
3588
5107
|
function renderSegment(text, props, fontScale, prefix) {
|
|
5108
|
+
let tspanContent;
|
|
3589
5109
|
if (!needsScriptSplit(props)) {
|
|
3590
5110
|
const styles = buildStyleAttrs(props, fontScale);
|
|
3591
|
-
|
|
3592
|
-
}
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
5111
|
+
tspanContent = `<tspan ${prefix}${styles}>${escapeXml(text)}</tspan>`;
|
|
5112
|
+
} else {
|
|
5113
|
+
const parts = splitByScript(text);
|
|
5114
|
+
const result = [];
|
|
5115
|
+
for (let i = 0; i < parts.length; i++) {
|
|
5116
|
+
const part = parts[i];
|
|
5117
|
+
const fonts = part.isEa ? [props.fontFamilyEa, props.fontFamily] : [props.fontFamily, props.fontFamilyEa];
|
|
5118
|
+
const styles = buildStyleAttrs(props, fontScale, fonts);
|
|
5119
|
+
if (i === 0) {
|
|
5120
|
+
result.push(`<tspan ${prefix}${styles}>${escapeXml(part.text)}</tspan>`);
|
|
5121
|
+
} else {
|
|
5122
|
+
result.push(`<tspan ${styles}>${escapeXml(part.text)}</tspan>`);
|
|
5123
|
+
}
|
|
3603
5124
|
}
|
|
5125
|
+
tspanContent = result.join("");
|
|
3604
5126
|
}
|
|
3605
|
-
|
|
5127
|
+
if (props.hyperlink) {
|
|
5128
|
+
const href = escapeXml(props.hyperlink.url);
|
|
5129
|
+
return `<a href="${href}">${tspanContent}</a>`;
|
|
5130
|
+
}
|
|
5131
|
+
return tspanContent;
|
|
3606
5132
|
}
|
|
3607
5133
|
function getDefaultFontSize(paragraphs) {
|
|
3608
5134
|
for (const p of paragraphs) {
|
|
@@ -3622,9 +5148,53 @@ function getDefaultLineHeightRatio(paragraphs) {
|
|
|
3622
5148
|
}
|
|
3623
5149
|
return 1.2;
|
|
3624
5150
|
}
|
|
5151
|
+
function computeSpAutofitHeight(textBody, transform) {
|
|
5152
|
+
const { bodyProperties, paragraphs } = textBody;
|
|
5153
|
+
const hasText = paragraphs.some((p) => p.runs.some((r) => r.text.length > 0));
|
|
5154
|
+
if (!hasText) return null;
|
|
5155
|
+
const width = emuToPixels(transform.extentWidth);
|
|
5156
|
+
const height = emuToPixels(transform.extentHeight);
|
|
5157
|
+
const marginLeft = emuToPixels(bodyProperties.marginLeft);
|
|
5158
|
+
const marginRight = emuToPixels(bodyProperties.marginRight);
|
|
5159
|
+
const marginTop = emuToPixels(bodyProperties.marginTop);
|
|
5160
|
+
const marginBottom = emuToPixels(bodyProperties.marginBottom);
|
|
5161
|
+
const fullTextWidth = width - marginLeft - marginRight;
|
|
5162
|
+
const numCol = bodyProperties.numCol ?? 1;
|
|
5163
|
+
const textWidth = numCol > 1 ? fullTextWidth / numCol : fullTextWidth;
|
|
5164
|
+
const defaultFontSize = getDefaultFontSize(paragraphs);
|
|
5165
|
+
const shouldWrap = bodyProperties.wrap !== "none";
|
|
5166
|
+
const textHeight = estimateTextHeight(paragraphs, defaultFontSize, shouldWrap, textWidth);
|
|
5167
|
+
const requiredHeightPx = textHeight + marginTop + marginBottom;
|
|
5168
|
+
if (requiredHeightPx <= height) return null;
|
|
5169
|
+
const DEFAULT_DPI2 = 96;
|
|
5170
|
+
return requiredHeightPx / DEFAULT_DPI2 * EMU_PER_INCH;
|
|
5171
|
+
}
|
|
5172
|
+
function computeShrinkToFitScale(paragraphs, defaultFontSize, fontScale, lnSpcReduction, textWidth, availableHeight) {
|
|
5173
|
+
if (availableHeight <= 0) return fontScale;
|
|
5174
|
+
const minScale = fontScale * 0.1;
|
|
5175
|
+
let scale = fontScale;
|
|
5176
|
+
const maxIterations = 5;
|
|
5177
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
5178
|
+
const scaledDefault = defaultFontSize * scale;
|
|
5179
|
+
const textHeight = estimateTextHeight(
|
|
5180
|
+
paragraphs,
|
|
5181
|
+
scaledDefault,
|
|
5182
|
+
true,
|
|
5183
|
+
textWidth,
|
|
5184
|
+
lnSpcReduction,
|
|
5185
|
+
scale
|
|
5186
|
+
);
|
|
5187
|
+
if (textHeight <= availableHeight) break;
|
|
5188
|
+
const newScale = scale * (availableHeight / textHeight);
|
|
5189
|
+
scale = Math.max(newScale, minScale);
|
|
5190
|
+
if (scale <= minScale) break;
|
|
5191
|
+
}
|
|
5192
|
+
return scale;
|
|
5193
|
+
}
|
|
3625
5194
|
function estimateTextHeight(paragraphs, defaultFontSize, shouldWrap, textWidth, lnSpcReduction = 0, fontScale = 1) {
|
|
3626
5195
|
let totalHeight = 0;
|
|
3627
5196
|
const defaultRatio = getDefaultLineHeightRatio(paragraphs);
|
|
5197
|
+
let prevSpaceAfterPx = 0;
|
|
3628
5198
|
for (let pIdx = 0; pIdx < paragraphs.length; pIdx++) {
|
|
3629
5199
|
const para = paragraphs[pIdx];
|
|
3630
5200
|
const lineSpacing = getLineSpacing(para, lnSpcReduction);
|
|
@@ -3638,14 +5208,19 @@ function estimateTextHeight(paragraphs, defaultFontSize, shouldWrap, textWidth,
|
|
|
3638
5208
|
lineCount = para.runs.some((r) => r.text.length > 0) ? 1 : 1;
|
|
3639
5209
|
}
|
|
3640
5210
|
totalHeight += lineCount * lineHeight;
|
|
3641
|
-
if (pIdx > 0
|
|
3642
|
-
|
|
5211
|
+
if (pIdx > 0) {
|
|
5212
|
+
const paraFontSize = getParagraphFontSize(para, defaultFontSize) * fontScale;
|
|
5213
|
+
const spaceBeforePx = resolveSpacingPx(para.properties.spaceBefore, paraFontSize);
|
|
5214
|
+
totalHeight += Math.max(prevSpaceAfterPx, spaceBeforePx);
|
|
3643
5215
|
}
|
|
5216
|
+
const paraFontSizeForAfter = getParagraphFontSize(para, defaultFontSize) * fontScale;
|
|
5217
|
+
prevSpaceAfterPx = resolveSpacingPx(para.properties.spaceAfter, paraFontSizeForAfter);
|
|
3644
5218
|
}
|
|
3645
5219
|
return totalHeight;
|
|
3646
5220
|
}
|
|
5221
|
+
var TAB_SPACES = " ";
|
|
3647
5222
|
function escapeXml(str) {
|
|
3648
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
5223
|
+
return str.replace(/\t/g, TAB_SPACES).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
3649
5224
|
}
|
|
3650
5225
|
|
|
3651
5226
|
// src/renderer/effect-renderer.ts
|
|
@@ -3766,28 +5341,41 @@ function buildTransformAttr(t) {
|
|
|
3766
5341
|
// src/renderer/shape-renderer.ts
|
|
3767
5342
|
function renderShape(shape) {
|
|
3768
5343
|
const { transform, geometry, fill, outline, textBody, effects } = shape;
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
5344
|
+
let effectiveTransform = transform;
|
|
5345
|
+
if (textBody?.bodyProperties.autoFit === "spAutofit") {
|
|
5346
|
+
const requiredHeightEmu = computeSpAutofitHeight(textBody, transform);
|
|
5347
|
+
if (requiredHeightEmu !== null) {
|
|
5348
|
+
effectiveTransform = { ...transform, extentHeight: requiredHeightEmu };
|
|
5349
|
+
}
|
|
5350
|
+
}
|
|
5351
|
+
const w = emuToPixels(effectiveTransform.extentWidth);
|
|
5352
|
+
const h = emuToPixels(effectiveTransform.extentHeight);
|
|
5353
|
+
const transformAttr = buildTransformAttr(effectiveTransform);
|
|
3772
5354
|
const fillResult = renderFillAttrs(fill);
|
|
3773
|
-
const
|
|
5355
|
+
const outlineResult = renderOutlineAttrs(outline);
|
|
3774
5356
|
const effectResult = renderEffects(effects);
|
|
3775
5357
|
const geometrySvg = renderGeometry(geometry, w, h);
|
|
3776
5358
|
const parts = [];
|
|
3777
5359
|
if (fillResult.defs) {
|
|
3778
5360
|
parts.push(fillResult.defs);
|
|
3779
5361
|
}
|
|
5362
|
+
if (outlineResult.defs) {
|
|
5363
|
+
parts.push(outlineResult.defs);
|
|
5364
|
+
}
|
|
3780
5365
|
if (effectResult.filterDefs) {
|
|
3781
5366
|
parts.push(effectResult.filterDefs);
|
|
3782
5367
|
}
|
|
3783
5368
|
const filterAttr = effectResult.filterAttr ? ` ${effectResult.filterAttr}` : "";
|
|
3784
5369
|
parts.push(`<g transform="${transformAttr}"${filterAttr}>`);
|
|
3785
5370
|
if (geometrySvg) {
|
|
3786
|
-
const styledGeometry = geometrySvg.replace(
|
|
5371
|
+
const styledGeometry = geometrySvg.replace(
|
|
5372
|
+
/^<(\w+)/,
|
|
5373
|
+
`<$1 ${fillResult.attrs} ${outlineResult.attrs}`
|
|
5374
|
+
);
|
|
3787
5375
|
parts.push(styledGeometry);
|
|
3788
5376
|
}
|
|
3789
5377
|
if (textBody) {
|
|
3790
|
-
const textSvg = renderTextBody(textBody,
|
|
5378
|
+
const textSvg = renderTextBody(textBody, effectiveTransform);
|
|
3791
5379
|
if (textSvg) {
|
|
3792
5380
|
parts.push(textSvg);
|
|
3793
5381
|
}
|
|
@@ -3796,20 +5384,32 @@ function renderShape(shape) {
|
|
|
3796
5384
|
return parts.join("");
|
|
3797
5385
|
}
|
|
3798
5386
|
function renderConnector(connector) {
|
|
3799
|
-
const { transform, outline, effects } = connector;
|
|
5387
|
+
const { transform, geometry, outline, effects } = connector;
|
|
3800
5388
|
const w = emuToPixels(transform.extentWidth);
|
|
3801
5389
|
const h = emuToPixels(transform.extentHeight);
|
|
3802
5390
|
const transformAttr = buildTransformAttr(transform);
|
|
3803
|
-
const
|
|
5391
|
+
const outlineResult = renderOutlineAttrs(outline);
|
|
3804
5392
|
const effectResult = renderEffects(effects);
|
|
5393
|
+
const markerResult = renderMarkers(outline);
|
|
3805
5394
|
const parts = [];
|
|
3806
|
-
if (
|
|
3807
|
-
|
|
3808
|
-
|
|
5395
|
+
if (outlineResult.defs) parts.push(outlineResult.defs);
|
|
5396
|
+
if (markerResult.defs) parts.push(markerResult.defs);
|
|
5397
|
+
if (effectResult.filterDefs) parts.push(effectResult.filterDefs);
|
|
3809
5398
|
const filterAttr = effectResult.filterAttr ? ` ${effectResult.filterAttr}` : "";
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
);
|
|
5399
|
+
const markerAttrs = [markerResult.startAttr, markerResult.endAttr].filter(Boolean).join(" ");
|
|
5400
|
+
const markerAttrStr = markerAttrs ? ` ${markerAttrs}` : "";
|
|
5401
|
+
const geometrySvg = renderGeometry(geometry, w, h);
|
|
5402
|
+
parts.push(`<g transform="${transformAttr}"${filterAttr}>`);
|
|
5403
|
+
if (geometrySvg) {
|
|
5404
|
+
parts.push(
|
|
5405
|
+
geometrySvg.replace(/^<(\w+)/, `<$1 ${outlineResult.attrs} fill="none"${markerAttrStr}`)
|
|
5406
|
+
);
|
|
5407
|
+
} else {
|
|
5408
|
+
parts.push(
|
|
5409
|
+
`<line x1="0" y1="0" x2="${w}" y2="${h}" ${outlineResult.attrs} fill="none"${markerAttrStr}/>`
|
|
5410
|
+
);
|
|
5411
|
+
}
|
|
5412
|
+
parts.push("</g>");
|
|
3813
5413
|
return parts.join("");
|
|
3814
5414
|
}
|
|
3815
5415
|
|
|
@@ -3824,9 +5424,24 @@ function renderImage(image) {
|
|
|
3824
5424
|
parts.push(effectResult.filterDefs);
|
|
3825
5425
|
}
|
|
3826
5426
|
const filterAttr = effectResult.filterAttr ? ` ${effectResult.filterAttr}` : "";
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
5427
|
+
const src = image.srcRect;
|
|
5428
|
+
if (src) {
|
|
5429
|
+
const clipId = `crop-${crypto.randomUUID()}`;
|
|
5430
|
+
const scaledW = Math.round(w / (1 - src.left - src.right));
|
|
5431
|
+
const scaledH = Math.round(h / (1 - src.top - src.bottom));
|
|
5432
|
+
const imgX = Math.round(-src.left * scaledW);
|
|
5433
|
+
const imgY = Math.round(-src.top * scaledH);
|
|
5434
|
+
parts.push(
|
|
5435
|
+
`<defs><clipPath id="${clipId}"><rect x="0" y="0" width="${w}" height="${h}"/></clipPath></defs>`
|
|
5436
|
+
);
|
|
5437
|
+
parts.push(
|
|
5438
|
+
`<g transform="${transformAttr}"${filterAttr}><image clip-path="url(#${clipId})" href="data:${image.mimeType};base64,${image.imageData}" x="${imgX}" y="${imgY}" width="${scaledW}" height="${scaledH}" preserveAspectRatio="none"/></g>`
|
|
5439
|
+
);
|
|
5440
|
+
} else {
|
|
5441
|
+
parts.push(
|
|
5442
|
+
`<g transform="${transformAttr}"${filterAttr}><image href="data:${image.mimeType};base64,${image.imageData}" width="${w}" height="${h}" preserveAspectRatio="none"/></g>`
|
|
5443
|
+
);
|
|
5444
|
+
}
|
|
3830
5445
|
return parts.join("");
|
|
3831
5446
|
}
|
|
3832
5447
|
|
|
@@ -3873,9 +5488,21 @@ function renderChart(element) {
|
|
|
3873
5488
|
case "pie":
|
|
3874
5489
|
parts.push(renderPieChart(chart, plotX, plotY, plotW, plotH));
|
|
3875
5490
|
break;
|
|
5491
|
+
case "doughnut":
|
|
5492
|
+
parts.push(renderDoughnutChart(chart, plotX, plotY, plotW, plotH));
|
|
5493
|
+
break;
|
|
3876
5494
|
case "scatter":
|
|
3877
5495
|
parts.push(renderScatterChart(chart, plotX, plotY, plotW, plotH));
|
|
3878
5496
|
break;
|
|
5497
|
+
case "bubble":
|
|
5498
|
+
parts.push(renderBubbleChart(chart, plotX, plotY, plotW, plotH));
|
|
5499
|
+
break;
|
|
5500
|
+
case "area":
|
|
5501
|
+
parts.push(renderAreaChart(chart, plotX, plotY, plotW, plotH));
|
|
5502
|
+
break;
|
|
5503
|
+
case "radar":
|
|
5504
|
+
parts.push(renderRadarChart(chart, plotX, plotY, plotW, plotH));
|
|
5505
|
+
break;
|
|
3879
5506
|
}
|
|
3880
5507
|
}
|
|
3881
5508
|
if (chart.legend && chart.series.length > 0) {
|
|
@@ -3991,6 +5618,47 @@ function renderLineChart(chart, x, y, w, h) {
|
|
|
3991
5618
|
}
|
|
3992
5619
|
return parts.join("");
|
|
3993
5620
|
}
|
|
5621
|
+
function renderAreaChart(chart, x, y, w, h) {
|
|
5622
|
+
const parts = [];
|
|
5623
|
+
const { series, categories } = chart;
|
|
5624
|
+
if (series.length === 0) return "";
|
|
5625
|
+
const maxVal = getMaxValue(series);
|
|
5626
|
+
if (maxVal === 0) return "";
|
|
5627
|
+
const catCount = categories.length || Math.max(...series.map((s) => s.values.length));
|
|
5628
|
+
if (catCount === 0) return "";
|
|
5629
|
+
parts.push(
|
|
5630
|
+
`<line x1="${round2(x)}" y1="${round2(y + h)}" x2="${round2(x + w)}" y2="${round2(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
|
|
5631
|
+
);
|
|
5632
|
+
parts.push(
|
|
5633
|
+
`<line x1="${round2(x)}" y1="${round2(y)}" x2="${round2(x)}" y2="${round2(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
|
|
5634
|
+
);
|
|
5635
|
+
for (let c = 0; c < catCount; c++) {
|
|
5636
|
+
const label = categories[c] ?? "";
|
|
5637
|
+
const divisor = catCount > 1 ? catCount - 1 : 1;
|
|
5638
|
+
const labelX = x + c / divisor * w;
|
|
5639
|
+
parts.push(
|
|
5640
|
+
`<text x="${round2(labelX)}" y="${round2(y + h + 15)}" text-anchor="middle" font-size="10" fill="#595959">${escapeXml2(label)}</text>`
|
|
5641
|
+
);
|
|
5642
|
+
}
|
|
5643
|
+
const baseline = round2(y + h);
|
|
5644
|
+
for (let s = 0; s < series.length; s++) {
|
|
5645
|
+
const color = series[s].color;
|
|
5646
|
+
const divisor = catCount > 1 ? catCount - 1 : 1;
|
|
5647
|
+
const dataPoints = series[s].values.map((val, i) => {
|
|
5648
|
+
const px = round2(x + i / divisor * w);
|
|
5649
|
+
const py = round2(y + h - val / maxVal * h);
|
|
5650
|
+
return { px, py };
|
|
5651
|
+
});
|
|
5652
|
+
const topPoints = dataPoints.map((p) => `${p.px},${p.py}`).join(" ");
|
|
5653
|
+
const lastX = dataPoints[dataPoints.length - 1].px;
|
|
5654
|
+
const firstX = dataPoints[0].px;
|
|
5655
|
+
const polygonPoints = `${topPoints} ${lastX},${baseline} ${firstX},${baseline}`;
|
|
5656
|
+
parts.push(
|
|
5657
|
+
`<polygon points="${polygonPoints}" fill="${color.hex}" fill-opacity="${color.alpha < 1 ? color.alpha : 0.5}" stroke="${color.hex}" stroke-width="2"${color.alpha < 1 ? ` stroke-opacity="${color.alpha}"` : ""}/>`
|
|
5658
|
+
);
|
|
5659
|
+
}
|
|
5660
|
+
return parts.join("");
|
|
5661
|
+
}
|
|
3994
5662
|
function renderPieChart(chart, x, y, w, h) {
|
|
3995
5663
|
const parts = [];
|
|
3996
5664
|
const series = chart.series[0];
|
|
@@ -4023,6 +5691,47 @@ function renderPieChart(chart, x, y, w, h) {
|
|
|
4023
5691
|
}
|
|
4024
5692
|
return parts.join("");
|
|
4025
5693
|
}
|
|
5694
|
+
function renderDoughnutChart(chart, x, y, w, h) {
|
|
5695
|
+
const parts = [];
|
|
5696
|
+
const series = chart.series[0];
|
|
5697
|
+
if (!series || series.values.length === 0) return "";
|
|
5698
|
+
const total = series.values.reduce((sum, v) => sum + v, 0);
|
|
5699
|
+
if (total === 0) return "";
|
|
5700
|
+
const cx = x + w / 2;
|
|
5701
|
+
const cy = y + h / 2;
|
|
5702
|
+
const outerR = Math.min(w, h) / 2 * 0.85;
|
|
5703
|
+
const holeSize = chart.holeSize ?? 50;
|
|
5704
|
+
const innerR = outerR * (holeSize / 100);
|
|
5705
|
+
let currentAngle = -Math.PI / 2;
|
|
5706
|
+
for (let i = 0; i < series.values.length; i++) {
|
|
5707
|
+
const val = series.values[i];
|
|
5708
|
+
const sliceAngle = val / total * 2 * Math.PI;
|
|
5709
|
+
const color = getPieSliceColor(i, chart);
|
|
5710
|
+
if (series.values.length === 1) {
|
|
5711
|
+
parts.push(
|
|
5712
|
+
`<circle cx="${round2(cx)}" cy="${round2(cy)}" r="${round2(outerR)}" ${fillAttr(color)}/>`
|
|
5713
|
+
);
|
|
5714
|
+
parts.push(
|
|
5715
|
+
`<circle cx="${round2(cx)}" cy="${round2(cy)}" r="${round2(innerR)}" fill="#FFFFFF"/>`
|
|
5716
|
+
);
|
|
5717
|
+
} else {
|
|
5718
|
+
const ox1 = cx + outerR * Math.cos(currentAngle);
|
|
5719
|
+
const oy1 = cy + outerR * Math.sin(currentAngle);
|
|
5720
|
+
const ox2 = cx + outerR * Math.cos(currentAngle + sliceAngle);
|
|
5721
|
+
const oy2 = cy + outerR * Math.sin(currentAngle + sliceAngle);
|
|
5722
|
+
const ix1 = cx + innerR * Math.cos(currentAngle + sliceAngle);
|
|
5723
|
+
const iy1 = cy + innerR * Math.sin(currentAngle + sliceAngle);
|
|
5724
|
+
const ix2 = cx + innerR * Math.cos(currentAngle);
|
|
5725
|
+
const iy2 = cy + innerR * Math.sin(currentAngle);
|
|
5726
|
+
const largeArc = sliceAngle > Math.PI ? 1 : 0;
|
|
5727
|
+
parts.push(
|
|
5728
|
+
`<path d="M${round2(ox1)},${round2(oy1)} A${round2(outerR)},${round2(outerR)} 0 ${largeArc},1 ${round2(ox2)},${round2(oy2)} L${round2(ix1)},${round2(iy1)} A${round2(innerR)},${round2(innerR)} 0 ${largeArc},0 ${round2(ix2)},${round2(iy2)} Z" ${fillAttr(color)}/>`
|
|
5729
|
+
);
|
|
5730
|
+
}
|
|
5731
|
+
currentAngle += sliceAngle;
|
|
5732
|
+
}
|
|
5733
|
+
return parts.join("");
|
|
5734
|
+
}
|
|
4026
5735
|
function renderScatterChart(chart, x, y, w, h) {
|
|
4027
5736
|
const parts = [];
|
|
4028
5737
|
const { series } = chart;
|
|
@@ -4055,27 +5764,137 @@ function renderScatterChart(chart, x, y, w, h) {
|
|
|
4055
5764
|
}
|
|
4056
5765
|
return parts.join("");
|
|
4057
5766
|
}
|
|
5767
|
+
function renderBubbleChart(chart, x, y, w, h) {
|
|
5768
|
+
const parts = [];
|
|
5769
|
+
const { series } = chart;
|
|
5770
|
+
if (series.length === 0) return "";
|
|
5771
|
+
let maxX = 0;
|
|
5772
|
+
let maxY = 0;
|
|
5773
|
+
let maxBubble = 0;
|
|
5774
|
+
for (const s of series) {
|
|
5775
|
+
const xVals = s.xValues ?? [];
|
|
5776
|
+
for (const v of xVals) maxX = Math.max(maxX, v);
|
|
5777
|
+
for (const v of s.values) maxY = Math.max(maxY, v);
|
|
5778
|
+
const sizes = s.bubbleSizes ?? [];
|
|
5779
|
+
for (const v of sizes) maxBubble = Math.max(maxBubble, v);
|
|
5780
|
+
}
|
|
5781
|
+
if (maxX === 0) maxX = 1;
|
|
5782
|
+
if (maxY === 0) maxY = 1;
|
|
5783
|
+
if (maxBubble === 0) maxBubble = 1;
|
|
5784
|
+
const maxRadius = Math.min(w, h) * 0.08;
|
|
5785
|
+
parts.push(
|
|
5786
|
+
`<line x1="${round2(x)}" y1="${round2(y + h)}" x2="${round2(x + w)}" y2="${round2(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
|
|
5787
|
+
);
|
|
5788
|
+
parts.push(
|
|
5789
|
+
`<line x1="${round2(x)}" y1="${round2(y)}" x2="${round2(x)}" y2="${round2(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
|
|
5790
|
+
);
|
|
5791
|
+
for (let s = 0; s < series.length; s++) {
|
|
5792
|
+
const color = series[s].color;
|
|
5793
|
+
const xVals = series[s].xValues ?? [];
|
|
5794
|
+
const sizes = series[s].bubbleSizes ?? [];
|
|
5795
|
+
for (let i = 0; i < series[s].values.length; i++) {
|
|
5796
|
+
const xVal = xVals[i] ?? i;
|
|
5797
|
+
const yVal = series[s].values[i];
|
|
5798
|
+
const size = sizes[i] ?? 1;
|
|
5799
|
+
const px = x + xVal / maxX * w;
|
|
5800
|
+
const py = y + h - yVal / maxY * h;
|
|
5801
|
+
const r = Math.max(2, Math.sqrt(size / maxBubble) * maxRadius);
|
|
5802
|
+
parts.push(
|
|
5803
|
+
`<circle cx="${round2(px)}" cy="${round2(py)}" r="${round2(r)}" ${fillAttr(color)} fill-opacity="0.6"/>`
|
|
5804
|
+
);
|
|
5805
|
+
}
|
|
5806
|
+
}
|
|
5807
|
+
return parts.join("");
|
|
5808
|
+
}
|
|
5809
|
+
function renderRadarChart(chart, x, y, w, h) {
|
|
5810
|
+
const parts = [];
|
|
5811
|
+
const { series, categories } = chart;
|
|
5812
|
+
if (series.length === 0) return "";
|
|
5813
|
+
const maxVal = getMaxValue(series);
|
|
5814
|
+
if (maxVal === 0) return "";
|
|
5815
|
+
const catCount = categories.length || Math.max(...series.map((s) => s.values.length));
|
|
5816
|
+
if (catCount === 0) return "";
|
|
5817
|
+
const cx = x + w / 2;
|
|
5818
|
+
const cy = y + h / 2;
|
|
5819
|
+
const radius = Math.min(w, h) / 2 * 0.85;
|
|
5820
|
+
const gridLevels = 5;
|
|
5821
|
+
for (let level = 1; level <= gridLevels; level++) {
|
|
5822
|
+
const r = radius / gridLevels * level;
|
|
5823
|
+
parts.push(
|
|
5824
|
+
`<circle cx="${round2(cx)}" cy="${round2(cy)}" r="${round2(r)}" fill="none" stroke="#D9D9D9" stroke-width="0.5"/>`
|
|
5825
|
+
);
|
|
5826
|
+
}
|
|
5827
|
+
for (let i = 0; i < catCount; i++) {
|
|
5828
|
+
const angle = i / catCount * 2 * Math.PI - Math.PI / 2;
|
|
5829
|
+
const ax = cx + radius * Math.cos(angle);
|
|
5830
|
+
const ay = cy + radius * Math.sin(angle);
|
|
5831
|
+
parts.push(
|
|
5832
|
+
`<line x1="${round2(cx)}" y1="${round2(cy)}" x2="${round2(ax)}" y2="${round2(ay)}" stroke="#D9D9D9" stroke-width="0.5"/>`
|
|
5833
|
+
);
|
|
5834
|
+
const label = categories[i] ?? "";
|
|
5835
|
+
if (label) {
|
|
5836
|
+
const labelR = radius + 12;
|
|
5837
|
+
const lx = cx + labelR * Math.cos(angle);
|
|
5838
|
+
const ly = cy + labelR * Math.sin(angle);
|
|
5839
|
+
const anchor = Math.abs(Math.cos(angle)) < 0.01 ? "middle" : Math.cos(angle) > 0 ? "start" : "end";
|
|
5840
|
+
parts.push(
|
|
5841
|
+
`<text x="${round2(lx)}" y="${round2(ly + 4)}" text-anchor="${anchor}" font-size="10" fill="#595959">${escapeXml2(label)}</text>`
|
|
5842
|
+
);
|
|
5843
|
+
}
|
|
5844
|
+
}
|
|
5845
|
+
const isFilled = chart.radarStyle === "filled";
|
|
5846
|
+
const showMarkers = chart.radarStyle === "marker";
|
|
5847
|
+
for (let s = 0; s < series.length; s++) {
|
|
5848
|
+
const color = series[s].color;
|
|
5849
|
+
const points = [];
|
|
5850
|
+
const coords = [];
|
|
5851
|
+
for (let i = 0; i < catCount; i++) {
|
|
5852
|
+
const val = series[s].values[i] ?? 0;
|
|
5853
|
+
const angle = i / catCount * 2 * Math.PI - Math.PI / 2;
|
|
5854
|
+
const r = val / maxVal * radius;
|
|
5855
|
+
const px = cx + r * Math.cos(angle);
|
|
5856
|
+
const py = cy + r * Math.sin(angle);
|
|
5857
|
+
points.push(`${round2(px)},${round2(py)}`);
|
|
5858
|
+
coords.push({ px, py });
|
|
5859
|
+
}
|
|
5860
|
+
if (isFilled) {
|
|
5861
|
+
parts.push(
|
|
5862
|
+
`<polygon points="${points.join(" ")}" fill="${color.hex}" fill-opacity="0.3" stroke="${color.hex}" stroke-width="2"${color.alpha < 1 ? ` stroke-opacity="${color.alpha}"` : ""}/>`
|
|
5863
|
+
);
|
|
5864
|
+
} else {
|
|
5865
|
+
parts.push(
|
|
5866
|
+
`<polygon points="${points.join(" ")}" fill="none" stroke="${color.hex}" stroke-width="2"${color.alpha < 1 ? ` stroke-opacity="${color.alpha}"` : ""}/>`
|
|
5867
|
+
);
|
|
5868
|
+
}
|
|
5869
|
+
if (showMarkers) {
|
|
5870
|
+
for (const { px, py } of coords) {
|
|
5871
|
+
parts.push(`<circle cx="${round2(px)}" cy="${round2(py)}" r="3" ${fillAttr(color)}/>`);
|
|
5872
|
+
}
|
|
5873
|
+
}
|
|
5874
|
+
}
|
|
5875
|
+
return parts.join("");
|
|
5876
|
+
}
|
|
4058
5877
|
function renderLegend(chart, chartW, chartH, position) {
|
|
4059
5878
|
const parts = [];
|
|
4060
|
-
const
|
|
5879
|
+
const entries2 = chart.chartType === "pie" || chart.chartType === "doughnut" ? chart.categories.map((cat, i) => ({
|
|
4061
5880
|
label: cat,
|
|
4062
5881
|
color: getPieSliceColor(i, chart)
|
|
4063
5882
|
})) : chart.series.map((s, i) => ({
|
|
4064
5883
|
label: s.name ?? `Series ${i + 1}`,
|
|
4065
5884
|
color: s.color
|
|
4066
5885
|
}));
|
|
4067
|
-
if (
|
|
5886
|
+
if (entries2.length === 0) return "";
|
|
4068
5887
|
const entryWidth = 80;
|
|
4069
|
-
const totalWidth =
|
|
5888
|
+
const totalWidth = entries2.length * entryWidth;
|
|
4070
5889
|
const startX = Math.max((chartW - totalWidth) / 2, 5);
|
|
4071
5890
|
const legendY = position === "t" ? 25 : chartH - 15;
|
|
4072
|
-
for (let i = 0; i <
|
|
5891
|
+
for (let i = 0; i < entries2.length; i++) {
|
|
4073
5892
|
const ex = startX + i * entryWidth;
|
|
4074
5893
|
parts.push(
|
|
4075
|
-
`<rect x="${round2(ex)}" y="${round2(legendY - 6)}" width="12" height="12" ${fillAttr(
|
|
5894
|
+
`<rect x="${round2(ex)}" y="${round2(legendY - 6)}" width="12" height="12" ${fillAttr(entries2[i].color)}/>`
|
|
4076
5895
|
);
|
|
4077
5896
|
parts.push(
|
|
4078
|
-
`<text x="${round2(ex + 16)}" y="${round2(legendY + 4)}" font-size="10" fill="#595959">${escapeXml2(
|
|
5897
|
+
`<text x="${round2(ex + 16)}" y="${round2(legendY + 4)}" font-size="10" fill="#595959">${escapeXml2(entries2[i].label)}</text>`
|
|
4079
5898
|
);
|
|
4080
5899
|
}
|
|
4081
5900
|
return parts.join("");
|
|
@@ -4136,23 +5955,27 @@ function renderTable(element, defs) {
|
|
|
4136
5955
|
);
|
|
4137
5956
|
if (cell.borders) {
|
|
4138
5957
|
if (cell.borders.top) {
|
|
4139
|
-
const
|
|
4140
|
-
|
|
5958
|
+
const topResult = renderOutlineAttrs(cell.borders.top);
|
|
5959
|
+
if (topResult.defs) defs.push(topResult.defs);
|
|
5960
|
+
parts.push(`<line x1="${x}" y1="${y}" x2="${x + cellW}" y2="${y}" ${topResult.attrs}/>`);
|
|
4141
5961
|
}
|
|
4142
5962
|
if (cell.borders.bottom) {
|
|
4143
|
-
const
|
|
5963
|
+
const bottomResult = renderOutlineAttrs(cell.borders.bottom);
|
|
5964
|
+
if (bottomResult.defs) defs.push(bottomResult.defs);
|
|
4144
5965
|
parts.push(
|
|
4145
|
-
`<line x1="${x}" y1="${y + cellH}" x2="${x + cellW}" y2="${y + cellH}" ${attrs}/>`
|
|
5966
|
+
`<line x1="${x}" y1="${y + cellH}" x2="${x + cellW}" y2="${y + cellH}" ${bottomResult.attrs}/>`
|
|
4146
5967
|
);
|
|
4147
5968
|
}
|
|
4148
5969
|
if (cell.borders.left) {
|
|
4149
|
-
const
|
|
4150
|
-
|
|
5970
|
+
const leftResult = renderOutlineAttrs(cell.borders.left);
|
|
5971
|
+
if (leftResult.defs) defs.push(leftResult.defs);
|
|
5972
|
+
parts.push(`<line x1="${x}" y1="${y}" x2="${x}" y2="${y + cellH}" ${leftResult.attrs}/>`);
|
|
4151
5973
|
}
|
|
4152
5974
|
if (cell.borders.right) {
|
|
4153
|
-
const
|
|
5975
|
+
const rightResult = renderOutlineAttrs(cell.borders.right);
|
|
5976
|
+
if (rightResult.defs) defs.push(rightResult.defs);
|
|
4154
5977
|
parts.push(
|
|
4155
|
-
`<line x1="${x + cellW}" y1="${y}" x2="${x + cellW}" y2="${y + cellH}" ${attrs}/>`
|
|
5978
|
+
`<line x1="${x + cellW}" y1="${y}" x2="${x + cellW}" y2="${y + cellH}" ${rightResult.attrs}/>`
|
|
4156
5979
|
);
|
|
4157
5980
|
}
|
|
4158
5981
|
}
|
|
@@ -4259,10 +6082,23 @@ function renderGroup(group, defs) {
|
|
|
4259
6082
|
const chY = emuToPixels(group.childTransform.offsetY);
|
|
4260
6083
|
const scaleX = chW !== 0 ? w / chW : 1;
|
|
4261
6084
|
const scaleY = chH !== 0 ? h / chH : 1;
|
|
6085
|
+
const transformParts = [];
|
|
6086
|
+
transformParts.push(`translate(${x}, ${y})`);
|
|
6087
|
+
if (group.transform.rotation !== 0) {
|
|
6088
|
+
transformParts.push(`rotate(${group.transform.rotation}, ${w / 2}, ${h / 2})`);
|
|
6089
|
+
}
|
|
6090
|
+
if (group.transform.flipH || group.transform.flipV) {
|
|
6091
|
+
const sx = group.transform.flipH ? -1 : 1;
|
|
6092
|
+
const sy = group.transform.flipV ? -1 : 1;
|
|
6093
|
+
transformParts.push(
|
|
6094
|
+
`translate(${group.transform.flipH ? w : 0}, ${group.transform.flipV ? h : 0})`
|
|
6095
|
+
);
|
|
6096
|
+
transformParts.push(`scale(${sx}, ${sy})`);
|
|
6097
|
+
}
|
|
6098
|
+
transformParts.push(`scale(${scaleX}, ${scaleY})`);
|
|
6099
|
+
transformParts.push(`translate(${-chX}, ${-chY})`);
|
|
4262
6100
|
const parts = [];
|
|
4263
|
-
parts.push(
|
|
4264
|
-
`<g transform="translate(${x}, ${y}) scale(${scaleX}, ${scaleY}) translate(${-chX}, ${-chY})">`
|
|
4265
|
-
);
|
|
6101
|
+
parts.push(`<g transform="${transformParts.join(" ")}">`);
|
|
4266
6102
|
for (const child of group.children) {
|
|
4267
6103
|
const rendered = renderElement(child, defs);
|
|
4268
6104
|
if (rendered) parts.push(rendered);
|
|
@@ -4271,17 +6107,29 @@ function renderGroup(group, defs) {
|
|
|
4271
6107
|
return parts.join("");
|
|
4272
6108
|
}
|
|
4273
6109
|
function extractDefs(svgFragment, defs) {
|
|
4274
|
-
const
|
|
4275
|
-
if (
|
|
4276
|
-
defs.push(...
|
|
6110
|
+
const linearGradientMatch = svgFragment.match(/<linearGradient[^]*?<\/linearGradient>/g);
|
|
6111
|
+
if (linearGradientMatch) {
|
|
6112
|
+
defs.push(...linearGradientMatch);
|
|
6113
|
+
}
|
|
6114
|
+
const radialGradientMatch = svgFragment.match(/<radialGradient[^]*?<\/radialGradient>/g);
|
|
6115
|
+
if (radialGradientMatch) {
|
|
6116
|
+
defs.push(...radialGradientMatch);
|
|
6117
|
+
}
|
|
6118
|
+
const patternMatch = svgFragment.match(/<pattern[^]*?<\/pattern>/g);
|
|
6119
|
+
if (patternMatch) {
|
|
6120
|
+
defs.push(...patternMatch);
|
|
4277
6121
|
}
|
|
4278
6122
|
const filterMatch = svgFragment.match(/<filter[^]*?<\/filter>/g);
|
|
4279
6123
|
if (filterMatch) {
|
|
4280
6124
|
defs.push(...filterMatch);
|
|
4281
6125
|
}
|
|
6126
|
+
const markerMatch = svgFragment.match(/<marker[^]*?<\/marker>/g);
|
|
6127
|
+
if (markerMatch) {
|
|
6128
|
+
defs.push(...markerMatch);
|
|
6129
|
+
}
|
|
4282
6130
|
}
|
|
4283
6131
|
function removeDefs(svgFragment) {
|
|
4284
|
-
return svgFragment.replace(/<linearGradient[^]*?<\/linearGradient>/g, "").replace(/<filter[^]*?<\/filter>/g, "");
|
|
6132
|
+
return svgFragment.replace(/<linearGradient[^]*?<\/linearGradient>/g, "").replace(/<radialGradient[^]*?<\/radialGradient>/g, "").replace(/<pattern[^]*?<\/pattern>/g, "").replace(/<filter[^]*?<\/filter>/g, "").replace(/<marker[^]*?<\/marker>/g, "");
|
|
4285
6133
|
}
|
|
4286
6134
|
|
|
4287
6135
|
// src/png/png-converter.ts
|
|
@@ -4305,8 +6153,88 @@ async function svgToPng(svgString, options) {
|
|
|
4305
6153
|
};
|
|
4306
6154
|
}
|
|
4307
6155
|
|
|
6156
|
+
// src/text-style-resolver.ts
|
|
6157
|
+
function applyTextStyleInheritance(elements, context) {
|
|
6158
|
+
for (const element of elements) {
|
|
6159
|
+
if (element.type === "shape") {
|
|
6160
|
+
resolveShapeTextInheritance(element, context);
|
|
6161
|
+
} else if (element.type === "group") {
|
|
6162
|
+
applyTextStyleInheritance(element.children, context);
|
|
6163
|
+
}
|
|
6164
|
+
}
|
|
6165
|
+
}
|
|
6166
|
+
function resolveShapeTextInheritance(shape, context) {
|
|
6167
|
+
if (!shape.textBody) return;
|
|
6168
|
+
const layoutLstStyle = findMatchingPlaceholderStyle(
|
|
6169
|
+
shape.placeholderType,
|
|
6170
|
+
shape.placeholderIdx,
|
|
6171
|
+
context.layoutPlaceholderStyles
|
|
6172
|
+
);
|
|
6173
|
+
const masterLstStyle = findMatchingPlaceholderStyle(
|
|
6174
|
+
shape.placeholderType,
|
|
6175
|
+
shape.placeholderIdx,
|
|
6176
|
+
context.masterPlaceholderStyles
|
|
6177
|
+
);
|
|
6178
|
+
const txStyle = getTxStyleForPlaceholder(shape.placeholderType, context.txStyles);
|
|
6179
|
+
const chainSources = [layoutLstStyle, masterLstStyle, txStyle, context.defaultTextStyle];
|
|
6180
|
+
for (const paragraph of shape.textBody.paragraphs) {
|
|
6181
|
+
const level = paragraph.properties.level;
|
|
6182
|
+
for (const run of paragraph.runs) {
|
|
6183
|
+
const props = run.properties;
|
|
6184
|
+
for (const source of chainSources) {
|
|
6185
|
+
if (props.fontSize !== null && props.fontFamily !== null && props.fontFamilyEa !== null && props.color !== null) {
|
|
6186
|
+
break;
|
|
6187
|
+
}
|
|
6188
|
+
const defRPr = getDefRPrFromStyle(source, level);
|
|
6189
|
+
if (!defRPr) continue;
|
|
6190
|
+
if (props.fontSize === null && defRPr.fontSize !== void 0) {
|
|
6191
|
+
props.fontSize = defRPr.fontSize;
|
|
6192
|
+
}
|
|
6193
|
+
if (props.fontFamily === null && defRPr.fontFamily != null) {
|
|
6194
|
+
props.fontFamily = resolveThemeFont(defRPr.fontFamily, context.fontScheme);
|
|
6195
|
+
}
|
|
6196
|
+
if (props.fontFamilyEa === null && defRPr.fontFamilyEa != null) {
|
|
6197
|
+
props.fontFamilyEa = resolveThemeFont(defRPr.fontFamilyEa, context.fontScheme);
|
|
6198
|
+
}
|
|
6199
|
+
if (props.color === null && defRPr.color !== void 0) {
|
|
6200
|
+
props.color = defRPr.color;
|
|
6201
|
+
}
|
|
6202
|
+
}
|
|
6203
|
+
}
|
|
6204
|
+
}
|
|
6205
|
+
}
|
|
6206
|
+
function findMatchingPlaceholderStyle(placeholderType, placeholderIdx, styles) {
|
|
6207
|
+
if (!placeholderType) return void 0;
|
|
6208
|
+
if (placeholderIdx !== void 0) {
|
|
6209
|
+
const byIdx = styles.find((s) => s.placeholderIdx === placeholderIdx);
|
|
6210
|
+
if (byIdx?.lstStyle) return byIdx.lstStyle;
|
|
6211
|
+
}
|
|
6212
|
+
const byType = styles.find((s) => s.placeholderType === placeholderType);
|
|
6213
|
+
return byType?.lstStyle;
|
|
6214
|
+
}
|
|
6215
|
+
function getTxStyleForPlaceholder(placeholderType, txStyles) {
|
|
6216
|
+
if (!txStyles) return void 0;
|
|
6217
|
+
if (!placeholderType) return txStyles.otherStyle;
|
|
6218
|
+
switch (placeholderType) {
|
|
6219
|
+
case "title":
|
|
6220
|
+
case "ctrTitle":
|
|
6221
|
+
return txStyles.titleStyle;
|
|
6222
|
+
case "body":
|
|
6223
|
+
case "subTitle":
|
|
6224
|
+
case "obj":
|
|
6225
|
+
return txStyles.bodyStyle;
|
|
6226
|
+
default:
|
|
6227
|
+
return txStyles.otherStyle;
|
|
6228
|
+
}
|
|
6229
|
+
}
|
|
6230
|
+
function getDefRPrFromStyle(style, level) {
|
|
6231
|
+
if (!style) return void 0;
|
|
6232
|
+
return style.levels[level]?.defaultRunProperties ?? style.defaultParagraph?.defaultRunProperties;
|
|
6233
|
+
}
|
|
6234
|
+
|
|
4308
6235
|
// src/converter.ts
|
|
4309
6236
|
async function convertPptxToSvg(input, options) {
|
|
6237
|
+
initWarningLogger(options?.logLevel ?? "off");
|
|
4310
6238
|
const archive = await readPptx(input);
|
|
4311
6239
|
const presentationXml = archive.files.get("ppt/presentation.xml");
|
|
4312
6240
|
if (!presentationXml) throw new Error("Invalid PPTX: missing ppt/presentation.xml");
|
|
@@ -4354,7 +6282,9 @@ async function convertPptxToSvg(input, options) {
|
|
|
4354
6282
|
masterFillContext = { rels: masterRels, archive, basePath: masterPath };
|
|
4355
6283
|
}
|
|
4356
6284
|
const masterBackground = masterXml ? parseSlideMasterBackground(masterXml, colorResolver, masterFillContext) : null;
|
|
4357
|
-
const masterElements = masterPath && masterXml ? parseSlideMasterElements(masterXml, masterPath, archive, colorResolver) : [];
|
|
6285
|
+
const masterElements = masterPath && masterXml ? parseSlideMasterElements(masterXml, masterPath, archive, colorResolver, theme.fontScheme) : [];
|
|
6286
|
+
const masterTxStyles = masterXml ? parseSlideMasterTxStyles(masterXml, colorResolver) : void 0;
|
|
6287
|
+
const masterPlaceholderStyles = masterXml ? parseSlideMasterPlaceholderStyles(masterXml, colorResolver) : [];
|
|
4358
6288
|
const slidePaths = [];
|
|
4359
6289
|
for (let i = 0; i < presInfo.slideRIds.length; i++) {
|
|
4360
6290
|
const rId = presInfo.slideRIds[i];
|
|
@@ -4369,8 +6299,10 @@ async function convertPptxToSvg(input, options) {
|
|
|
4369
6299
|
for (const { slideNumber, path } of targetSlides) {
|
|
4370
6300
|
const slideXml = archive.files.get(path);
|
|
4371
6301
|
if (!slideXml) continue;
|
|
4372
|
-
const slide = parseSlide(slideXml, path, slideNumber, archive, colorResolver);
|
|
6302
|
+
const slide = parseSlide(slideXml, path, slideNumber, archive, colorResolver, theme.fontScheme);
|
|
4373
6303
|
let layoutElements = [];
|
|
6304
|
+
let layoutPlaceholderStyles = [];
|
|
6305
|
+
let layoutShowMasterSp = true;
|
|
4374
6306
|
const slideRelsPath = buildRelsPath(path);
|
|
4375
6307
|
const slideRelsXml = archive.files.get(slideRelsPath);
|
|
4376
6308
|
if (slideRelsXml) {
|
|
@@ -4399,8 +6331,11 @@ async function convertPptxToSvg(input, options) {
|
|
|
4399
6331
|
layoutXml,
|
|
4400
6332
|
layoutPath,
|
|
4401
6333
|
archive,
|
|
4402
|
-
colorResolver
|
|
6334
|
+
colorResolver,
|
|
6335
|
+
theme.fontScheme
|
|
4403
6336
|
);
|
|
6337
|
+
layoutPlaceholderStyles = parseSlideLayoutPlaceholderStyles(layoutXml, colorResolver);
|
|
6338
|
+
layoutShowMasterSp = parseSlideLayoutShowMasterSp(layoutXml);
|
|
4404
6339
|
}
|
|
4405
6340
|
break;
|
|
4406
6341
|
}
|
|
@@ -4409,10 +6344,19 @@ async function convertPptxToSvg(input, options) {
|
|
|
4409
6344
|
if (!slide.background) {
|
|
4410
6345
|
slide.background = masterBackground;
|
|
4411
6346
|
}
|
|
4412
|
-
slide.elements
|
|
6347
|
+
applyTextStyleInheritance(slide.elements, {
|
|
6348
|
+
layoutPlaceholderStyles,
|
|
6349
|
+
masterPlaceholderStyles,
|
|
6350
|
+
txStyles: masterTxStyles,
|
|
6351
|
+
defaultTextStyle: presInfo.defaultTextStyle,
|
|
6352
|
+
fontScheme: theme.fontScheme
|
|
6353
|
+
});
|
|
6354
|
+
const effectiveMasterElements = slide.showMasterSp && layoutShowMasterSp ? masterElements : [];
|
|
6355
|
+
slide.elements = mergeElements(effectiveMasterElements, layoutElements, slide.elements);
|
|
4413
6356
|
const svg = renderSlideToSvg(slide, presInfo.slideSize);
|
|
4414
6357
|
results.push({ slideNumber, svg });
|
|
4415
6358
|
}
|
|
6359
|
+
flushWarnings();
|
|
4416
6360
|
return results;
|
|
4417
6361
|
}
|
|
4418
6362
|
async function convertPptxToPng(input, options) {
|
|
@@ -4431,28 +6375,13 @@ async function convertPptxToPng(input, options) {
|
|
|
4431
6375
|
}
|
|
4432
6376
|
return results;
|
|
4433
6377
|
}
|
|
4434
|
-
function
|
|
4435
|
-
const
|
|
4436
|
-
for (const el of elements) {
|
|
4437
|
-
if (el.type === "shape" && el.placeholderType) {
|
|
4438
|
-
types.add(el.placeholderType);
|
|
4439
|
-
}
|
|
4440
|
-
}
|
|
4441
|
-
return types;
|
|
4442
|
-
}
|
|
4443
|
-
function filterByPlaceholder(elements, excludeTypes) {
|
|
4444
|
-
return elements.filter((el) => {
|
|
6378
|
+
function mergeElements(masterElements, layoutElements, slideElements) {
|
|
6379
|
+
const filterPlaceholders = (elements) => elements.filter((el) => {
|
|
4445
6380
|
if (el.type !== "shape") return true;
|
|
4446
|
-
|
|
4447
|
-
return !excludeTypes.has(el.placeholderType);
|
|
6381
|
+
return !el.placeholderType;
|
|
4448
6382
|
});
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
const slidePh = collectPlaceholderTypes(slideElements);
|
|
4452
|
-
const layoutPh = collectPlaceholderTypes(layoutElements);
|
|
4453
|
-
const allOverrides = /* @__PURE__ */ new Set([...slidePh, ...layoutPh]);
|
|
4454
|
-
const filteredMaster = filterByPlaceholder(masterElements, allOverrides);
|
|
4455
|
-
const filteredLayout = filterByPlaceholder(layoutElements, slidePh);
|
|
6383
|
+
const filteredMaster = filterPlaceholders(masterElements);
|
|
6384
|
+
const filteredLayout = filterPlaceholders(layoutElements);
|
|
4456
6385
|
return [...filteredMaster, ...filteredLayout, ...slideElements];
|
|
4457
6386
|
}
|
|
4458
6387
|
function defaultColorScheme2() {
|
|
@@ -4489,5 +6418,7 @@ function defaultColorMap() {
|
|
|
4489
6418
|
}
|
|
4490
6419
|
export {
|
|
4491
6420
|
convertPptxToPng,
|
|
4492
|
-
convertPptxToSvg
|
|
6421
|
+
convertPptxToSvg,
|
|
6422
|
+
getWarningEntries,
|
|
6423
|
+
getWarningSummary
|
|
4493
6424
|
};
|