ppt2json 0.3.2 → 0.3.4

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.mjs CHANGED
@@ -1769,18 +1769,20 @@ async function getPicFill(type, node, warpObj) {
1769
1769
  }
1770
1770
  function getPicFillOpacity(node) {
1771
1771
  const aBlipNode = node["a:blip"];
1772
- const aphaModFixNode = getTextByPathList(aBlipNode, ["a:alphaModFix", "attrs"]);
1773
- let opacity = 1;
1774
- if (aphaModFixNode && aphaModFixNode["amt"] && aphaModFixNode["amt"] !== "") {
1775
- opacity = parseInt(aphaModFixNode["amt"]) / 1e5;
1776
- }
1777
- return opacity;
1772
+ return getBlipOpacityRatio(aBlipNode) ?? 1;
1778
1773
  }
1779
1774
  function getPicFilters(node) {
1780
1775
  if (!node) return null;
1781
1776
  const aBlipNode = node["a:blip"];
1782
1777
  if (!aBlipNode) return null;
1783
1778
  const filters = {};
1779
+ const opacity = getBlipOpacityRatio(aBlipNode);
1780
+ if (opacity !== void 0) {
1781
+ filters.opacity = formatFilterPercent(opacity);
1782
+ }
1783
+ if (aBlipNode["a:grayscl"]) {
1784
+ filters.grayscale = "100%";
1785
+ }
1784
1786
  const extLstNode = aBlipNode["a:extLst"];
1785
1787
  if (extLstNode && extLstNode["a:ext"]) {
1786
1788
  const extNodes = Array.isArray(extLstNode["a:ext"]) ? extLstNode["a:ext"] : [extLstNode["a:ext"]];
@@ -1794,7 +1796,12 @@ function getPicFilters(node) {
1794
1796
  if (effect["a14:saturation"]) {
1795
1797
  const satAttr = getTextByPathList(effect, ["a14:saturation", "attrs", "sat"]);
1796
1798
  if (satAttr) {
1797
- filters.saturation = parseInt(String(satAttr)) / 1e5;
1799
+ const saturation = parseInt(String(satAttr)) / 1e5;
1800
+ if (saturation <= 0) {
1801
+ filters.grayscale = "100%";
1802
+ } else {
1803
+ filters.saturation = saturation;
1804
+ }
1798
1805
  }
1799
1806
  }
1800
1807
  if (effect["a14:brightnessContrast"]) {
@@ -1832,16 +1839,34 @@ function getPicFilters(node) {
1832
1839
  async function getBgPicFill(bgPr, sorce, warpObj) {
1833
1840
  const picBase64 = await getPicFill(sorce, bgPr["a:blipFill"], warpObj);
1834
1841
  const aBlipNode = bgPr["a:blipFill"]["a:blip"];
1835
- const aphaModFixNode = getTextByPathList(aBlipNode, ["a:alphaModFix", "attrs"]);
1836
- let opacity = 1;
1837
- if (aphaModFixNode && aphaModFixNode["amt"] && aphaModFixNode["amt"] !== "") {
1838
- opacity = parseInt(aphaModFixNode["amt"]) / 1e5;
1839
- }
1842
+ const opacity = getBlipOpacityRatio(aBlipNode) ?? 1;
1840
1843
  return {
1841
1844
  picBase64,
1842
1845
  opacity
1843
1846
  };
1844
1847
  }
1848
+ function getBlipOpacityRatio(aBlipNode) {
1849
+ if (!aBlipNode) return void 0;
1850
+ const alphaNodes = Array.isArray(aBlipNode["a:alphaModFix"]) ? aBlipNode["a:alphaModFix"] : aBlipNode["a:alphaModFix"] ? [aBlipNode["a:alphaModFix"]] : [];
1851
+ if (!alphaNodes.length) return void 0;
1852
+ let opacity = 1;
1853
+ let hasOpacity = false;
1854
+ for (const alphaNode of alphaNodes) {
1855
+ const amt = alphaNode?.attrs?.amt;
1856
+ if (amt === void 0 || amt === "") continue;
1857
+ const parsed = parseInt(String(amt), 10);
1858
+ if (!Number.isFinite(parsed)) continue;
1859
+ opacity *= parsed / 1e5;
1860
+ hasOpacity = true;
1861
+ }
1862
+ return hasOpacity ? opacity : void 0;
1863
+ }
1864
+ function formatFilterPercent(value) {
1865
+ const percent = Math.max(0, Math.min(100, value * 100));
1866
+ const rounded = Math.round(percent * 1e3) / 1e3;
1867
+ const normalized = Number.isInteger(rounded) ? rounded.toFixed(0) : String(rounded);
1868
+ return `${normalized}%`;
1869
+ }
1845
1870
  function getGradientFill(node, warpObj) {
1846
1871
  const gsLst = node["a:gsLst"]["a:gs"];
1847
1872
  const colors = [];
@@ -3094,7 +3119,7 @@ function getSpanStyleInfo(node, pNode, textBodyNode, pFontStyle, slideLayoutSpNo
3094
3119
 
3095
3120
  // parser/shape.ts
3096
3121
  function shapeArc(cX, cY, rX, rY, stAng, endAng, isClose) {
3097
- let dData;
3122
+ let dData = "";
3098
3123
  let angle = stAng;
3099
3124
  if (endAng >= stAng) {
3100
3125
  while (angle <= endAng) {
@@ -3139,66 +3164,87 @@ function identifyShape(shapeData) {
3139
3164
  return matchShape(analysis);
3140
3165
  }
3141
3166
  function extractPathCommands(path) {
3142
- const commands = [];
3143
- const moveToList = normalizeToArray(path["a:moveTo"]);
3144
- moveToList.forEach((moveTo) => {
3145
- const pt = Array.isArray(moveTo?.["a:pt"]) ? moveTo["a:pt"][0] : moveTo?.["a:pt"];
3146
- if (pt) {
3147
- commands.push({
3148
- type: "moveTo",
3149
- points: [{ x: parseInt(pt.attrs?.x) || 0, y: parseInt(pt.attrs?.y) || 0 }]
3150
- });
3151
- }
3152
- });
3153
- const lineToList = normalizeToArray(path["a:lnTo"]);
3154
- lineToList.forEach((lnTo) => {
3155
- const pt = lnTo["a:pt"];
3156
- if (pt) {
3157
- commands.push({
3158
- type: "lineTo",
3159
- points: [{ x: parseInt(pt.attrs?.x) || 0, y: parseInt(pt.attrs?.y) || 0 }]
3160
- });
3161
- }
3162
- });
3163
- const cubicList = normalizeToArray(path["a:cubicBezTo"]);
3164
- cubicList.forEach((cubic) => {
3165
- const pts = normalizeToArray(cubic["a:pt"]);
3166
- const points = pts.map((pt) => ({
3167
- x: parseInt(pt.attrs?.x) || 0,
3168
- y: parseInt(pt.attrs?.y) || 0
3169
- }));
3170
- if (points.length === 3) {
3171
- commands.push({ type: "cubicBezTo", points });
3167
+ const orderedNodes = collectOrderedCommandNodes(path);
3168
+ return orderedNodes.flatMap(({ type, node }) => {
3169
+ switch (type) {
3170
+ case "moveTo": {
3171
+ const pt = Array.isArray(node?.["a:pt"]) ? node["a:pt"][0] : node?.["a:pt"];
3172
+ if (!pt) return [];
3173
+ return [{
3174
+ type: "moveTo",
3175
+ points: [{ x: parseInt(pt.attrs?.x) || 0, y: parseInt(pt.attrs?.y) || 0 }]
3176
+ }];
3177
+ }
3178
+ case "lineTo": {
3179
+ const pt = node?.["a:pt"];
3180
+ if (!pt) return [];
3181
+ return [{
3182
+ type: "lineTo",
3183
+ points: [{ x: parseInt(pt.attrs?.x) || 0, y: parseInt(pt.attrs?.y) || 0 }]
3184
+ }];
3185
+ }
3186
+ case "cubicBezTo": {
3187
+ const pts = normalizeToArray(node?.["a:pt"]);
3188
+ const points = pts.map((pt) => ({
3189
+ x: parseInt(pt.attrs?.x) || 0,
3190
+ y: parseInt(pt.attrs?.y) || 0
3191
+ }));
3192
+ return points.length === 3 ? [{ type: "cubicBezTo", points }] : [];
3193
+ }
3194
+ case "arcTo":
3195
+ return [{
3196
+ type: "arcTo",
3197
+ wR: parseInt(node?.attrs?.wR) || 0,
3198
+ hR: parseInt(node?.attrs?.hR) || 0,
3199
+ stAng: parseInt(node?.attrs?.stAng) || 0,
3200
+ swAng: parseInt(node?.attrs?.swAng) || 0
3201
+ }];
3202
+ case "quadBezTo": {
3203
+ const pts = normalizeToArray(node?.["a:pt"]);
3204
+ const points = pts.map((pt) => ({
3205
+ x: parseInt(pt.attrs?.x) || 0,
3206
+ y: parseInt(pt.attrs?.y) || 0
3207
+ }));
3208
+ return points.length ? [{ type: "quadBezTo", points }] : [];
3209
+ }
3210
+ case "close":
3211
+ return [{ type: "close" }];
3212
+ default:
3213
+ return [];
3172
3214
  }
3173
3215
  });
3174
- const arcList = normalizeToArray(path["a:arcTo"]);
3175
- arcList.forEach((arc) => {
3176
- commands.push({
3177
- type: "arcTo",
3178
- wR: parseInt(arc.attrs?.wR) || 0,
3179
- hR: parseInt(arc.attrs?.hR) || 0,
3180
- stAng: parseInt(arc.attrs?.stAng) || 0,
3181
- swAng: parseInt(arc.attrs?.swAng) || 0
3182
- });
3183
- });
3184
- const quadList = normalizeToArray(path["a:quadBezTo"]);
3185
- quadList.forEach((quad) => {
3186
- const pts = normalizeToArray(quad["a:pt"]);
3187
- const points = pts.map((pt) => ({
3188
- x: parseInt(pt.attrs?.x) || 0,
3189
- y: parseInt(pt.attrs?.y) || 0
3190
- }));
3191
- commands.push({ type: "quadBezTo", points });
3192
- });
3193
- if (path["a:close"]) {
3194
- commands.push({ type: "close" });
3195
- }
3196
- return commands;
3197
3216
  }
3198
3217
  function normalizeToArray(value) {
3199
3218
  if (!value) return [];
3200
3219
  return Array.isArray(value) ? value : [value];
3201
3220
  }
3221
+ function collectOrderedCommandNodes(path) {
3222
+ const commandNodes = [
3223
+ ...toOrderedCommandEntries(path["a:moveTo"], "moveTo"),
3224
+ ...toOrderedCommandEntries(path["a:lnTo"], "lineTo"),
3225
+ ...toOrderedCommandEntries(path["a:cubicBezTo"], "cubicBezTo"),
3226
+ ...toOrderedCommandEntries(path["a:arcTo"], "arcTo"),
3227
+ ...toOrderedCommandEntries(path["a:quadBezTo"], "quadBezTo"),
3228
+ ...toOrderedCommandEntries(path["a:close"], "close")
3229
+ ];
3230
+ return commandNodes.sort((left, right) => {
3231
+ if (left.order !== right.order) return left.order - right.order;
3232
+ return left.index - right.index;
3233
+ });
3234
+ }
3235
+ function toOrderedCommandEntries(value, type) {
3236
+ return normalizeToArray(value).map((node, index) => ({
3237
+ type,
3238
+ node,
3239
+ index,
3240
+ order: getNodeOrder(node)
3241
+ }));
3242
+ }
3243
+ function getNodeOrder(node) {
3244
+ const raw = node?.attrs?.order;
3245
+ const order = typeof raw === "number" ? raw : parseInt(String(raw ?? ""), 10);
3246
+ return Number.isFinite(order) ? order : Number.MAX_SAFE_INTEGER;
3247
+ }
3202
3248
  function buildCustomShapeSegment(pathNode, w, h) {
3203
3249
  if (!pathNode || typeof pathNode !== "object") return "";
3204
3250
  const maxX = parseInt(pathNode.attrs?.w) || 0;
@@ -8172,9 +8218,28 @@ async function parse2(file) {
8172
8218
  }
8173
8219
  };
8174
8220
  }
8221
+ function getNodeByLocalName(node, localName) {
8222
+ if (!node || typeof node !== "object") return void 0;
8223
+ if (node[localName] !== void 0) return node[localName];
8224
+ for (const key of Object.keys(node)) {
8225
+ if (key === localName || key.endsWith(":" + localName)) {
8226
+ return node[key];
8227
+ }
8228
+ }
8229
+ return void 0;
8230
+ }
8231
+ function toNodeArray(node) {
8232
+ if (node === void 0 || node === null) return [];
8233
+ return Array.isArray(node) ? node : [node];
8234
+ }
8235
+ function getRelationshipArray(content) {
8236
+ const relationshipsNode = getNodeByLocalName(content, "Relationships");
8237
+ return toNodeArray(getNodeByLocalName(relationshipsNode, "Relationship"));
8238
+ }
8175
8239
  async function getContentTypes(zip) {
8176
8240
  const ContentTypesJson = await readXmlFile(zip, "[Content_Types].xml");
8177
- const subObj = ContentTypesJson["Types"]["Override"];
8241
+ const typesNode = getNodeByLocalName(ContentTypesJson, "Types");
8242
+ const subObj = toNodeArray(getNodeByLocalName(typesNode, "Override"));
8178
8243
  let slidesLocArray = [];
8179
8244
  let slideLayoutsLocArray = [];
8180
8245
  for (const item of subObj) {
@@ -8212,17 +8277,13 @@ async function getSlideInfo(zip) {
8212
8277
  }
8213
8278
  async function getTheme(zip) {
8214
8279
  const preResContent = await readXmlFile(zip, "ppt/_rels/presentation.xml.rels");
8215
- const relationshipArray = preResContent["Relationships"]["Relationship"];
8280
+ const relationshipArray = getRelationshipArray(preResContent);
8216
8281
  let themeURI;
8217
- if (relationshipArray.constructor === Array) {
8218
- for (const relationshipItem of relationshipArray) {
8219
- if (relationshipItem["attrs"]["Type"] === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme") {
8220
- themeURI = relationshipItem["attrs"]["Target"];
8221
- break;
8222
- }
8282
+ for (const relationshipItem of relationshipArray) {
8283
+ if (relationshipItem["attrs"]["Type"] === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme") {
8284
+ themeURI = relationshipItem["attrs"]["Target"];
8285
+ break;
8223
8286
  }
8224
- } else if (relationshipArray["attrs"]["Type"] === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme") {
8225
- themeURI = relationshipArray["attrs"]["Target"];
8226
8287
  }
8227
8288
  const themeContent = await readXmlFile(zip, "ppt/" + themeURI);
8228
8289
  const themeColors = [];
@@ -8239,8 +8300,7 @@ async function getTheme(zip) {
8239
8300
  async function processSingleSlide(zip, sldFileName, themeContent, defaultTextStyle) {
8240
8301
  const resName = sldFileName.replace("slides/slide", "slides/_rels/slide") + ".rels";
8241
8302
  const resContent = await readXmlFile(zip, resName);
8242
- let relationshipArray = resContent["Relationships"]["Relationship"];
8243
- if (relationshipArray.constructor !== Array) relationshipArray = [relationshipArray];
8303
+ let relationshipArray = getRelationshipArray(resContent);
8244
8304
  let noteFilename = "";
8245
8305
  let layoutFilename = "";
8246
8306
  let masterFilename = "";
@@ -8305,8 +8365,7 @@ async function processSingleSlide(zip, sldFileName, themeContent, defaultTextSty
8305
8365
  const slideLayoutTables = await indexNodes(slideLayoutContent);
8306
8366
  const slideLayoutResFilename = layoutFilename.replace("slideLayouts/slideLayout", "slideLayouts/_rels/slideLayout") + ".rels";
8307
8367
  const slideLayoutResContent = await readXmlFile(zip, slideLayoutResFilename);
8308
- relationshipArray = slideLayoutResContent["Relationships"]["Relationship"];
8309
- if (relationshipArray.constructor !== Array) relationshipArray = [relationshipArray];
8368
+ relationshipArray = getRelationshipArray(slideLayoutResContent);
8310
8369
  for (const relationshipArrayItem of relationshipArray) {
8311
8370
  const relType = relationshipArrayItem["attrs"]["Type"].replace("http://schemas.openxmlformats.org/officeDocument/2006/relationships/", "");
8312
8371
  let relTarget = relationshipArrayItem["attrs"]["Target"];
@@ -8328,8 +8387,7 @@ async function processSingleSlide(zip, sldFileName, themeContent, defaultTextSty
8328
8387
  const slideMasterTables = indexNodes(slideMasterContent);
8329
8388
  const slideMasterResFilename = masterFilename.replace("slideMasters/slideMaster", "slideMasters/_rels/slideMaster") + ".rels";
8330
8389
  const slideMasterResContent = await readXmlFile(zip, slideMasterResFilename);
8331
- relationshipArray = slideMasterResContent["Relationships"]["Relationship"];
8332
- if (relationshipArray.constructor !== Array) relationshipArray = [relationshipArray];
8390
+ relationshipArray = getRelationshipArray(slideMasterResContent);
8333
8391
  for (const relationshipArrayItem of relationshipArray) {
8334
8392
  const relType = relationshipArrayItem["attrs"]["Type"].replace("http://schemas.openxmlformats.org/officeDocument/2006/relationships/", "");
8335
8393
  let relTarget = relationshipArrayItem["attrs"]["Target"];
@@ -8351,9 +8409,8 @@ async function processSingleSlide(zip, sldFileName, themeContent, defaultTextSty
8351
8409
  const themeResFileName = themeFilename.replace(themeName, "_rels/" + themeName) + ".rels";
8352
8410
  const themeResContent = await readXmlFile(zip, themeResFileName);
8353
8411
  if (themeResContent) {
8354
- relationshipArray = themeResContent["Relationships"]["Relationship"];
8412
+ relationshipArray = getRelationshipArray(themeResContent);
8355
8413
  if (relationshipArray) {
8356
- if (relationshipArray.constructor !== Array) relationshipArray = [relationshipArray];
8357
8414
  for (const relationshipArrayItem of relationshipArray) {
8358
8415
  themeResObj[relationshipArrayItem["attrs"]["Id"]] = {
8359
8416
  "type": relationshipArrayItem["attrs"]["Type"].replace("http://schemas.openxmlformats.org/officeDocument/2006/relationships/", ""),
@@ -8381,8 +8438,7 @@ async function processSingleSlide(zip, sldFileName, themeContent, defaultTextSty
8381
8438
  }
8382
8439
  const digramResContent = await readXmlFile(zip, diagramResFileName);
8383
8440
  if (digramResContent) {
8384
- relationshipArray = digramResContent["Relationships"]["Relationship"];
8385
- if (relationshipArray.constructor !== Array) relationshipArray = [relationshipArray];
8441
+ relationshipArray = getRelationshipArray(digramResContent);
8386
8442
  for (const relationshipArrayItem of relationshipArray) {
8387
8443
  diagramResObj[relationshipArrayItem["attrs"]["Id"]] = {
8388
8444
  "type": relationshipArrayItem["attrs"]["Type"].replace("http://schemas.openxmlformats.org/officeDocument/2006/relationships/", ""),
@@ -9228,7 +9284,9 @@ function parseGradientPosition(value) {
9228
9284
  }
9229
9285
  function mapGradient(value) {
9230
9286
  if (!value || typeof value !== "object") return void 0;
9231
- const colors = Array.isArray(value.colors) ? value.colors.filter((stop) => stop && typeof stop === "object").map((stop) => ({
9287
+ const colors = Array.isArray(value.colors) ? value.colors.filter(
9288
+ (stop) => typeof stop === "object" && stop !== null
9289
+ ).map((stop) => ({
9232
9290
  pos: parseGradientPosition(stop.pos),
9233
9291
  color: mapFillColor(stop.color) ?? "#FFFFFF"
9234
9292
  })) : [];
@@ -9501,7 +9559,7 @@ function mapElement(raw) {
9501
9559
  fill,
9502
9560
  ...special ? { special: true } : {},
9503
9561
  text: hasText ? {
9504
- content: normalizedShapeText,
9562
+ content: normalizedShapeText ?? "",
9505
9563
  align: normalizeVAlign(raw.vAlign) ?? "middle",
9506
9564
  defaultColor: normalizeColor(raw.defaultColor) ?? normalizeColor(raw.color) ?? "#333",
9507
9565
  defaultFontName: raw.fontName ?? "",
@@ -9556,7 +9614,6 @@ function normalizeElement(element) {
9556
9614
  element.text.content = normalizeTextContent(element.text.content);
9557
9615
  }
9558
9616
  if (element.type === "image") {
9559
- delete element.filters;
9560
9617
  delete element.imageType;
9561
9618
  delete element.outline;
9562
9619
  }
@@ -9615,6 +9672,7 @@ function mapBaseElement(raw, exportedMeta) {
9615
9672
  rotate: raw.rotate,
9616
9673
  fill,
9617
9674
  opacity: raw.opacity,
9675
+ filters: raw.filters,
9618
9676
  outline: outline ?? void 0,
9619
9677
  shadow: shadow ?? void 0,
9620
9678
  flipH: raw.isFlipH,