pptx-glimpse 0.3.1 → 0.4.1

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.
Files changed (3) hide show
  1. package/dist/index.cjs +240 -95
  2. package/dist/index.js +240 -95
  3. package/package.json +2 -1
package/dist/index.cjs CHANGED
@@ -1540,8 +1540,8 @@ var standardParser = new import_fast_xml_parser.XMLParser({
1540
1540
  attributeNamePrefix: "@_",
1541
1541
  removeNSPrefix: true,
1542
1542
  htmlEntities: true,
1543
- isArray: (_name, jpath) => {
1544
- const tag = jpath.split(".").pop() ?? "";
1543
+ isArray: (_name, jpath, _isLeafNode, _isAttribute) => {
1544
+ const tag = String(jpath).split(".").pop() ?? "";
1545
1545
  return ARRAY_TAGS.has(tag);
1546
1546
  }
1547
1547
  });
@@ -1819,18 +1819,48 @@ function extractAlpha(node) {
1819
1819
 
1820
1820
  // src/parser/pptx-reader.ts
1821
1821
  var import_fflate = require("fflate");
1822
+ var LazyMediaMap = class {
1823
+ rawInput;
1824
+ cache = /* @__PURE__ */ new Map();
1825
+ entryIndex;
1826
+ constructor(rawInput, mediaEntryNames) {
1827
+ this.rawInput = rawInput;
1828
+ this.entryIndex = mediaEntryNames;
1829
+ }
1830
+ get(path) {
1831
+ if (!this.entryIndex.has(path)) return void 0;
1832
+ const cached = this.cache.get(path);
1833
+ if (cached) return cached;
1834
+ const result = (0, import_fflate.unzipSync)(this.rawInput, {
1835
+ filter: (file) => file.name === path
1836
+ });
1837
+ const data = result[path];
1838
+ if (data) {
1839
+ this.cache.set(path, data);
1840
+ }
1841
+ return data;
1842
+ }
1843
+ };
1822
1844
  function readPptx(input) {
1823
- const unzipped = (0, import_fflate.unzipSync)(new Uint8Array(input));
1845
+ const rawInput = new Uint8Array(input);
1846
+ const mediaEntryNames = /* @__PURE__ */ new Set();
1847
+ const unzipped = (0, import_fflate.unzipSync)(rawInput, {
1848
+ filter: (file) => {
1849
+ if (file.name.startsWith("ppt/media/")) {
1850
+ mediaEntryNames.add(file.name);
1851
+ return false;
1852
+ }
1853
+ return true;
1854
+ }
1855
+ });
1824
1856
  const files = /* @__PURE__ */ new Map();
1825
- const media = /* @__PURE__ */ new Map();
1826
1857
  for (const [relativePath, data] of Object.entries(unzipped)) {
1827
1858
  if (relativePath.endsWith("/")) continue;
1828
- if (relativePath.startsWith("ppt/media/")) {
1829
- media.set(relativePath, data);
1830
- } else if (relativePath.endsWith(".xml") || relativePath.endsWith(".rels") || relativePath === "[Content_Types].xml") {
1859
+ if (relativePath.endsWith(".xml") || relativePath.endsWith(".rels") || relativePath === "[Content_Types].xml") {
1831
1860
  files.set(relativePath, (0, import_fflate.strFromU8)(data));
1832
1861
  }
1833
1862
  }
1863
+ const media = new LazyMediaMap(rawInput, mediaEntryNames);
1834
1864
  return { files, media };
1835
1865
  }
1836
1866
 
@@ -2270,13 +2300,11 @@ function parseBlipEffects(blipNode, colorResolver) {
2270
2300
  const blur = parseBlur(blipNode.blur);
2271
2301
  const lum = parseLum(blipNode.lum);
2272
2302
  const duotone = parseDuotone(blipNode.duotone, colorResolver);
2273
- if (blipNode.clrChange !== void 0) {
2274
- warn("blip.clrChange", "color change effect not implemented");
2275
- }
2276
- if (!grayscale && !biLevel && !blur && !lum && !duotone) {
2303
+ const clrChange = parseClrChange(blipNode.clrChange, colorResolver);
2304
+ if (!grayscale && !biLevel && !blur && !lum && !duotone && !clrChange) {
2277
2305
  return null;
2278
2306
  }
2279
- return { grayscale, biLevel, blur, lum, duotone };
2307
+ return { grayscale, biLevel, blur, lum, duotone, clrChange };
2280
2308
  }
2281
2309
  function parseBiLevel(node) {
2282
2310
  if (!node) return null;
@@ -2312,6 +2340,16 @@ function parseDuotone(node, colorResolver) {
2312
2340
  if (colors.length < 2) return null;
2313
2341
  return { color1: colors[0], color2: colors[1] };
2314
2342
  }
2343
+ function parseClrChange(node, colorResolver) {
2344
+ if (!node) return null;
2345
+ const clrFrom = node.clrFrom;
2346
+ const clrTo = node.clrTo;
2347
+ if (!clrFrom || !clrTo) return null;
2348
+ const from = colorResolver.resolve(clrFrom);
2349
+ const to = colorResolver.resolve(clrTo);
2350
+ if (!from || !to) return null;
2351
+ return { clrFrom: from, clrTo: to };
2352
+ }
2315
2353
  function resolveColorNode(key, node, colorResolver) {
2316
2354
  if (key === "prstClr") {
2317
2355
  const val = node["@_val"];
@@ -4586,38 +4624,21 @@ function parsePptxData(input) {
4586
4624
  break;
4587
4625
  }
4588
4626
  }
4589
- let colorMap = defaultColorMap2();
4590
- let masterPath = null;
4627
+ const masterCache = /* @__PURE__ */ new Map();
4628
+ let firstMasterPath = null;
4591
4629
  for (const [, rel] of presRels) {
4592
4630
  if (rel.type.includes("slideMaster")) {
4593
- masterPath = resolveRelationshipTarget("ppt/presentation.xml", rel.target);
4594
- const masterXml2 = archive.files.get(masterPath);
4595
- if (masterXml2) {
4596
- colorMap = parseSlideMasterColorMap(masterXml2);
4597
- }
4598
- break;
4631
+ const mPath = resolveRelationshipTarget("ppt/presentation.xml", rel.target);
4632
+ if (!firstMasterPath) firstMasterPath = mPath;
4633
+ parseMasterDataCached(mPath, archive, theme, masterCache);
4599
4634
  }
4600
4635
  }
4601
- const colorResolver = new ColorResolver(theme.colorScheme, colorMap);
4602
- const masterXml = masterPath ? archive.files.get(masterPath) : void 0;
4603
- let masterFillContext;
4604
- if (masterPath) {
4605
- const masterRelsPath = buildRelsPath(masterPath);
4606
- const masterRelsXml = archive.files.get(masterRelsPath);
4607
- const masterRels = masterRelsXml ? parseRelationships(masterRelsXml) : /* @__PURE__ */ new Map();
4608
- masterFillContext = { rels: masterRels, archive, basePath: masterPath };
4609
- }
4610
- const masterBackground = masterXml ? parseSlideMasterBackground(masterXml, colorResolver, masterFillContext) : null;
4611
- const masterElements = masterPath && masterXml ? parseSlideMasterElements(
4612
- masterXml,
4613
- masterPath,
4614
- archive,
4615
- colorResolver,
4616
- theme.fontScheme,
4617
- theme.fmtScheme
4618
- ) : [];
4619
- const masterTxStyles = masterXml ? parseSlideMasterTxStyles(masterXml, colorResolver) : void 0;
4620
- const masterPlaceholderStyles = masterXml ? parseSlideMasterPlaceholderStyles(masterXml, colorResolver) : [];
4636
+ const defaultMaster = firstMasterPath ? masterCache.get(firstMasterPath) : void 0;
4637
+ const colorResolver = defaultMaster?.colorResolver ?? new ColorResolver(theme.colorScheme, defaultColorMap2());
4638
+ const masterBackground = defaultMaster?.background ?? null;
4639
+ const masterElements = defaultMaster?.elements ?? [];
4640
+ const masterTxStyles = defaultMaster?.txStyles;
4641
+ const masterPlaceholderStyles = defaultMaster?.placeholderStyles ?? [];
4621
4642
  const slidePaths = [];
4622
4643
  for (let i = 0; i < presInfo.slideRIds.length; i++) {
4623
4644
  const rId = presInfo.slideRIds[i];
@@ -4636,77 +4657,100 @@ function parsePptxData(input) {
4636
4657
  masterTxStyles,
4637
4658
  masterPlaceholderStyles,
4638
4659
  slidePaths,
4639
- archive
4660
+ archive,
4661
+ masterCache
4640
4662
  };
4641
4663
  }
4642
4664
  function parseSlideWithLayout(slideNumber, path, data) {
4643
4665
  const slideXml = data.archive.files.get(path);
4644
4666
  if (!slideXml) return null;
4667
+ const slideRelsPath = buildRelsPath(path);
4668
+ const slideRelsXml = data.archive.files.get(slideRelsPath);
4669
+ const slideRels = slideRelsXml ? parseRelationships(slideRelsXml) : /* @__PURE__ */ new Map();
4670
+ let layoutPath = null;
4671
+ let layoutXml;
4672
+ let layoutRels = /* @__PURE__ */ new Map();
4673
+ for (const [, rel] of slideRels) {
4674
+ if (rel.type.includes("slideLayout")) {
4675
+ layoutPath = resolveRelationshipTarget(path, rel.target);
4676
+ layoutXml = data.archive.files.get(layoutPath);
4677
+ if (layoutXml) {
4678
+ const layoutRelsPath = buildRelsPath(layoutPath);
4679
+ const layoutRelsXml = data.archive.files.get(layoutRelsPath);
4680
+ layoutRels = layoutRelsXml ? parseRelationships(layoutRelsXml) : /* @__PURE__ */ new Map();
4681
+ }
4682
+ break;
4683
+ }
4684
+ }
4685
+ let slideMasterData;
4686
+ for (const [, rel] of layoutRels) {
4687
+ if (rel.type.includes("slideMaster") && layoutPath) {
4688
+ const masterPath = resolveRelationshipTarget(layoutPath, rel.target);
4689
+ slideMasterData = parseMasterDataCached(
4690
+ masterPath,
4691
+ data.archive,
4692
+ data.theme,
4693
+ data.masterCache
4694
+ );
4695
+ break;
4696
+ }
4697
+ }
4698
+ const slideColorResolver = resolveSlideColorResolver(
4699
+ slideXml,
4700
+ layoutXml,
4701
+ slideMasterData,
4702
+ data.theme
4703
+ );
4645
4704
  const slide = parseSlide(
4646
4705
  slideXml,
4647
4706
  path,
4648
4707
  slideNumber,
4649
4708
  data.archive,
4650
- data.colorResolver,
4709
+ slideColorResolver,
4651
4710
  data.theme.fontScheme,
4652
4711
  data.theme.fmtScheme
4653
4712
  );
4654
4713
  let layoutElements = [];
4655
4714
  let layoutPlaceholderStyles = [];
4656
4715
  let layoutShowMasterSp = true;
4657
- const slideRelsPath = buildRelsPath(path);
4658
- const slideRelsXml = data.archive.files.get(slideRelsPath);
4659
- if (slideRelsXml) {
4660
- const slideRels = parseRelationships(slideRelsXml);
4661
- for (const [, rel] of slideRels) {
4662
- if (rel.type.includes("slideLayout")) {
4663
- const layoutPath = resolveRelationshipTarget(path, rel.target);
4664
- const layoutXml = data.archive.files.get(layoutPath);
4665
- if (layoutXml) {
4666
- if (!slide.background) {
4667
- const layoutRelsPath = buildRelsPath(layoutPath);
4668
- const layoutRelsXml = data.archive.files.get(layoutRelsPath);
4669
- const layoutRels = layoutRelsXml ? parseRelationships(layoutRelsXml) : /* @__PURE__ */ new Map();
4670
- const layoutFillContext = {
4671
- rels: layoutRels,
4672
- archive: data.archive,
4673
- basePath: layoutPath
4674
- };
4675
- slide.background = parseSlideLayoutBackground(
4676
- layoutXml,
4677
- data.colorResolver,
4678
- layoutFillContext
4679
- );
4680
- }
4681
- layoutElements = parseSlideLayoutElements(
4682
- layoutXml,
4683
- layoutPath,
4684
- data.archive,
4685
- data.colorResolver,
4686
- data.theme.fontScheme,
4687
- data.theme.fmtScheme
4688
- );
4689
- layoutPlaceholderStyles = parseSlideLayoutPlaceholderStyles(
4690
- layoutXml,
4691
- data.colorResolver
4692
- );
4693
- layoutShowMasterSp = parseSlideLayoutShowMasterSp(layoutXml);
4694
- }
4695
- break;
4696
- }
4716
+ if (layoutXml && layoutPath) {
4717
+ if (!slide.background) {
4718
+ const layoutFillContext = {
4719
+ rels: layoutRels,
4720
+ archive: data.archive,
4721
+ basePath: layoutPath
4722
+ };
4723
+ slide.background = parseSlideLayoutBackground(
4724
+ layoutXml,
4725
+ slideColorResolver,
4726
+ layoutFillContext
4727
+ );
4697
4728
  }
4729
+ layoutElements = parseSlideLayoutElements(
4730
+ layoutXml,
4731
+ layoutPath,
4732
+ data.archive,
4733
+ slideColorResolver,
4734
+ data.theme.fontScheme,
4735
+ data.theme.fmtScheme
4736
+ );
4737
+ layoutPlaceholderStyles = parseSlideLayoutPlaceholderStyles(layoutXml, slideColorResolver);
4738
+ layoutShowMasterSp = parseSlideLayoutShowMasterSp(layoutXml);
4698
4739
  }
4699
4740
  if (!slide.background) {
4700
- slide.background = data.masterBackground;
4741
+ slide.background = slideMasterData?.background ?? data.masterBackground;
4701
4742
  }
4743
+ const masterPlaceholderStyles = slideMasterData?.placeholderStyles ?? data.masterPlaceholderStyles;
4744
+ const masterTxStyles = slideMasterData?.txStyles ?? data.masterTxStyles;
4702
4745
  applyTextStyleInheritance(slide.elements, {
4703
4746
  layoutPlaceholderStyles,
4704
- masterPlaceholderStyles: data.masterPlaceholderStyles,
4705
- txStyles: data.masterTxStyles,
4747
+ masterPlaceholderStyles,
4748
+ txStyles: masterTxStyles,
4706
4749
  defaultTextStyle: data.presInfo.defaultTextStyle,
4707
4750
  fontScheme: data.theme.fontScheme
4708
4751
  });
4709
- return { slide, layoutElements, layoutShowMasterSp };
4752
+ const masterElements = slideMasterData?.elements ?? data.masterElements;
4753
+ return { slide, layoutElements, layoutShowMasterSp, masterElements };
4710
4754
  }
4711
4755
  function defaultColorScheme2() {
4712
4756
  return {
@@ -4740,6 +4784,88 @@ function defaultColorMap2() {
4740
4784
  folHlink: "folHlink"
4741
4785
  };
4742
4786
  }
4787
+ function parseMasterDataCached(masterPath, archive, theme, cache) {
4788
+ const cached = cache.get(masterPath);
4789
+ if (cached) return cached;
4790
+ const masterXml = archive.files.get(masterPath);
4791
+ if (!masterXml) return void 0;
4792
+ const colorMap = parseSlideMasterColorMap(masterXml);
4793
+ const colorResolver = new ColorResolver(theme.colorScheme, colorMap);
4794
+ const masterRelsPath = buildRelsPath(masterPath);
4795
+ const masterRelsXml = archive.files.get(masterRelsPath);
4796
+ const masterRels = masterRelsXml ? parseRelationships(masterRelsXml) : /* @__PURE__ */ new Map();
4797
+ const masterFillContext = { rels: masterRels, archive, basePath: masterPath };
4798
+ const background = parseSlideMasterBackground(masterXml, colorResolver, masterFillContext);
4799
+ const elements = parseSlideMasterElements(
4800
+ masterXml,
4801
+ masterPath,
4802
+ archive,
4803
+ colorResolver,
4804
+ theme.fontScheme,
4805
+ theme.fmtScheme
4806
+ );
4807
+ const txStyles = parseSlideMasterTxStyles(masterXml, colorResolver);
4808
+ const placeholderStyles = parseSlideMasterPlaceholderStyles(masterXml, colorResolver);
4809
+ const data = {
4810
+ colorMap,
4811
+ colorResolver,
4812
+ background,
4813
+ elements,
4814
+ txStyles,
4815
+ placeholderStyles
4816
+ };
4817
+ cache.set(masterPath, data);
4818
+ return data;
4819
+ }
4820
+ function resolveSlideColorResolver(slideXml, layoutXml, masterData, theme) {
4821
+ const baseColorMap = masterData?.colorMap ?? defaultColorMap2();
4822
+ const layoutOverride = layoutXml ? parseClrMapOverride(layoutXml) : null;
4823
+ const slideOverride = parseClrMapOverride(slideXml);
4824
+ let effectiveColorMap = baseColorMap;
4825
+ if (layoutOverride) {
4826
+ effectiveColorMap = { ...effectiveColorMap, ...layoutOverride };
4827
+ }
4828
+ if (slideOverride) {
4829
+ effectiveColorMap = { ...effectiveColorMap, ...slideOverride };
4830
+ }
4831
+ if (!layoutOverride && !slideOverride && masterData) {
4832
+ return masterData.colorResolver;
4833
+ }
4834
+ return new ColorResolver(theme.colorScheme, effectiveColorMap);
4835
+ }
4836
+ function parseClrMapOverride(xml) {
4837
+ if (!xml.includes("clrMapOvr")) return null;
4838
+ const parsed = parseXml(xml);
4839
+ const root = parsed.sld ?? parsed.sldLayout;
4840
+ if (!root) return null;
4841
+ const clrMapOvr = root.clrMapOvr;
4842
+ if (!clrMapOvr) return null;
4843
+ if (clrMapOvr.masterClrMapping !== void 0) return null;
4844
+ const override = clrMapOvr.overrideClrMapping;
4845
+ if (!override) return null;
4846
+ const result = {};
4847
+ const keys = [
4848
+ "bg1",
4849
+ "tx1",
4850
+ "bg2",
4851
+ "tx2",
4852
+ "accent1",
4853
+ "accent2",
4854
+ "accent3",
4855
+ "accent4",
4856
+ "accent5",
4857
+ "accent6",
4858
+ "hlink",
4859
+ "folHlink"
4860
+ ];
4861
+ for (const key of keys) {
4862
+ const val = override[`@_${key}`];
4863
+ if (val) {
4864
+ result[key] = val;
4865
+ }
4866
+ }
4867
+ return Object.keys(result).length > 0 ? result : null;
4868
+ }
4743
4869
 
4744
4870
  // src/renderer/transform.ts
4745
4871
  function buildTransformAttr(t) {
@@ -5906,6 +6032,15 @@ function renderBlipEffects(blipEffects) {
5906
6032
  );
5907
6033
  lastResult = "duotoneResult";
5908
6034
  }
6035
+ if (blipEffects.clrChange && blipEffects.clrChange.clrTo.alpha === 0) {
6036
+ const { clrFrom } = blipEffects.clrChange;
6037
+ const [rf, gf, bf] = hexToRgbNorm(clrFrom.hex);
6038
+ const scale = 20;
6039
+ primitives.push(
6040
+ `<feColorMatrix in="${lastResult}" type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 ${round2(scale)} ${round2(scale)} ${round2(scale)} 0 ${round2(-scale * (rf + gf + bf))}" result="clrChangeResult"/>`
6041
+ );
6042
+ lastResult = "clrChangeResult";
6043
+ }
5909
6044
  if (primitives.length === 0) return { filterAttr: "", filterDefs: "" };
5910
6045
  const id = `blip-effect-${crypto.randomUUID()}`;
5911
6046
  const filterDefs = [
@@ -6141,7 +6276,8 @@ var presetGeometries = {
6141
6276
  rect: (w, h) => `<rect width="${w}" height="${h}"/>`,
6142
6277
  ellipse: (w, h) => `<ellipse cx="${w / 2}" cy="${h / 2}" rx="${w / 2}" ry="${h / 2}"/>`,
6143
6278
  roundRect: (w, h, adj) => {
6144
- const r = (adj["adj"] ?? 16667) / 1e5 * Math.min(w, h);
6279
+ const a = Math.min(5e4, Math.max(0, adj["adj"] ?? 16667));
6280
+ const r = a / 1e5 * Math.min(w, h);
6145
6281
  return `<rect width="${w}" height="${h}" rx="${r}" ry="${r}"/>`;
6146
6282
  },
6147
6283
  triangle: (w, h, adj) => {
@@ -6787,17 +6923,22 @@ var presetGeometries = {
6787
6923
  return `<path d="M ${r} 0 L ${w - d} 0 L ${w} ${d} L ${w} ${h} L 0 ${h} L 0 ${r} A ${r} ${r} 0 0 1 ${r} 0 Z"/>`;
6788
6924
  },
6789
6925
  round1Rect: (w, h, adj) => {
6790
- const r = (adj["adj"] ?? 16667) / 1e5 * Math.min(w, h);
6926
+ const a = Math.min(5e4, Math.max(0, adj["adj"] ?? 16667));
6927
+ const r = a / 1e5 * Math.min(w, h);
6791
6928
  return `<path d="M 0 0 L ${w - r} 0 A ${r} ${r} 0 0 1 ${w} ${r} L ${w} ${h} L 0 ${h} Z"/>`;
6792
6929
  },
6793
6930
  round2SameRect: (w, h, adj) => {
6794
- const r1 = (adj["adj1"] ?? 16667) / 1e5 * Math.min(w, h);
6795
- const r2 = (adj["adj2"] ?? 0) / 1e5 * Math.min(w, h);
6931
+ const a1 = Math.min(5e4, Math.max(0, adj["adj1"] ?? 16667));
6932
+ const a2 = Math.min(5e4, Math.max(0, adj["adj2"] ?? 0));
6933
+ const r1 = a1 / 1e5 * Math.min(w, h);
6934
+ const r2 = a2 / 1e5 * Math.min(w, h);
6796
6935
  return `<path d="M ${r1} 0 L ${w - r1} 0 A ${r1} ${r1} 0 0 1 ${w} ${r1} L ${w} ${h - r2} A ${r2} ${r2} 0 0 1 ${w - r2} ${h} L ${r2} ${h} A ${r2} ${r2} 0 0 1 0 ${h - r2} L 0 ${r1} A ${r1} ${r1} 0 0 1 ${r1} 0 Z"/>`;
6797
6936
  },
6798
6937
  round2DiagRect: (w, h, adj) => {
6799
- const r1 = (adj["adj1"] ?? 16667) / 1e5 * Math.min(w, h);
6800
- const r2 = (adj["adj2"] ?? 0) / 1e5 * Math.min(w, h);
6938
+ const a1 = Math.min(5e4, Math.max(0, adj["adj1"] ?? 16667));
6939
+ const a2 = Math.min(5e4, Math.max(0, adj["adj2"] ?? 0));
6940
+ const r1 = a1 / 1e5 * Math.min(w, h);
6941
+ const r2 = a2 / 1e5 * Math.min(w, h);
6801
6942
  return `<path d="M ${r1} 0 L ${w} 0 L ${w} ${h - r2} A ${r2} ${r2} 0 0 1 ${w - r2} ${h} L 0 ${h} L 0 ${r1} A ${r1} ${r1} 0 0 1 ${r1} 0 Z"/>`;
6802
6943
  },
6803
6944
  // Brackets and braces
@@ -8458,8 +8599,8 @@ async function convertPptxToSvg(input, options) {
8458
8599
  for (const { slideNumber, path } of targetSlides) {
8459
8600
  const parsed = parseSlideWithLayout(slideNumber, path, data);
8460
8601
  if (!parsed) continue;
8461
- const { slide, layoutElements, layoutShowMasterSp } = parsed;
8462
- const effectiveMasterElements = slide.showMasterSp && layoutShowMasterSp ? data.masterElements : [];
8602
+ const { slide, layoutElements, layoutShowMasterSp, masterElements } = parsed;
8603
+ const effectiveMasterElements = slide.showMasterSp && layoutShowMasterSp ? masterElements : [];
8463
8604
  slide.elements = mergeElements(effectiveMasterElements, layoutElements, slide.elements);
8464
8605
  const svg = renderSlideToSvg(slide, data.presInfo.slideSize);
8465
8606
  results.push({ slideNumber, svg });
@@ -8508,12 +8649,16 @@ function collectUsedFonts(input) {
8508
8649
  const fontScheme = data.theme.fontScheme;
8509
8650
  const fonts = /* @__PURE__ */ new Set();
8510
8651
  collectThemeFonts(fontScheme, fonts);
8652
+ const collectedMasters = /* @__PURE__ */ new Set();
8511
8653
  for (const { slideNumber, path } of data.slidePaths) {
8512
8654
  const parsed = parseSlideWithLayout(slideNumber, path, data);
8513
8655
  if (!parsed) continue;
8514
8656
  collectFontsFromElements(parsed.slide.elements, fonts);
8657
+ if (!collectedMasters.has(parsed.masterElements)) {
8658
+ collectedMasters.add(parsed.masterElements);
8659
+ collectFontsFromElements(parsed.masterElements, fonts);
8660
+ }
8515
8661
  }
8516
- collectFontsFromElements(data.masterElements, fonts);
8517
8662
  return {
8518
8663
  theme: {
8519
8664
  majorFont: fontScheme.majorFont,
package/dist/index.js CHANGED
@@ -1495,8 +1495,8 @@ var standardParser = new XMLParser({
1495
1495
  attributeNamePrefix: "@_",
1496
1496
  removeNSPrefix: true,
1497
1497
  htmlEntities: true,
1498
- isArray: (_name, jpath) => {
1499
- const tag = jpath.split(".").pop() ?? "";
1498
+ isArray: (_name, jpath, _isLeafNode, _isAttribute) => {
1499
+ const tag = String(jpath).split(".").pop() ?? "";
1500
1500
  return ARRAY_TAGS.has(tag);
1501
1501
  }
1502
1502
  });
@@ -1774,18 +1774,48 @@ function extractAlpha(node) {
1774
1774
 
1775
1775
  // src/parser/pptx-reader.ts
1776
1776
  import { strFromU8, unzipSync } from "fflate";
1777
+ var LazyMediaMap = class {
1778
+ rawInput;
1779
+ cache = /* @__PURE__ */ new Map();
1780
+ entryIndex;
1781
+ constructor(rawInput, mediaEntryNames) {
1782
+ this.rawInput = rawInput;
1783
+ this.entryIndex = mediaEntryNames;
1784
+ }
1785
+ get(path) {
1786
+ if (!this.entryIndex.has(path)) return void 0;
1787
+ const cached = this.cache.get(path);
1788
+ if (cached) return cached;
1789
+ const result = unzipSync(this.rawInput, {
1790
+ filter: (file) => file.name === path
1791
+ });
1792
+ const data = result[path];
1793
+ if (data) {
1794
+ this.cache.set(path, data);
1795
+ }
1796
+ return data;
1797
+ }
1798
+ };
1777
1799
  function readPptx(input) {
1778
- const unzipped = unzipSync(new Uint8Array(input));
1800
+ const rawInput = new Uint8Array(input);
1801
+ const mediaEntryNames = /* @__PURE__ */ new Set();
1802
+ const unzipped = unzipSync(rawInput, {
1803
+ filter: (file) => {
1804
+ if (file.name.startsWith("ppt/media/")) {
1805
+ mediaEntryNames.add(file.name);
1806
+ return false;
1807
+ }
1808
+ return true;
1809
+ }
1810
+ });
1779
1811
  const files = /* @__PURE__ */ new Map();
1780
- const media = /* @__PURE__ */ new Map();
1781
1812
  for (const [relativePath, data] of Object.entries(unzipped)) {
1782
1813
  if (relativePath.endsWith("/")) continue;
1783
- if (relativePath.startsWith("ppt/media/")) {
1784
- media.set(relativePath, data);
1785
- } else if (relativePath.endsWith(".xml") || relativePath.endsWith(".rels") || relativePath === "[Content_Types].xml") {
1814
+ if (relativePath.endsWith(".xml") || relativePath.endsWith(".rels") || relativePath === "[Content_Types].xml") {
1786
1815
  files.set(relativePath, strFromU8(data));
1787
1816
  }
1788
1817
  }
1818
+ const media = new LazyMediaMap(rawInput, mediaEntryNames);
1789
1819
  return { files, media };
1790
1820
  }
1791
1821
 
@@ -2225,13 +2255,11 @@ function parseBlipEffects(blipNode, colorResolver) {
2225
2255
  const blur = parseBlur(blipNode.blur);
2226
2256
  const lum = parseLum(blipNode.lum);
2227
2257
  const duotone = parseDuotone(blipNode.duotone, colorResolver);
2228
- if (blipNode.clrChange !== void 0) {
2229
- warn("blip.clrChange", "color change effect not implemented");
2230
- }
2231
- if (!grayscale && !biLevel && !blur && !lum && !duotone) {
2258
+ const clrChange = parseClrChange(blipNode.clrChange, colorResolver);
2259
+ if (!grayscale && !biLevel && !blur && !lum && !duotone && !clrChange) {
2232
2260
  return null;
2233
2261
  }
2234
- return { grayscale, biLevel, blur, lum, duotone };
2262
+ return { grayscale, biLevel, blur, lum, duotone, clrChange };
2235
2263
  }
2236
2264
  function parseBiLevel(node) {
2237
2265
  if (!node) return null;
@@ -2267,6 +2295,16 @@ function parseDuotone(node, colorResolver) {
2267
2295
  if (colors.length < 2) return null;
2268
2296
  return { color1: colors[0], color2: colors[1] };
2269
2297
  }
2298
+ function parseClrChange(node, colorResolver) {
2299
+ if (!node) return null;
2300
+ const clrFrom = node.clrFrom;
2301
+ const clrTo = node.clrTo;
2302
+ if (!clrFrom || !clrTo) return null;
2303
+ const from = colorResolver.resolve(clrFrom);
2304
+ const to = colorResolver.resolve(clrTo);
2305
+ if (!from || !to) return null;
2306
+ return { clrFrom: from, clrTo: to };
2307
+ }
2270
2308
  function resolveColorNode(key, node, colorResolver) {
2271
2309
  if (key === "prstClr") {
2272
2310
  const val = node["@_val"];
@@ -4541,38 +4579,21 @@ function parsePptxData(input) {
4541
4579
  break;
4542
4580
  }
4543
4581
  }
4544
- let colorMap = defaultColorMap2();
4545
- let masterPath = null;
4582
+ const masterCache = /* @__PURE__ */ new Map();
4583
+ let firstMasterPath = null;
4546
4584
  for (const [, rel] of presRels) {
4547
4585
  if (rel.type.includes("slideMaster")) {
4548
- masterPath = resolveRelationshipTarget("ppt/presentation.xml", rel.target);
4549
- const masterXml2 = archive.files.get(masterPath);
4550
- if (masterXml2) {
4551
- colorMap = parseSlideMasterColorMap(masterXml2);
4552
- }
4553
- break;
4586
+ const mPath = resolveRelationshipTarget("ppt/presentation.xml", rel.target);
4587
+ if (!firstMasterPath) firstMasterPath = mPath;
4588
+ parseMasterDataCached(mPath, archive, theme, masterCache);
4554
4589
  }
4555
4590
  }
4556
- const colorResolver = new ColorResolver(theme.colorScheme, colorMap);
4557
- const masterXml = masterPath ? archive.files.get(masterPath) : void 0;
4558
- let masterFillContext;
4559
- if (masterPath) {
4560
- const masterRelsPath = buildRelsPath(masterPath);
4561
- const masterRelsXml = archive.files.get(masterRelsPath);
4562
- const masterRels = masterRelsXml ? parseRelationships(masterRelsXml) : /* @__PURE__ */ new Map();
4563
- masterFillContext = { rels: masterRels, archive, basePath: masterPath };
4564
- }
4565
- const masterBackground = masterXml ? parseSlideMasterBackground(masterXml, colorResolver, masterFillContext) : null;
4566
- const masterElements = masterPath && masterXml ? parseSlideMasterElements(
4567
- masterXml,
4568
- masterPath,
4569
- archive,
4570
- colorResolver,
4571
- theme.fontScheme,
4572
- theme.fmtScheme
4573
- ) : [];
4574
- const masterTxStyles = masterXml ? parseSlideMasterTxStyles(masterXml, colorResolver) : void 0;
4575
- const masterPlaceholderStyles = masterXml ? parseSlideMasterPlaceholderStyles(masterXml, colorResolver) : [];
4591
+ const defaultMaster = firstMasterPath ? masterCache.get(firstMasterPath) : void 0;
4592
+ const colorResolver = defaultMaster?.colorResolver ?? new ColorResolver(theme.colorScheme, defaultColorMap2());
4593
+ const masterBackground = defaultMaster?.background ?? null;
4594
+ const masterElements = defaultMaster?.elements ?? [];
4595
+ const masterTxStyles = defaultMaster?.txStyles;
4596
+ const masterPlaceholderStyles = defaultMaster?.placeholderStyles ?? [];
4576
4597
  const slidePaths = [];
4577
4598
  for (let i = 0; i < presInfo.slideRIds.length; i++) {
4578
4599
  const rId = presInfo.slideRIds[i];
@@ -4591,77 +4612,100 @@ function parsePptxData(input) {
4591
4612
  masterTxStyles,
4592
4613
  masterPlaceholderStyles,
4593
4614
  slidePaths,
4594
- archive
4615
+ archive,
4616
+ masterCache
4595
4617
  };
4596
4618
  }
4597
4619
  function parseSlideWithLayout(slideNumber, path, data) {
4598
4620
  const slideXml = data.archive.files.get(path);
4599
4621
  if (!slideXml) return null;
4622
+ const slideRelsPath = buildRelsPath(path);
4623
+ const slideRelsXml = data.archive.files.get(slideRelsPath);
4624
+ const slideRels = slideRelsXml ? parseRelationships(slideRelsXml) : /* @__PURE__ */ new Map();
4625
+ let layoutPath = null;
4626
+ let layoutXml;
4627
+ let layoutRels = /* @__PURE__ */ new Map();
4628
+ for (const [, rel] of slideRels) {
4629
+ if (rel.type.includes("slideLayout")) {
4630
+ layoutPath = resolveRelationshipTarget(path, rel.target);
4631
+ layoutXml = data.archive.files.get(layoutPath);
4632
+ if (layoutXml) {
4633
+ const layoutRelsPath = buildRelsPath(layoutPath);
4634
+ const layoutRelsXml = data.archive.files.get(layoutRelsPath);
4635
+ layoutRels = layoutRelsXml ? parseRelationships(layoutRelsXml) : /* @__PURE__ */ new Map();
4636
+ }
4637
+ break;
4638
+ }
4639
+ }
4640
+ let slideMasterData;
4641
+ for (const [, rel] of layoutRels) {
4642
+ if (rel.type.includes("slideMaster") && layoutPath) {
4643
+ const masterPath = resolveRelationshipTarget(layoutPath, rel.target);
4644
+ slideMasterData = parseMasterDataCached(
4645
+ masterPath,
4646
+ data.archive,
4647
+ data.theme,
4648
+ data.masterCache
4649
+ );
4650
+ break;
4651
+ }
4652
+ }
4653
+ const slideColorResolver = resolveSlideColorResolver(
4654
+ slideXml,
4655
+ layoutXml,
4656
+ slideMasterData,
4657
+ data.theme
4658
+ );
4600
4659
  const slide = parseSlide(
4601
4660
  slideXml,
4602
4661
  path,
4603
4662
  slideNumber,
4604
4663
  data.archive,
4605
- data.colorResolver,
4664
+ slideColorResolver,
4606
4665
  data.theme.fontScheme,
4607
4666
  data.theme.fmtScheme
4608
4667
  );
4609
4668
  let layoutElements = [];
4610
4669
  let layoutPlaceholderStyles = [];
4611
4670
  let layoutShowMasterSp = true;
4612
- const slideRelsPath = buildRelsPath(path);
4613
- const slideRelsXml = data.archive.files.get(slideRelsPath);
4614
- if (slideRelsXml) {
4615
- const slideRels = parseRelationships(slideRelsXml);
4616
- for (const [, rel] of slideRels) {
4617
- if (rel.type.includes("slideLayout")) {
4618
- const layoutPath = resolveRelationshipTarget(path, rel.target);
4619
- const layoutXml = data.archive.files.get(layoutPath);
4620
- if (layoutXml) {
4621
- if (!slide.background) {
4622
- const layoutRelsPath = buildRelsPath(layoutPath);
4623
- const layoutRelsXml = data.archive.files.get(layoutRelsPath);
4624
- const layoutRels = layoutRelsXml ? parseRelationships(layoutRelsXml) : /* @__PURE__ */ new Map();
4625
- const layoutFillContext = {
4626
- rels: layoutRels,
4627
- archive: data.archive,
4628
- basePath: layoutPath
4629
- };
4630
- slide.background = parseSlideLayoutBackground(
4631
- layoutXml,
4632
- data.colorResolver,
4633
- layoutFillContext
4634
- );
4635
- }
4636
- layoutElements = parseSlideLayoutElements(
4637
- layoutXml,
4638
- layoutPath,
4639
- data.archive,
4640
- data.colorResolver,
4641
- data.theme.fontScheme,
4642
- data.theme.fmtScheme
4643
- );
4644
- layoutPlaceholderStyles = parseSlideLayoutPlaceholderStyles(
4645
- layoutXml,
4646
- data.colorResolver
4647
- );
4648
- layoutShowMasterSp = parseSlideLayoutShowMasterSp(layoutXml);
4649
- }
4650
- break;
4651
- }
4671
+ if (layoutXml && layoutPath) {
4672
+ if (!slide.background) {
4673
+ const layoutFillContext = {
4674
+ rels: layoutRels,
4675
+ archive: data.archive,
4676
+ basePath: layoutPath
4677
+ };
4678
+ slide.background = parseSlideLayoutBackground(
4679
+ layoutXml,
4680
+ slideColorResolver,
4681
+ layoutFillContext
4682
+ );
4652
4683
  }
4684
+ layoutElements = parseSlideLayoutElements(
4685
+ layoutXml,
4686
+ layoutPath,
4687
+ data.archive,
4688
+ slideColorResolver,
4689
+ data.theme.fontScheme,
4690
+ data.theme.fmtScheme
4691
+ );
4692
+ layoutPlaceholderStyles = parseSlideLayoutPlaceholderStyles(layoutXml, slideColorResolver);
4693
+ layoutShowMasterSp = parseSlideLayoutShowMasterSp(layoutXml);
4653
4694
  }
4654
4695
  if (!slide.background) {
4655
- slide.background = data.masterBackground;
4696
+ slide.background = slideMasterData?.background ?? data.masterBackground;
4656
4697
  }
4698
+ const masterPlaceholderStyles = slideMasterData?.placeholderStyles ?? data.masterPlaceholderStyles;
4699
+ const masterTxStyles = slideMasterData?.txStyles ?? data.masterTxStyles;
4657
4700
  applyTextStyleInheritance(slide.elements, {
4658
4701
  layoutPlaceholderStyles,
4659
- masterPlaceholderStyles: data.masterPlaceholderStyles,
4660
- txStyles: data.masterTxStyles,
4702
+ masterPlaceholderStyles,
4703
+ txStyles: masterTxStyles,
4661
4704
  defaultTextStyle: data.presInfo.defaultTextStyle,
4662
4705
  fontScheme: data.theme.fontScheme
4663
4706
  });
4664
- return { slide, layoutElements, layoutShowMasterSp };
4707
+ const masterElements = slideMasterData?.elements ?? data.masterElements;
4708
+ return { slide, layoutElements, layoutShowMasterSp, masterElements };
4665
4709
  }
4666
4710
  function defaultColorScheme2() {
4667
4711
  return {
@@ -4695,6 +4739,88 @@ function defaultColorMap2() {
4695
4739
  folHlink: "folHlink"
4696
4740
  };
4697
4741
  }
4742
+ function parseMasterDataCached(masterPath, archive, theme, cache) {
4743
+ const cached = cache.get(masterPath);
4744
+ if (cached) return cached;
4745
+ const masterXml = archive.files.get(masterPath);
4746
+ if (!masterXml) return void 0;
4747
+ const colorMap = parseSlideMasterColorMap(masterXml);
4748
+ const colorResolver = new ColorResolver(theme.colorScheme, colorMap);
4749
+ const masterRelsPath = buildRelsPath(masterPath);
4750
+ const masterRelsXml = archive.files.get(masterRelsPath);
4751
+ const masterRels = masterRelsXml ? parseRelationships(masterRelsXml) : /* @__PURE__ */ new Map();
4752
+ const masterFillContext = { rels: masterRels, archive, basePath: masterPath };
4753
+ const background = parseSlideMasterBackground(masterXml, colorResolver, masterFillContext);
4754
+ const elements = parseSlideMasterElements(
4755
+ masterXml,
4756
+ masterPath,
4757
+ archive,
4758
+ colorResolver,
4759
+ theme.fontScheme,
4760
+ theme.fmtScheme
4761
+ );
4762
+ const txStyles = parseSlideMasterTxStyles(masterXml, colorResolver);
4763
+ const placeholderStyles = parseSlideMasterPlaceholderStyles(masterXml, colorResolver);
4764
+ const data = {
4765
+ colorMap,
4766
+ colorResolver,
4767
+ background,
4768
+ elements,
4769
+ txStyles,
4770
+ placeholderStyles
4771
+ };
4772
+ cache.set(masterPath, data);
4773
+ return data;
4774
+ }
4775
+ function resolveSlideColorResolver(slideXml, layoutXml, masterData, theme) {
4776
+ const baseColorMap = masterData?.colorMap ?? defaultColorMap2();
4777
+ const layoutOverride = layoutXml ? parseClrMapOverride(layoutXml) : null;
4778
+ const slideOverride = parseClrMapOverride(slideXml);
4779
+ let effectiveColorMap = baseColorMap;
4780
+ if (layoutOverride) {
4781
+ effectiveColorMap = { ...effectiveColorMap, ...layoutOverride };
4782
+ }
4783
+ if (slideOverride) {
4784
+ effectiveColorMap = { ...effectiveColorMap, ...slideOverride };
4785
+ }
4786
+ if (!layoutOverride && !slideOverride && masterData) {
4787
+ return masterData.colorResolver;
4788
+ }
4789
+ return new ColorResolver(theme.colorScheme, effectiveColorMap);
4790
+ }
4791
+ function parseClrMapOverride(xml) {
4792
+ if (!xml.includes("clrMapOvr")) return null;
4793
+ const parsed = parseXml(xml);
4794
+ const root = parsed.sld ?? parsed.sldLayout;
4795
+ if (!root) return null;
4796
+ const clrMapOvr = root.clrMapOvr;
4797
+ if (!clrMapOvr) return null;
4798
+ if (clrMapOvr.masterClrMapping !== void 0) return null;
4799
+ const override = clrMapOvr.overrideClrMapping;
4800
+ if (!override) return null;
4801
+ const result = {};
4802
+ const keys = [
4803
+ "bg1",
4804
+ "tx1",
4805
+ "bg2",
4806
+ "tx2",
4807
+ "accent1",
4808
+ "accent2",
4809
+ "accent3",
4810
+ "accent4",
4811
+ "accent5",
4812
+ "accent6",
4813
+ "hlink",
4814
+ "folHlink"
4815
+ ];
4816
+ for (const key of keys) {
4817
+ const val = override[`@_${key}`];
4818
+ if (val) {
4819
+ result[key] = val;
4820
+ }
4821
+ }
4822
+ return Object.keys(result).length > 0 ? result : null;
4823
+ }
4698
4824
 
4699
4825
  // src/renderer/transform.ts
4700
4826
  function buildTransformAttr(t) {
@@ -5861,6 +5987,15 @@ function renderBlipEffects(blipEffects) {
5861
5987
  );
5862
5988
  lastResult = "duotoneResult";
5863
5989
  }
5990
+ if (blipEffects.clrChange && blipEffects.clrChange.clrTo.alpha === 0) {
5991
+ const { clrFrom } = blipEffects.clrChange;
5992
+ const [rf, gf, bf] = hexToRgbNorm(clrFrom.hex);
5993
+ const scale = 20;
5994
+ primitives.push(
5995
+ `<feColorMatrix in="${lastResult}" type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 ${round2(scale)} ${round2(scale)} ${round2(scale)} 0 ${round2(-scale * (rf + gf + bf))}" result="clrChangeResult"/>`
5996
+ );
5997
+ lastResult = "clrChangeResult";
5998
+ }
5864
5999
  if (primitives.length === 0) return { filterAttr: "", filterDefs: "" };
5865
6000
  const id = `blip-effect-${crypto.randomUUID()}`;
5866
6001
  const filterDefs = [
@@ -6096,7 +6231,8 @@ var presetGeometries = {
6096
6231
  rect: (w, h) => `<rect width="${w}" height="${h}"/>`,
6097
6232
  ellipse: (w, h) => `<ellipse cx="${w / 2}" cy="${h / 2}" rx="${w / 2}" ry="${h / 2}"/>`,
6098
6233
  roundRect: (w, h, adj) => {
6099
- const r = (adj["adj"] ?? 16667) / 1e5 * Math.min(w, h);
6234
+ const a = Math.min(5e4, Math.max(0, adj["adj"] ?? 16667));
6235
+ const r = a / 1e5 * Math.min(w, h);
6100
6236
  return `<rect width="${w}" height="${h}" rx="${r}" ry="${r}"/>`;
6101
6237
  },
6102
6238
  triangle: (w, h, adj) => {
@@ -6742,17 +6878,22 @@ var presetGeometries = {
6742
6878
  return `<path d="M ${r} 0 L ${w - d} 0 L ${w} ${d} L ${w} ${h} L 0 ${h} L 0 ${r} A ${r} ${r} 0 0 1 ${r} 0 Z"/>`;
6743
6879
  },
6744
6880
  round1Rect: (w, h, adj) => {
6745
- const r = (adj["adj"] ?? 16667) / 1e5 * Math.min(w, h);
6881
+ const a = Math.min(5e4, Math.max(0, adj["adj"] ?? 16667));
6882
+ const r = a / 1e5 * Math.min(w, h);
6746
6883
  return `<path d="M 0 0 L ${w - r} 0 A ${r} ${r} 0 0 1 ${w} ${r} L ${w} ${h} L 0 ${h} Z"/>`;
6747
6884
  },
6748
6885
  round2SameRect: (w, h, adj) => {
6749
- const r1 = (adj["adj1"] ?? 16667) / 1e5 * Math.min(w, h);
6750
- const r2 = (adj["adj2"] ?? 0) / 1e5 * Math.min(w, h);
6886
+ const a1 = Math.min(5e4, Math.max(0, adj["adj1"] ?? 16667));
6887
+ const a2 = Math.min(5e4, Math.max(0, adj["adj2"] ?? 0));
6888
+ const r1 = a1 / 1e5 * Math.min(w, h);
6889
+ const r2 = a2 / 1e5 * Math.min(w, h);
6751
6890
  return `<path d="M ${r1} 0 L ${w - r1} 0 A ${r1} ${r1} 0 0 1 ${w} ${r1} L ${w} ${h - r2} A ${r2} ${r2} 0 0 1 ${w - r2} ${h} L ${r2} ${h} A ${r2} ${r2} 0 0 1 0 ${h - r2} L 0 ${r1} A ${r1} ${r1} 0 0 1 ${r1} 0 Z"/>`;
6752
6891
  },
6753
6892
  round2DiagRect: (w, h, adj) => {
6754
- const r1 = (adj["adj1"] ?? 16667) / 1e5 * Math.min(w, h);
6755
- const r2 = (adj["adj2"] ?? 0) / 1e5 * Math.min(w, h);
6893
+ const a1 = Math.min(5e4, Math.max(0, adj["adj1"] ?? 16667));
6894
+ const a2 = Math.min(5e4, Math.max(0, adj["adj2"] ?? 0));
6895
+ const r1 = a1 / 1e5 * Math.min(w, h);
6896
+ const r2 = a2 / 1e5 * Math.min(w, h);
6756
6897
  return `<path d="M ${r1} 0 L ${w} 0 L ${w} ${h - r2} A ${r2} ${r2} 0 0 1 ${w - r2} ${h} L 0 ${h} L 0 ${r1} A ${r1} ${r1} 0 0 1 ${r1} 0 Z"/>`;
6757
6898
  },
6758
6899
  // Brackets and braces
@@ -8413,8 +8554,8 @@ async function convertPptxToSvg(input, options) {
8413
8554
  for (const { slideNumber, path } of targetSlides) {
8414
8555
  const parsed = parseSlideWithLayout(slideNumber, path, data);
8415
8556
  if (!parsed) continue;
8416
- const { slide, layoutElements, layoutShowMasterSp } = parsed;
8417
- const effectiveMasterElements = slide.showMasterSp && layoutShowMasterSp ? data.masterElements : [];
8557
+ const { slide, layoutElements, layoutShowMasterSp, masterElements } = parsed;
8558
+ const effectiveMasterElements = slide.showMasterSp && layoutShowMasterSp ? masterElements : [];
8418
8559
  slide.elements = mergeElements(effectiveMasterElements, layoutElements, slide.elements);
8419
8560
  const svg = renderSlideToSvg(slide, data.presInfo.slideSize);
8420
8561
  results.push({ slideNumber, svg });
@@ -8463,12 +8604,16 @@ function collectUsedFonts(input) {
8463
8604
  const fontScheme = data.theme.fontScheme;
8464
8605
  const fonts = /* @__PURE__ */ new Set();
8465
8606
  collectThemeFonts(fontScheme, fonts);
8607
+ const collectedMasters = /* @__PURE__ */ new Set();
8466
8608
  for (const { slideNumber, path } of data.slidePaths) {
8467
8609
  const parsed = parseSlideWithLayout(slideNumber, path, data);
8468
8610
  if (!parsed) continue;
8469
8611
  collectFontsFromElements(parsed.slide.elements, fonts);
8612
+ if (!collectedMasters.has(parsed.masterElements)) {
8613
+ collectedMasters.add(parsed.masterElements);
8614
+ collectFontsFromElements(parsed.masterElements, fonts);
8615
+ }
8470
8616
  }
8471
- collectFontsFromElements(data.masterElements, fonts);
8472
8617
  return {
8473
8618
  theme: {
8474
8619
  majorFont: fontScheme.majorFont,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pptx-glimpse",
3
- "version": "0.3.1",
3
+ "version": "0.4.1",
4
4
  "description": "A lightweight JavaScript library for rendering PowerPoint (.pptx) files as SVG or PNG in Node.js. No LibreOffice required.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -42,6 +42,7 @@
42
42
  "files": [
43
43
  "dist"
44
44
  ],
45
+ "packageManager": "npm@10.9.6",
45
46
  "engines": {
46
47
  "node": ">=20"
47
48
  },