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/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
- "pt"
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
- console.warn(`${WARN_PREFIX} Presentation: missing root element "presentation" in XML`);
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
- console.warn(
82
- `${WARN_PREFIX} Presentation: sldSz missing, using default ${DEFAULT_SLIDE_WIDTH}x${DEFAULT_SLIDE_HEIGHT} EMU`
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?.sldId ?? [];
92
- const slideRIds = sldIdLst.map((s) => s["@_r:id"] ?? s["@_id"]).filter((id) => {
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
- console.warn(
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
- return { slideSize, slideRIds };
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
- console.warn(`${WARN_PREFIX2} Theme: missing root element "theme" in XML`);
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
- console.warn(`${WARN_PREFIX2} Theme: themeElements not found, using defaults`);
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
- console.warn(`${WARN_PREFIX2} Theme: colorScheme not found, using defaults`);
322
+ debug("theme.colorScheme", "colorScheme not found, using defaults");
135
323
  }
136
324
  if (!themeElements.fontScheme) {
137
- console.warn(`${WARN_PREFIX2} Theme: fontScheme not found, using defaults`);
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
- if (colorNode.srgbClr) {
163
- return `#${colorNode.srgbClr["@_val"]}`;
350
+ const srgbClr = colorNode.srgbClr;
351
+ if (srgbClr) {
352
+ return `#${srgbClr["@_val"]}`;
164
353
  }
165
- if (colorNode.sysClr) {
166
- return `#${colorNode.sysClr["@_lastClr"] ?? "000000"}`;
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 majorFont = fontScheme.majorFont?.latin?.["@_typeface"] ?? "Calibri";
174
- const minorFont = fontScheme.minorFont?.latin?.["@_typeface"] ?? "Calibri";
175
- const majorFontEa = fontScheme.majorFont?.ea?.["@_typeface"] ?? null;
176
- const minorFontEa = fontScheme.minorFont?.ea?.["@_typeface"] ?? null;
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?.Relationships) {
203
- console.warn(`${WARN_PREFIX3} Relationship: missing root element "Relationships" in XML`);
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.Relationships.Relationship;
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
- console.warn(`${WARN_PREFIX3} Relationship: entry missing required attribute, skipping`);
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 rId = blipFillNode?.blip?.["@_r:embed"] ?? blipFillNode?.blip?.["@_embed"];
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?.gs;
288
- if (!gsLst) {
289
- console.warn(`${WARN_PREFIX4} GradientFill: gsLst not found, skipping gradient`);
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 gsLst) {
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
- if (gradNode.lin) {
302
- angle = Number(gradNode.lin["@_ang"] ?? 0) / 6e4;
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 dashStyle = lnNode.prstDash?.["@_val"] ?? "solid";
320
- return { width, fill, dashStyle };
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", "pie"],
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 barDirection = chartType === "bar" ? chartNode.barDir?.["@_val"] ?? "col" : void 0;
422
- return { chartType, series, categories, barDirection };
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 values = parseNumericData(chartType === "scatter" ? ser.yVal : ser.val);
429
- const xValues = chartType === "scatter" ? parseNumericData(ser.xVal) : void 0;
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 strCache = tx.strRef?.strCache;
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 numCache = valNode.numRef?.numCache;
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 strCache = cat.strRef?.strCache ?? cat.numRef?.numCache;
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 rich = titleNode.tx?.rich;
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?.["#text"]) texts.push(String(t["#text"]));
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 pos = legendNode.legendPos?.["@_val"] ?? "b";
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 trList) {
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 tcList) {
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 WARN_PREFIX5 = "[pptx-glimpse]";
562
- function parseSlide(slideXml, slidePath, slideNumber, archive, colorResolver) {
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
- console.warn(`${WARN_PREFIX5} Slide ${slideNumber}: missing root element "sld" in XML`);
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 background = parseBackground(sld?.cSld?.bg, colorResolver, fillContext);
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
- sld?.cSld?.spTree,
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
- return { slideNumber, background, elements };
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 parseShapeTree(spTree, rels, slidePath, archive, colorResolver, context) {
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
- console.warn(`${WARN_PREFIX5} ${ctx}: shape skipped (parse returned null)`);
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
- console.warn(`${WARN_PREFIX5} ${ctx}: image skipped (parse returned null)`);
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
- console.warn(`${WARN_PREFIX5} ${ctx}: connector skipped (parse returned null)`);
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
- console.warn(`${WARN_PREFIX5} ${ctx}: group skipped (parse returned null)`);
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
- console.warn(`${WARN_PREFIX5} ${ctx}: graphicFrame skipped (parse returned null)`);
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 parseShape(sp, colorResolver) {
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
- const textBody = parseTextBody(sp.txBody, colorResolver);
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 ph = sp.nvSpPr?.nvPr?.ph;
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 rId = blipFill?.blip?.["@_r:embed"] ?? blipFill?.blip?.["@_embed"];
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 transform = parseTransform(grpSpPr.xfrm);
1504
+ const xfrm = grpSpPr.xfrm;
1505
+ const transform = parseTransform(xfrm);
713
1506
  if (!transform) return null;
714
- const childOff = grpSpPr.xfrm?.chOff;
715
- const childExt = grpSpPr.xfrm?.chExt;
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 children = parseShapeTree(grp, rels, slidePath, archive, colorResolver);
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 graphicData = gf.graphic?.graphicData;
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
- console.warn(`${WARN_PREFIX5} NaN detected in transform offsetX, defaulting to 0`);
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
- console.warn(`${WARN_PREFIX5} NaN detected in transform offsetY, defaulting to 0`);
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
- console.warn(`${WARN_PREFIX5} NaN detected in transform extentWidth, defaulting to 0`);
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
- console.warn(`${WARN_PREFIX5} NaN detected in transform extentHeight, defaulting to 0`);
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
- console.warn(`${WARN_PREFIX5} NaN detected in transform rotation, defaulting to 0`);
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 preset = spPr.prstGeom["@_prst"] ?? "rect";
799
- const avLst = spPr.prstGeom.avLst;
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 name = gd["@_name"];
805
- const fmla = gd["@_fmla"];
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 pathData = parseCustomGeometryPaths(spPr.custGeom);
816
- if (pathData) {
817
- return { type: "custom", pathData };
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 parseCustomGeometryPaths(custGeom) {
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
- fontScale = normAutofit["@_fontScale"] ? Number(normAutofit["@_fontScale"]) / 1e5 : 1;
876
- lnSpcReduction = normAutofit["@_lnSpcReduction"] ? Number(normAutofit["@_lnSpcReduction"]) / 1e5 : 0;
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 (const p of pList) {
895
- paragraphs.push(parseParagraph(p, colorResolver));
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
- let bulletColor = colorResolver.resolve(pPr?.buClr);
915
- if (!pPr?.buClr) bulletColor = null;
916
- const bulletSizePct = pPr?.buSzPct ? Number(pPr.buSzPct["@_val"]) : null;
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
- bullet = { type: "char", char: pPr.buChar["@_char"] ?? "\u2022" };
1801
+ const buChar = pPr.buChar;
1802
+ bullet = { type: "char", char: buChar["@_char"] ?? "\u2022" };
921
1803
  } else if (pPr?.buAutoNum) {
922
- const scheme = pPr.buAutoNum["@_type"] ?? "arabicPeriod";
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(pPr.buAutoNum["@_startAt"] ?? 1)
1809
+ startAt: Number(buAutoNum["@_startAt"] ?? 1)
927
1810
  };
928
1811
  }
929
1812
  if (pPr?.buFont) {
930
- bulletFont = pPr.buFont["@_typeface"] ?? null;
1813
+ const buFont = pPr.buFont;
1814
+ bulletFont = buFont["@_typeface"] ?? null;
931
1815
  }
932
1816
  return { bullet, bulletFont, bulletColor, bulletSizePct };
933
1817
  }
934
- function parseParagraph(p, colorResolver) {
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: pPr?.lnSpc?.spcPct ? Number(pPr.lnSpc.spcPct["@_val"]) : null,
940
- spaceBefore: pPr?.spcBef?.spcPts ? Number(pPr.spcBef.spcPts["@_val"]) : 0,
941
- spaceAfter: pPr?.spcAft?.spcPts ? Number(pPr.spcAft.spcPts["@_val"]) : 0,
942
- level: Number(pPr?.["@_lvl"] ?? 0),
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?.["@_marL"] ?? 0),
948
- indent: Number(pPr?.["@_indent"] ?? 0)
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
- const rList = p.r ?? [];
952
- for (const r of rList) {
953
- const text = r.t ?? "";
954
- const textContent = typeof text === "object" ? text["#text"] ?? "" : String(text);
955
- const rPr = r.rPr;
956
- const runProps = parseRunProperties(rPr, colorResolver);
957
- runs.push({ text: textContent, properties: runProps });
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 parseRunProperties(rPr, colorResolver) {
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
- const fontSize = rPr["@_sz"] ? hundredthPointToPoint(Number(rPr["@_sz"])) : null;
976
- const fontFamily = rPr.latin?.["@_typeface"] ?? null;
977
- const fontFamilyEa = rPr.ea?.["@_typeface"] ?? null;
978
- const bold = rPr["@_b"] === "1" || rPr["@_b"] === "true";
979
- const italic = rPr["@_i"] === "1" || rPr["@_i"] === "true";
980
- const underline = rPr["@_u"] !== void 0 && rPr["@_u"] !== "none";
981
- const strikethrough = rPr["@_strike"] !== void 0 && rPr["@_strike"] !== "noStrike";
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
- let color = colorResolver.resolve(rPr.solidFill ?? rPr);
984
- if (!rPr.solidFill && !rPr.srgbClr && !rPr.schemeClr && !rPr.sysClr) {
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
- if (!parsed.sldMaster) {
1019
- console.warn(`${WARN_PREFIX6} SlideMaster: missing root element "sldMaster" in XML`);
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 = parsed.sldMaster.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
- if (!parsed.sldMaster) {
1034
- console.warn(`${WARN_PREFIX6} SlideMaster: missing root element "sldMaster" in XML`);
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 bg = parsed.sldMaster.cSld?.bg;
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
- if (!parsed.sldMaster) {
1047
- console.warn(`${WARN_PREFIX6} SlideMaster: missing root element "sldMaster" in XML`);
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 spTree = parsed.sldMaster.cSld?.spTree;
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
- return parseShapeTree(spTree, rels, masterPath, archive, colorResolver);
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
- if (!parsed.sldLayout) {
1063
- console.warn(`${WARN_PREFIX7} SlideLayout: missing root element "sldLayout" in XML`);
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 bg = parsed.sldLayout.cSld?.bg;
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
- if (!parsed.sldLayout) {
1076
- console.warn(`${WARN_PREFIX7} SlideLayout: missing root element "sldLayout" in XML`);
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 spTree = parsed.sldLayout.cSld?.spTree;
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
- return parseShapeTree(spTree, rels, layoutPath, archive, colorResolver);
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
- if (node.lumMod || node.lumOff) {
1091
- hex = applyLuminance(hex, node.lumMod?.["@_val"], node.lumOff?.["@_val"]);
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
- if (node.tint) {
1094
- hex = applyTint(hex, Number(node.tint["@_val"]) / 1e5);
2274
+ const tintNode = node.tint;
2275
+ if (tintNode) {
2276
+ hex = applyTint(hex, Number(tintNode["@_val"]) / 1e5);
1095
2277
  }
1096
- if (node.shade) {
1097
- hex = applyShade(hex, Number(node.shade["@_val"]) / 1e5);
2278
+ const shadeNode = node.shade;
2279
+ if (shadeNode) {
2280
+ hex = applyShade(hex, Number(shadeNode["@_val"]) / 1e5);
1098
2281
  }
1099
- if (node.alpha) {
1100
- alpha = Number(node.alpha["@_val"]) / 1e5;
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
- console.warn(
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.pathData) {
1997
- return `<path d="${geometry.pathData}"/>`;
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 id = `grad-${crypto.randomUUID()}`;
2013
- const angle = fill.angle;
2014
- const rad = angle * Math.PI / 180;
2015
- const x1 = 50 - Math.cos(rad) * 50;
2016
- const y1 = 50 - Math.sin(rad) * 50;
2017
- const x2 = 50 + Math.cos(rad) * 50;
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
- parts.push(`stroke="${outline.fill.color.hex}"`);
2034
- if (outline.fill.color.alpha < 1) {
2035
- parts.push(`stroke-opacity="${outline.fill.color.alpha}"`);
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.dashStyle !== "solid") {
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
- return parts.join(" ");
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 || codePoint >= 63744 && codePoint <= 64255 || codePoint >= 65281 && codePoint <= 65376 || codePoint >= 131072 && codePoint <= 173791;
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 (currentWidth + token.width <= availableWidth) {
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 textWidth = width - marginLeft - marginRight;
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
- const fontScale = bodyProperties.fontScale;
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, para, true);
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
- para,
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
- para,
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
- para,
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
- para,
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
- para,
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 computeDy(isFirstLine, fontSizePt, lineSpacingFactor, para, isFirstLineOfParagraph) {
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
- let dy = lineHeight;
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
- return `<tspan ${prefix}${styles}>${escapeXml(text)}</tspan>`;
3592
- }
3593
- const parts = splitByScript(text);
3594
- const result = [];
3595
- for (let i = 0; i < parts.length; i++) {
3596
- const part = parts[i];
3597
- const fonts = part.isEa ? [props.fontFamilyEa, props.fontFamily] : [props.fontFamily, props.fontFamilyEa];
3598
- const styles = buildStyleAttrs(props, fontScale, fonts);
3599
- if (i === 0) {
3600
- result.push(`<tspan ${prefix}${styles}>${escapeXml(part.text)}</tspan>`);
3601
- } else {
3602
- result.push(`<tspan ${styles}>${escapeXml(part.text)}</tspan>`);
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
- return result.join("");
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 && para.properties.spaceBefore > 0) {
3642
- totalHeight += para.properties.spaceBefore / 100 * PX_PER_PT2;
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
5223
+ return str.replace(/\t/g, TAB_SPACES).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
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
- const w = emuToPixels(transform.extentWidth);
3770
- const h = emuToPixels(transform.extentHeight);
3771
- const transformAttr = buildTransformAttr(transform);
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 outlineAttr = renderOutlineAttrs(outline);
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(/^<(\w+)/, `<$1 ${fillResult.attrs} ${outlineAttr}`);
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, transform);
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 outlineAttr = renderOutlineAttrs(outline);
5391
+ const outlineResult = renderOutlineAttrs(outline);
3804
5392
  const effectResult = renderEffects(effects);
5393
+ const markerResult = renderMarkers(outline);
3805
5394
  const parts = [];
3806
- if (effectResult.filterDefs) {
3807
- parts.push(effectResult.filterDefs);
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
- parts.push(
3811
- `<g transform="${transformAttr}"${filterAttr}><line x1="0" y1="0" x2="${w}" y2="${h}" ${outlineAttr} fill="none"/></g>`
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
- parts.push(
3828
- `<g transform="${transformAttr}"${filterAttr}><image href="data:${image.mimeType};base64,${image.imageData}" width="${w}" height="${h}" preserveAspectRatio="none"/></g>`
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 entries = chart.chartType === "pie" ? chart.categories.map((cat, i) => ({
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 (entries.length === 0) return "";
5886
+ if (entries2.length === 0) return "";
4068
5887
  const entryWidth = 80;
4069
- const totalWidth = entries.length * entryWidth;
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 < entries.length; 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(entries[i].color)}/>`
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(entries[i].label)}</text>`
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 attrs = renderOutlineAttrs(cell.borders.top);
4140
- parts.push(`<line x1="${x}" y1="${y}" x2="${x + cellW}" y2="${y}" ${attrs}/>`);
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 attrs = renderOutlineAttrs(cell.borders.bottom);
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 attrs = renderOutlineAttrs(cell.borders.left);
4150
- parts.push(`<line x1="${x}" y1="${y}" x2="${x}" y2="${y + cellH}" ${attrs}/>`);
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 attrs = renderOutlineAttrs(cell.borders.right);
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 gradientMatch = svgFragment.match(/<linearGradient[^]*?<\/linearGradient>/g);
4275
- if (gradientMatch) {
4276
- defs.push(...gradientMatch);
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 = mergeElements(masterElements, layoutElements, 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 collectPlaceholderTypes(elements) {
4435
- const types = /* @__PURE__ */ new Set();
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
- if (!el.placeholderType) return true;
4447
- return !excludeTypes.has(el.placeholderType);
6381
+ return !el.placeholderType;
4448
6382
  });
4449
- }
4450
- function mergeElements(masterElements, layoutElements, slideElements) {
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
  };