UNPKG

modern-openxml

Version:
1,665 lines (1,638 loc) 161 kB
'use strict'; const fflate = require('fflate'); const modernPath2d = require('modern-path2d'); const modernText = require('modern-text'); class OOXMLValue { static DPI = 72; static encode(value, type) { switch (type) { case "boolean": return value ? "1" : "0"; case "degree": return String(Number(value) * 6e4); case "fontSize": return String(Number(value) * 100); case "number": return String(value); case "string": return String(value); case "emu": return String(Number(value) / this.DPI * 914400); case "dxa": return String(Number(value) / this.DPI * 1440); case "percentage": return String(Number(value) * 1e3); case "rate": return String(Number(value) * 1e5); case "lineHeight": return String(value * 1e5 / 1.2018 - 34e-4); default: throw new Error(`type not found: ${type}`); } } static decode(value, type) { if (value === void 0) { return void 0; } switch (type) { case "boolean": return value === "true" || Number(value) === 1; case "degree": case "ST_Angle": case "ST_PositiveFixedAngle": case "positiveFixedAngle": return Number(value) / 6e4; case "fontSize": return Number(value) / 100; case "int": case "unsignedInt": case "number": case "SByteValue": case "ST_TLTimeNodeID": case "ST_ShapeID": return Number(value); case "string": case "HexBinaryValue": case "StringValue": return String(value); case "emu": case "ST_PositiveCoordinate": case "ST_LineWidth": case "ST_Coordinate32": case "ST_AdjCoordinate": return Number(value) / 914400 * this.DPI; case "dxa": return Number(value) / 1440 * this.DPI; case "percentage": case "ST_Percentage": case "ST_PositivePercentage": case "CT_PositiveFixedPercentage": case "ST_PositiveFixedPercentage": case "positiveFixedPercentage": case "rate": return Number(value) / 1e5; case "ST_TextSpacingPercentOrPercentString": return Number(String(value).replace("%", "")) / 1e5; case "ST_TextSpacingPoint": return Number(value) / 100; case "lineHeight": return Number(value) / 1e5 * 1.2018 + 34e-4; } throw new Error(`type not found: ${type}`); } } const fixtures = { "&sbquo;": "\u201A", "&bdquo;": "\u201E", "&hellip;": "\u2026", "&permil;": "\u2030", "&circ;": "\u02C6", "&cent;": "\uFFE0", "&pound;": "\xA3", "&yen;": "\xA5", "&euro;": "\u20AC", "&sect;": "\xA7", "&copy;": "\xA9", "&reg;": "\xAE", "&trade;": "\u2122", "&times;": "\xD7", "&divide;": "\xF7", "&fnof;": "\u0192" }; class OOXMLNode { constructor(dom, namespaces) { this.dom = dom; this.namespaces = namespaces; this.doc = dom.ownerDocument; this.find = this.find.bind(this); this.get = this.get.bind(this); this.attr = this.attr.bind(this); this.query = this.query.bind(this); } doc; resolver = (prefix) => prefix ? this.namespaces[prefix] || null : null; get name() { return this.dom.nodeName; } static fromXML(xml = "", userNamespaces) { xml = xml.replace(/xmlns=".*?"/g, ""); for (const key in fixtures) { xml = xml.replace(new RegExp(key, "gi"), fixtures[key]); } const doc = new DOMParser().parseFromString(xml, "text/xml"); const namespaces = {}; for (const [, key, value] of xml.matchAll(/xmlns:(\w)="(.+?)"/g)) { namespaces[key] = value; } return new OOXMLNode( doc.documentElement, { ...namespaces, ...userNamespaces } ); } getDOM() { return this.dom; } evaluate(xpath, type = XPathResult.ANY_TYPE) { return this.doc.evaluate( xpath, this.dom, this.resolver, type, null ); } query(xpath, type = "node") { switch (type) { case "node": { const result = this.evaluate(xpath, XPathResult.FIRST_ORDERED_NODE_TYPE).singleNodeValue; return result ? new OOXMLNode(result, this.namespaces) : void 0; } case "nodes": { const result = this.evaluate(xpath, XPathResult.ORDERED_NODE_ITERATOR_TYPE); const value = []; let node; while (node = result.iterateNext()) { value.push(new OOXMLNode(node, this.namespaces)); } return value; } default: { return OOXMLValue.decode( this.evaluate(xpath, XPathResult.STRING_TYPE).stringValue || void 0, type ); } } } get(xpath) { return this.query(xpath, "nodes"); } find(xpath) { return this.query(xpath, "node"); } attr(xpath, type = "string") { return this.query(xpath, type); } } function stringifyProperties(slides) { return `<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes" > <Application>Microsoft Office PowerPoint</Application> <PresentationFormat>Widescreen</PresentationFormat> <Slides>${slides}</Slides> <Notes>0</Notes> <HiddenSlides>0</HiddenSlides> <ScaleCrop>false</ScaleCrop> <HeadingPairs> <vt:vector size="4" baseType="variant"> <vt:variant> <vt:lpstr>Theme</vt:lpstr> </vt:variant> <vt:variant> <vt:i4>1</vt:i4> </vt:variant> <vt:variant> <vt:lpstr>Slide Titles</vt:lpstr> </vt:variant> <vt:variant> <vt:i4>${slides}</vt:i4> </vt:variant> </vt:vector> </HeadingPairs> <TitlesOfParts> <vt:vector size="${slides + 1}" baseType="lpstr"> <vt:lpstr>Office Theme</vt:lpstr> ${Array.from({ length: slides }).map((_) => "<vt:lpstr>PowerPoint Presentation</vt:lpstr>").join("\n")} </vt:vector> </TitlesOfParts> <LinksUpToDate>false</LinksUpToDate> <SharedDoc>false</SharedDoc> <HyperlinksChanged>false</HyperlinksChanged> <AppVersion>16.0000</AppVersion> </Properties>`; } function stringifyCoreProperties() { const d = /* @__PURE__ */ new Date(); const str = `${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()}T${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}Z`; return `<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:dcmitype="http://purl.org/dc/dcmitype/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" > <dc:title>modern-openxml</dc:title> <dc:subject>modern-openxml</dc:subject> <dc:creator>modern-openxml</dc:creator> <cp:lastModifiedBy>modern-openxml</cp:lastModifiedBy> <cp:revision>1</cp:revision> <dcterms:modified xsi:type="dcterms:W3CDTF">${str}</dcterms:modified> </cp:coreProperties>`; } var Algorithm = /* @__PURE__ */ ((Algorithm2) => { Algorithm2["sha1"] = "SHA-1"; Algorithm2["sha256"] = "SHA-256"; Algorithm2["sha384"] = "SHA-384"; Algorithm2["sha512"] = "SHA-512"; return Algorithm2; })(Algorithm || {}); const IN_BROWSER = typeof window !== "undefined"; const SUPPORTS_CRYPTO = IN_BROWSER && "crypto" in window; const SUPPORTS_CRYPTO_SUBTLE = SUPPORTS_CRYPTO && "subtle" in window.crypto; function hashBlob(blob, algorithm = "sha1") { return new Promise((resolve, reject) => { const fileReader = new FileReader(); fileReader.addEventListener("load", () => { crypto.subtle.digest(Algorithm[algorithm], fileReader.result).then((buffer) => { const typedArray = new Uint8Array(buffer); resolve(Array.prototype.map.call(typedArray, (x) => `00${x.toString(16)}`.slice(-2)).join("")); }); }); fileReader.addEventListener("error", () => { reject(fileReader.error); }); fileReader.readAsArrayBuffer(blob); }); } const XML_HEADER = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'; function withXmlHeader(str) { return `${XML_HEADER} ${str}`; } function compressXml(str) { return str.replace(/\n/g, "").replace(/> +</g, "><").replace(/ +([:\w]+=".+?")/g, " $1").replace(/([:\w]+=".+?") +/g, "$1 "); } function withAttr(name, value) { if (value === void 0) return ""; return `${name}="${value}"`; } function withAttrs(attrs) { return attrs.length ? ` ${attrs.filter(Boolean).join(" ")}` : ""; } function withIndents(str, deep = 1, ignoreFirstLine = true) { if (str === void 0) { return ""; } const spaces = Array.from({ length: deep }).map(() => " ").join(""); str = typeof str === "string" ? str : str.join("\n"); return str.split("\n").filter(Boolean).map((v, i) => { return ignoreFirstLine && i === 0 ? v : `${spaces}${v}`; }).join("\n"); } function withChildren(tagName, content) { return content ? `<${tagName}>${content}</${tagName}>` : ""; } const CONTENT_TYPES$1 = [ [/docProps\/app\.xml$/, "app", null], [/docProps\/core\.xml$/, "core", null], [/tableStyles\.xml$/, "tableStyles", null], [/presProps\.xml$/, "presProps", null], [/viewProps\.xml$/, "viewProps", null], [/theme\d+\.xml$/, "theme", null], [/slide\d+\.xml$/, "slide", null], [/colors\d+\.xml$/, "diagramColor", null], [/data\d+\.xml$/, "diagramData", null], [/layout\d+\.xml$/, "diagramLayout", null], [/quickStyle\d+\.xml$/, "diagramStyle", null], [/drawing\d+\.xml$/, "diagramDrawing", null], [/slideLayout\d+\.xml$/, "slideLayout", null], [/slideMaster\d+\.xml$/, "slideMaster", null], [/notesSlide\d+\.xml$/, "notesSlide", null], [/notesMaster\d+\.xml$/, "notesMaster", null], [/presentation\.xml$/, "presentation", null], [/\.rels$/, "relationship", "rels"], [/\.svg$/i, "image/svg+xml", "svg"], [/\.gif$/i, "image/gif", "gif"], [/\.png$/i, "image/png", "png"], [/\.jpg$/i, "image/jpeg", "jpg"], [/\.jpeg$/i, "image/jpeg", "jpeg"], [/\.wmf$/i, "image/x-wmf", "wmf"], [/\.mp4$/i, "video/mp4", "mp4"], [/\.mp3$/i, "audio/mpeg", "mp3"], [/\.fntdata$/i, "font", "fntdata"] ]; function pathToContentType(path) { for (const [RE, contentType, extension] of CONTENT_TYPES$1) { if (RE.test(path)) return [contentType, extension]; } return void 0; } const EXT_TO_MIMES = { jpeg: "image/jpeg", jpg: "image/jpeg", png: "image/png", webp: "image/webp", svg: "image/svg+xml", mp3: "audio/mpeg", mp4: "video/mp4", mov: "video/quicktime" }; const MINES_TO_EXT = Object.fromEntries(Object.entries(EXT_TO_MIMES).map(([k, v]) => [v, k])); function encodeForMap(value, map) { if (value === void 0) return void 0; for (const [k, v] of Object.entries(map)) { if (v === value) return k; } return value; } function decodeForMap(value, map) { if (value === void 0) return void 0; return map[value]; } const Alignment = { map: { ctr: "center", dist: "distributed", just: "justified", justLow: "justified-low", l: "left", r: "right", thaiDist: "thai-distributed" }, encode(value) { return encodeForMap(value, this.map); }, decode(value) { return decodeForMap(value, this.map); } }; function clearUndef(obj) { if (typeof obj !== "object" || !obj) { return obj; } if (Array.isArray(obj)) { return obj.map((v) => clearUndef(v)); } const newObj = {}; for (const key in obj) { const value = obj[key]; if (value === void 0 || value === null) { continue; } newObj[key] = clearUndef(value); } return newObj; } const presetColorMap = /* @__PURE__ */ new Map([ ["aliceBlue", "#F0F8FF"], ["antiqueWhite", "#FAEBD7"], ["aqua", "#00FFFF"], ["aquamarine", "#7FFFD4"], ["azure", "#F0FFFF"], ["beige", "#F5F5DC"], ["bisque", "#FFE4C4"], ["black", "#000000"], ["blanchedAlmond", "#FFEBCD"], ["blue", "#0000FF"], ["blueViolet", "#8A2BE2"], ["brown", "#A52A2A"], ["burlyWood", "#DEB887"], ["cadetBlue", "#5F9EA0"], ["chartreuse", "#7FFF00"], ["chocolate", "#D2691E"], ["coral", "#FF7F50"], ["cornflowerBlue", "#6495ED"], ["cornsilk", "#FFF8DC"], ["crimson", "#DC143C"], ["cyan", "#00FFFF"], ["darkBlue", "#00008B"], ["darkCyan", "#008B8B"], ["darkGoldenrod", "#B8860B"], ["darkGray", "#A9A9A9"], ["darkGreen", "#006400"], ["darkKhaki", "#BDB76B"], ["darkMagenta", "#8B008B"], ["darkOliveGreen", "#556B2F"], ["darkOrange", "#FF8C00"], ["darkOrchid", "#9932CC"], ["darkRed", "#8B0000"], ["darkSalmon", "#E9967A"], ["darkSeaGreen", "#8FBC8F"], ["darkSlateBlue", "#483D8B"], ["darkSlateGray", "#2F4F4F"], ["darkTurquoise", "#00CED1"], ["darkViolet", "#9400D3"], ["deepPink", "#FF1493"], ["deepSkyBlue", "#00BFFF"], ["dimGray", "#696969"], ["dodgerBlue", "#1E90FF"], ["firebrick", "#B22222"], ["floralWhite", "#FFFAF0"], ["forestGreen", "#228B22"], ["fuchsia", "#FF00FF"], ["gainsboro", "#DCDCDC"], ["ghostWhite", "#F8F8FF"], ["gold", "#FFD700"], ["goldenrod", "#DAA520"], ["gray", "#808080"], ["green", "#008000"], ["greenYellow", "#ADFF2F"], ["honeydew", "#F0FFF0"], ["hotPink", "#FF69B4"], ["indianRed", "#CD5C5C"], ["indigo", "#4B0082"], ["ivory", "#FFFFF0"], ["khaki", "#F0E68C"], ["lavender", "#E6E6FA"], ["lavenderBlush", "#FFF0F5"], ["lawnGreen", "#7CFC00"], ["lemonChiffon", "#FFFACD"], ["lightBlue", "#ADD8E6"], ["lightCoral", "#F08080"], ["lightCyan", "#E0FFFF"], ["lightGoldenrodYellow", "#FAFAD2"], ["lightGray", "#D3D3D3"], ["lightGreen", "#90EE90"], ["lightPink", "#FFB6C1"], ["lightSalmon", "#FFA07A"], ["lightSeaGreen", "#20B2AA"], ["lightSkyBlue", "#87CEFA"], ["lightSlateGray", "#778899"], ["lightSteelBlue", "#B0C4DE"], ["lightYellow", "#FFFFE0"], ["lime", "#00FF00"], ["limeGreen", "#32CD32"], ["linen", "#FAF0E6"], ["magenta", "#FF00FF"], ["maroon", "#800000"], ["mediumAquamarine", "#66CDAA"], ["mediumBlue", "#0000CD"], ["mediumOrchid", "#BA55D3"], ["mediumPurple", "#9370DB"], ["mediumSeaGreen", "#3CB371"], ["mediumSlateBlue", "#7B68EE"], ["mediumSpringGreen", "#00FA9A"], ["mediumTurquoise", "#48D1CC"], ["mediumVioletRed", "#C71585"], ["midnightBlue", "#191970"], ["mintCream", "#F5FFFA"], ["mistyRose", "#FFE4E1"], ["moccasin", "#FFE4B5"], ["navajoWhite", "#FFDEAD"], ["navy", "#000080"], ["oldLace", "#FDF5E6"], ["olive", "#808000"], ["oliveDrab", "#6B8E23"], ["orange", "#FFA500"], ["orangeRed", "#FF4500"], ["orchid", "#DA70D6"], ["paleGoldenrod", "#EEE8AA"], ["paleGreen", "#98FB98"], ["paleTurquoise", "#AFEEEE"], ["paleVioletRed", "#DB7093"], ["papayaWhip", "#FFEFD5"], ["peachPuff", "#FFDAB9"], ["peru", "#CD853F"], ["pink", "#FFC0CB"], ["plum", "#DDA0DD"], ["powderBlue", "#B0E0E6"], ["purple", "#800080"], ["red", "#FF0000"], ["rosyBrown", "#BC8F8F"], ["royalBlue", "#4169E1"], ["saddleBrown", "#8B4513"], ["salmon", "#FA8072"], ["sandyBrown", "#F4A460"], ["seaGreen", "#2E8B57"], ["seaShell", "#FFF5EE"], ["sienna", "#A0522D"], ["silver", "#C0C0C0"], ["skyBlue", "#87CEEB"], ["slateBlue", "#6A5ACD"], ["slateGray", "#708090"], ["snow", "#FFFAFA"], ["springGreen", "#00FF7F"], ["steelBlue", "#4682B4"], ["tan", "#D2B48C"], ["teal", "#008080"], ["thistle", "#D8BFD8"], ["tomato", "#FF6347"], ["turquoise", "#40E0D0"], ["violet", "#EE82EE"], ["wheat", "#F5DEB3"], ["white", "#FFFFFF"], ["whiteSmoke", "#F5F5F5"], ["yellow", "#FFFF00"], ["yellowGreen", "#9ACD32"] ]); const sysColors = { windowText: "#000000", window: "#FFFFFF", menu: "#F0F0F0", buttonFace: "#F0F0F0", buttonText: "#000000", highlight: "#3399FF", highlightText: "#FFFFFF" }; const tags$1 = [ "a:hslClr", "a:prstClr", "a:schemeClr", "a:scrgbClr", "a:srgbClr", "a:sysClr" ]; const colorXPath = `*[(${tags$1.map((v) => `self::${v}`).join(" or ")})]`; function parseColorHex(node, ctx) { switch (node.name) { case "a:hslClr": return toHex(hslToRgb({ h: node.attr("@hue", "ST_PositiveFixedAngle"), s: node.attr("@sat", "ST_Percentage"), l: node.attr("@lum", "ST_Percentage") })); case "a:prstClr": { const val = node.attr("@val"); return toHex(presetColorMap.get(val) ?? val); } case "a:schemeClr": { const master = ctx?.master; const theme = ctx?.theme; const val = node.attr("@val"); let key = val; key = master?.meta?.colorMap?.[key] ?? key; let colorScheme = theme?.colorScheme?.[key]; if (!colorScheme) { key = theme?.extraColorMap?.[key] ?? key; colorScheme = theme?.extraColorScheme?.[key]; } return colorScheme ? toHex(colorScheme) : val; } case "a:scrgbClr": return toHex({ r: node.attr("@r", "ST_Percentage"), g: node.attr("@g", "ST_Percentage"), b: node.attr("@b", "ST_Percentage") }); case "a:srgbClr": return toHex(node.attr("@val")); case "a:sysClr": return toHex(sysColors[node.attr("@val")] ?? "#000000"); default: return "#000000"; } } function parseColor(node, ctx) { if (node && !tags$1.includes(node?.name)) { node = node.find(colorXPath); } if (!node) return void 0; const hex = parseColorHex(node, ctx); if (!hex || !hex.startsWith("#")) { return hex; } const rgba = { ...hexToRgb(hex), a: ~~((node.attr("a:alpha/@val", "ST_PositivePercentage") ?? 1) * 100) / 100 }; const luminanceModulation = node.attr("a:lumMod/@val", "rate"); const luminanceOffset = node.attr("a:lumOff/@val", "rate"); if (luminanceModulation) { const hsl = rgbToHsl(rgba); hsl.l = hsl.l * Number(luminanceModulation) + Number(luminanceOffset ?? 0); const newRgb = hslToRgb(hsl); rgba.r = newRgb.r; rgba.g = newRgb.g; rgba.b = newRgb.b; } return `rgba(${rgba.r},${rgba.g},${rgba.b},${rgba.a})`; } function stringifyColor(color) { if (!color) return ""; if (color.startsWith("linear-gradient")) { const str = color.match(/linear-gradient\((.+)\)$/)?.[1] ?? ""; const first = str.split(",")[0]; const deg = first.includes("deg") ? first : "0deg"; let degree = Number(deg.replace("deg", "")); degree = degree ? (degree + 270) % 360 : degree; const ang = OOXMLValue.encode(degree, "positiveFixedAngle"); const matched = str.replace(deg, "").matchAll(/(#|rgba|rgb)(.+?) ([\d.]+%)/gi); const gs = Array.from(matched).map((res) => { let color2 = res[2]; if (color2.startsWith("(")) { color2 = color2.split(",").length > 3 ? `rgba${color2}` : `rgb${color2}`; } else { color2 = `#${color2}`; } return `<a:gs pos="${Number(res[3]?.replace("%", "") ?? 0) * 1e3}"> ${withIndents(_stringifyColor(color2))} </a:gs>`; }); return `<a:gradFill> <a:gsLst> ${withIndents(gs, 2)} </a:gsLst> <a:lin${withAttrs([withAttr("ang", ang), withAttr("scaled", 0)])}/> </a:gradFill>`; } return `<a:solidFill> ${withIndents(_stringifyColor(color))} </a:solidFill>`; } function _stringifyColor(color) { let alpha = 1e5; if (color === "transparent") { color = "#0000"; } if (color.startsWith("#")) { color = color.substring(1); if (color.length === 3 || color.length === 4) { color = color.split("").map((v) => v + v).join(""); } if (color.length === 8) { alpha *= Number(`0x${color.substring(6, 8)}`) / 255; color = color.substring(0, 6); } } else if (color.startsWith("rgba")) { const rgba = color.match(/rgba\((.+)\)/)?.[1]?.split(",").map((v) => Number(v.trim())); if (rgba) { color = rgbToHex({ r: rgba[0], g: rgba[1], b: rgba[2] }); if (rgba[3] > 1) { rgba[3] /= 255; } alpha = rgba[3] * 1e5; } } else if (color.startsWith("rgb")) { const rgb = color.match(/rgb\((.+)\)/)?.[1]?.split(",").map((v) => Number(v.trim())); if (rgb) color = rgbToHex({ r: rgb[0], g: rgb[1], b: rgb[2] }); } return `<a:srgbClr val="${color}"> <a:alpha val="${Math.floor(alpha)}"/> </a:srgbClr>`; } function toHex(value) { if (typeof value === "object") { return `#${value.r}${value.g}${value.b}`; } return value.startsWith("#") ? value : `#${value}`; } function hueTo(p, q, t) { if (t < 0) t += 1; if (t > 1) t -= 1; if (t < 1 / 6) return p + (q - p) * 6 * t; if (t < 1 / 2) return q; if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; return p; } function hslToRgb(hsl) { const { h, s, l } = hsl; let r; let g; let b; if (s === 0) { r = g = b = l; } else { const q = l < 0.5 ? l * (1 + s) : l + s - l * s; const p = 2 * l - q; r = hueTo(p, q, h + 1 / 3); g = hueTo(p, q, h); b = hueTo(p, q, h - 1 / 3); } return { r: Math.round(r * 255), g: Math.round(g * 255), b: Math.round(b * 255) }; } function rgbToHex(rgb) { const { r, g, b } = rgb; return ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); } function hexToRgb(hex) { hex = hex.replace(/^#/, ""); if (hex.length === 3) { hex = hex.split("").map((char) => char + char).join(""); } const r = Number.parseInt(hex.substring(0, 2), 16); const g = Number.parseInt(hex.substring(2, 4), 16); const b = Number.parseInt(hex.substring(4, 6), 16); return { r, g, b }; } function rgbToHsl(rgb) { let { r, g, b } = rgb; r /= 255; g /= 255; b /= 255; const max = Math.max(r, g, b); const min = Math.min(r, g, b); let h = (max + min) / 2; let s = h; const l = s; if (max === min) { h = s = 0; } else { const d = max - min; s = l > 0.5 ? d / (2 - max - min) : d / (max + min); switch (max) { case r: h = (g - b) / d + (g < b ? 6 : 0); break; case g: h = (b - r) / d + 2; break; case b: h = (r - g) / d + 4; break; } h /= 6; } return { h, s, l }; } function parseColorScheme(clrScheme) { if (!clrScheme) return void 0; const map = {}; clrScheme.get("*").forEach((color) => { const key = color.name.replace("a:", ""); const value = color.attr("a:srgbClr/@val") ?? color.attr("a:sysClr/@lastClr"); map[key] = value ? `#${value}` : value; }); return map; } function parseInnerShadow(innerShdw, ctx) { if (!innerShdw) return void 0; const color = parseColor(innerShdw, ctx); if (!color) return void 0; const blur = innerShdw.attr("@blurRad", "ST_PositiveCoordinate") ?? 0; const dir = innerShdw.attr("@dir", "ST_PositiveFixedAngle") ?? 0; const dist = innerShdw.attr("@dist", "ST_PositiveCoordinate") ?? 0; const degree = dir + 90; const radian = degree / 180 * Math.PI; const offsetX = dist * Math.sin(radian); const offsetY = dist * -Math.cos(radian); return { color, offsetX, offsetY, blur }; } function parseOuterShadow(outerShdw, ctx) { const base = parseInnerShadow(outerShdw, ctx); if (!base) { return void 0; } const sx = outerShdw.attr("@sx", "ST_Percentage") ?? 1; const sy = outerShdw.attr("@sy", "ST_Percentage") ?? 1; return { ...base, offsetX: base.offsetX * sx, offsetY: base.offsetY * sy }; } function parseSoftEdge(softEdge) { if (!softEdge) { return void 0; } return { radius: softEdge.attr("@rad", "ST_PositiveCoordinate") ?? 0 }; } function parseEffectList(effectLst, ctx) { if (!effectLst) return void 0; return { innerShadow: parseInnerShadow(effectLst.find("a:innerShdw"), ctx), outerShadow: parseOuterShadow(effectLst.find("a:outerShdw"), ctx), softEdge: parseSoftEdge(effectLst.find("a:softEdge")) }; } const tags = [ "a:noFill", "a:blipFill", "p:blipFill", "a:gradFill", "a:grpFill", "a:pattFill", "a:solidFill" ]; const fillXPath = `*[(${tags.map((v) => `self::${v}`).join(" or ")})]`; function parseFill(fill, ctx) { if (fill && !tags.includes(fill?.name)) { fill = fill.find(fillXPath); } if (!fill) return void 0; switch (fill.name) { case "a:blipFill": case "p:blipFill": { return parseBlipFill(fill, ctx); } case "a:solidFill": return { color: parseColor(fill, ctx) }; case "a:gradFill": return { color: parseGradientFill(fill, ctx) }; case "a:grpFill": return ctx?.parents?.length ? ctx.parents[ctx.parents.length - 1]?.fill : void 0; case "a:pattFill": return void 0; case "a:noFill": default: return void 0; } } function parseBlipFill(fill, ctx) { if (!fill) return void 0; const embed = fill.attr("a:blip/a:extLst//a:ext/asvg:svgBlip/@r:embed") ?? fill.attr("a:blip/@r:embed"); let src; if (ctx?.drawing) { src = ctx?.drawing.rels.find((v) => v.id === embed)?.path; } else { src = ctx?.rels?.find((v) => v.id === embed)?.path; } src = src ?? embed; const srcRectNode = fill.find("a:srcRect"); const srcRect = srcRectNode ? clearUndef({ top: srcRectNode.attr("@t", "ST_Percentage"), right: srcRectNode.attr("@r", "ST_Percentage"), bottom: srcRectNode.attr("@b", "ST_Percentage"), left: srcRectNode.attr("@l", "ST_Percentage") }) : void 0; const fillRectNode = fill.find("a:stretch/a:fillRect"); const fillRect = fillRectNode ? clearUndef({ top: fillRectNode.attr("@t", "ST_Percentage"), right: fillRectNode.attr("@r", "ST_Percentage"), bottom: fillRectNode.attr("@b", "ST_Percentage"), left: fillRectNode.attr("@l", "ST_Percentage") }) : void 0; const tileNode = fill.find("a:tile"); const tile = tileNode ? clearUndef({ scaleX: tileNode.attr("@sx", "ST_Percentage"), scaleY: tileNode.attr("@sy", "ST_Percentage"), alignment: tileNode.attr("@algn"), translateX: tileNode.attr("@tx", "ST_Percentage"), translateY: tileNode.attr("@ty", "ST_Percentage"), flip: tileNode.attr("@flip") }) : void 0; return { rotateWithShape: fill.attr("@rotWithShape", "boolean"), dpi: fill.attr("@dpi", "number"), src, opacity: fill.attr("a:blip/a:alphaModFix/@amt", "ST_PositivePercentage"), srcRect, stretch: fillRect && Object.keys(fillRect).length > 0 ? { rect: fillRect } : void 0, tile: tile && Object.keys(tile).length > 0 ? tile : void 0 }; } function parseGradientFill(gradFill, ctx) { if (!gradFill) return void 0; const colorStops = gradFill.get("a:gsLst/a:gs").map((gs) => { return { color: parseColor(gs, ctx), percentage: (gs.attr("@pos", "positiveFixedPercentage") ?? 0) * 100 }; }).filter(({ color }) => color).sort((a, b) => a.percentage - b.percentage); if (!colorStops.length) return void 0; if (gradFill.attr("a:path/@path") === "circle") { return `radial-gradient(${colorStops.map(({ color, percentage }) => `${color} ${percentage}%`).join(",")})`; } const degree = gradFill.attr("a:lin/@ang", "positiveFixedAngle") ?? 0; return `linear-gradient(${[ `${(degree + 90) % 360}deg`, ...colorStops.map(({ color, percentage }) => `${color} ${percentage}%`) ].join(",")})`; } function stringifyFill(fill, isPic = false) { if (!fill) return void 0; if (!!fill.src || isPic) { const tagName = isPic ? "p:blipFill" : "a:blipFill"; const url = fill.src ?? fill.src; return `<${tagName}> <a:blip${withAttrs([withAttr("r:embed", url)])}> ${withIndents([ fill.opacity !== void 0 && `<a:alphaModFix amt="${OOXMLValue.encode(fill.opacity, "ST_PositivePercentage")}" />` ])} <a:lum/> </a:blip> <a:srcRect/> <a:stretch> <a:fillRect/> </a:stretch> </${tagName}>`; } else if (fill.color) { return stringifyColor(String(fill.color ?? "#FFFFFF")); } return void 0; } function parseFontScheme(fontScheme) { if (!fontScheme) return void 0; return fontScheme?.get("*").reduce((props, node) => { const key = node.name.match(/a:(\w+)Font/)?.[1]; if (!key) return props; props[key] = clearUndef({ complexScript: node.attr("a:cs/@typeface") || void 0, eastasian: node.attr("a:ea/@typeface") || void 0, latin: node.attr("a:latin/@typeface") || void 0, symbol: node.attr("a:sym/@typeface") || void 0 }); return props; }, {}); } var LineEndType = /* @__PURE__ */ ((LineEndType2) => { LineEndType2["NONE"] = "none"; LineEndType2["OVAL"] = "oval"; LineEndType2["STEALTH"] = "stealth"; LineEndType2["TRIANGLE"] = "triangle"; LineEndType2["ARROW"] = "arrow"; LineEndType2["DIAMOND"] = "diamond"; return LineEndType2; })(LineEndType || {}); var DashType = /* @__PURE__ */ ((DashType2) => { DashType2["SOLID"] = "solid"; DashType2["SYS_DOT"] = "sysDot"; DashType2["SYS_DASH"] = "sysDash"; DashType2["DASH"] = "dash"; DashType2["DASH_DOT"] = "dashDot"; DashType2["LG_DASH"] = "lgDash"; DashType2["LG_DASH_DOT"] = "lgDashDot"; DashType2["LG_DASH_DOT_DOT"] = "lgDashDotDot"; return DashType2; })(DashType || {}); function parseOutline(node, ctx) { if (node && node.name !== "a:ln") { node = node.find(".//a:ln"); } if (!node) return void 0; const query = ctx?.query ?? node.query; const prstDash = node.attr("a:prstDash/@val"); const fill = parseFill(query(fillXPath), ctx); const style = prstDash ? prstDash !== "solid" /* SOLID */ ? "dashed" : "solid" : void 0; return { style, width: node.attr("@w", "ST_LineWidth"), color: fill?.color }; } function stringifyOutline(ln) { if (!ln) return void 0; return `<a:ln${withAttrs([ withAttr("w", OOXMLValue.encode(ln.width, "ST_LineWidth")) ])}> ${ln.width ? withIndents(stringifyColor(String(ln.color))) : "<a:noFill/>"} </a:ln>`; } function parseBackground(bg, ctx) { if (!bg) return void 0; const bgRef = bg.find("p:bgRef"); const bgRefIdx = bgRef?.attr("@idx", "number"); if (bgRefIdx) { const backgroundFillStyleList = ctx?.theme?.backgroundFillStyleList; if (!backgroundFillStyleList) { return void 0; } const bgFill = backgroundFillStyleList[bgRefIdx - 1] ?? backgroundFillStyleList[0]; if (bgFill?.color === "phClr") { return { color: parseColor(bgRef, ctx) }; } return bgFill; } else { return parseFill(bg.find("p:bgPr"), ctx); } } function stringifyBackground(bg) { if (!bg) return void 0; const fill = stringifyFill(bg); return `<p:bg> <p:bgPr> ${withIndents(fill, 2)} <a:effectLst/> </p:bgPr> </p:bg>`; } function parseColorMap(clrMap) { if (!clrMap) return void 0; const node = clrMap.getDOM(); const length = node.attributes.length; const map = {}; for (let i = 0; i < length; i++) { const attr = node.attributes.item(i); map[attr.name] = attr.value; } return map; } function parseNonVisualDrawingProperties(cNvPr) { if (!cNvPr) return void 0; return { name: cNvPr.attr("@name"), meta: { id: cNvPr.attr("@id"), desc: cNvPr.attr("@descr"), click: cNvPr.attr("a:hlinkClick/@action") }, style: { visibility: cNvPr.attr("@hidden", "boolean") ? "hidden" : void 0 } }; } function stringifyNonVisualDrawingProperties(cNvPr) { return cNvPr.meta.click ? `<p:cNvPr${withAttrs([ withAttr("id", cNvPr.meta.id), withAttr("name", cNvPr.name ?? "") ])}> <a:hlinkClick${withAttrs([ withAttr("r:id", ""), withAttr("action", cNvPr.meta.click) ])}/> </p:cNvPr>` : `<p:cNvPr${withAttrs([ withAttr("id", cNvPr.meta.id), withAttr("name", cNvPr.name ?? "") ])}/>`; } function parsePlaceholder(ph, ctx) { if (ph && ph.name !== "p:ph") { ph = ph.find("//p:nvPr/p:ph"); } if (!ph) return void 0; const index = ph?.attr("@idx"); const hasPhIdx = index !== void 0; const type = ph?.attr("@type") ?? (hasPhIdx ? "body" : void 0); let node; if (type) { const required = [`@type="${type}"`, hasPhIdx && `@idx="${index}"`].filter(Boolean).join(" and "); const path = `p:cSld/p:spTree/p:sp/p:nvSpPr/p:nvPr/p:ph[${required}]/ancestor::p:sp`; node = ctx?.layout?.node?.find(path) ?? ctx?.master?.node?.find(path); } return { type, index, node }; } function parseNonVisualProperties(nvPr, ctx) { if (!nvPr) return void 0; const audioId = nvPr.attr("a:audioFile/@r:link"); const videoId = nvPr.attr("a:videoFile/@r:link"); const audio = ctx?.rels?.find((v) => v.id === audioId)?.path; const video = ctx?.rels?.find((v) => v.id === videoId)?.path; const placeholder = parsePlaceholder(nvPr.find("p:ph"), ctx); return { audio: audio ? { url: audio } : void 0, video: video ? { url: video } : void 0, placeholder }; } function stringifyNonVisualProperties(_nvPr) { return "<p:nvPr/>"; } function parseGdList(gdList) { return gdList?.get("*[(self::a:gd or self::gd)]").map((gd) => ({ name: gd.attr("@name"), fmla: gd.attr("@fmla") })) ?? []; } function parseGeometry(geom, ctx) { if (!geom) return void 0; let prstGeom, custGeom; switch (geom.name) { case "a:prstGeom": prstGeom = geom; break; case "a:custGeom": custGeom = geom; break; } const preset = prstGeom?.attr("@prst"); if (preset === "rect" && !prstGeom?.get("a:avLst//a:gd")?.length) ; else { if (preset) { const node = ctx?.presetShapeDefinitions?.find(preset); ctx = { ...ctx, preset, avLst: [ ...parseGdList(node?.find("avLst")), ...parseGdList(prstGeom?.find("a:avLst")) ], gdLst: parseGdList(node?.find("gdLst")), pathLst: node?.find("pathLst") }; return { name: preset, paths: getPaths(ctx).map((path) => { const { commands, ...props } = path; return { ...props, data: modernPath2d.svgPathCommandsToData(commands) }; }) }; } else if (custGeom) { ctx = { ...ctx, avLst: parseGdList(custGeom.find("a:avLst")), gdLst: parseGdList(custGeom.find("a:gdLst")), pathLst: custGeom.find("a:pathLst") }; return { paths: getPaths(ctx).map((path) => { const { commands, ...props } = path; return { ...props, data: modernPath2d.svgPathCommandsToData(commands) }; }) }; } } } function stringifyGeometry(geometry) { if (!geometry) { return void 0; } if (geometry.name && geometry.name !== "custom") { return `<a:prstGeom prst="${geometry.name}"> <a:avLst/> </a:prstGeom>`; } return `<a:prstGeom prst="rect"> <a:avLst/> </a:prstGeom>`; } function parseVariables(width, height, vars) { width = Number(OOXMLValue.encode(width, "emu")); height = Number(OOXMLValue.encode(height, "emu")); const max = Math.max(width, height); const min = Math.min(width, height); const variables = { "l": 0, "r": width, "w": width, "wd2": width / 2, "wd4": width / 4, "wd5": width / 5, "wd6": width / 6, "wd8": width / 8, "wd10": width / 10, "hc": width / 2, "t": 0, "b": height, "h": height, "hd2": height / 2, "hd4": height / 4, "hd5": height / 5, "hd6": height / 6, "hd8": height / 8, "hd10": height / 10, "vc": height / 2, "ls": max, "ss": min, "ssd2": min / 2, "ssd4": min / 4, "ssd5": min / 5, "ssd6": min / 6, "ssd8": min / 8, "ssd10": min / 10, "ssd16": min / 16, "ssd32": min / 32, "3cd4": Number(OOXMLValue.encode(360 * 3 / 4, "degree")), "3cd8": Number(OOXMLValue.encode(360 * 3 / 8, "degree")), "5cd8": Number(OOXMLValue.encode(360 * 5 / 8, "degree")), "7cd8": Number(OOXMLValue.encode(360 * 7 / 8, "degree")), "cd2": Number(OOXMLValue.encode(360 / 2, "degree")), "cd4": Number(OOXMLValue.encode(360 / 4, "degree")), "cd8": Number(OOXMLValue.encode(360 / 8, "degree")) }; function parse(variable) { if (variable in variables) { return variables[variable]; } const after = vars.find((item) => item[0] === variable); if (after) { return parse(after[1]); } const [cmd, ...args] = variable.split(" "); let res; if (cmd === "*/") { res = parse(args[0]) * parse(args[1]) / parse(args[2]); } else if (cmd === "+-") { res = parse(args[0]) + parse(args[1]) - parse(args[2]); } else if (cmd === "+/") { res = (parse(args[0]) + parse(args[1])) / parse(args[2]); } else if (cmd === "?:") { res = parse(args[0]) > 0 ? parse(args[1]) : parse(args[2]); } else if (cmd === "abs") { res = Math.abs(parse(args[0])); } else if (cmd === "max") { res = Math.max(parse(args[0]), parse(args[1])); } else if (cmd === "min") { res = Math.min(parse(args[0]), parse(args[1])); } else if (cmd === "mod") { res = Math.sqrt( parse(args[0]) * parse(args[0]) + parse(args[1]) * parse(args[1]) + parse(args[2]) * parse(args[2]) ); } else if (cmd === "pin") { res = parse(args[1]) < parse(args[0]) ? parse(args[0]) : parse(args[1]) > parse(args[2]) ? parse(args[2]) : parse(args[1]); } else if (cmd === "sqrt") { res = Math.sqrt(parse(args[0])); } else if (cmd === "val") { res = Number(args[0]); } else if (cmd === "at2") { res = Number(OOXMLValue.encode(Math.atan2(parse(args[1]), parse(args[0])) / Math.PI * 180, "degree")); } else if (cmd === "cat2") { res = parse(args[0]) * Math.cos(Math.atan2(parse(args[2]), parse(args[1]))); } else if (cmd === "sat2") { res = parse(args[0]) * Math.sin(Math.atan2(parse(args[2]), parse(args[1]))); } else if (cmd === "cos") { res = parse(args[0]) * Math.cos(OOXMLValue.decode(String(parse(args[1])), "degree") / 180 * Math.PI); } else if (cmd === "sin") { res = parse(args[0]) * Math.sin(OOXMLValue.decode(String(parse(args[1])), "degree") / 180 * Math.PI); } else if (cmd === "tan") { res = parse(args[0]) * Math.tan(OOXMLValue.decode(String(parse(args[1])), "degree") / 180 * Math.PI); } else { res = Number(variable); } variables[variable] = res; return res; } for (const item of vars) { const [name, value] = item; variables[name] = parse(value); } return variables; } function getEllipsePoint(a, b, theta) { const aSinTheta = a * Math.sin(theta); const bCosTheta = b * Math.cos(theta); const circleRadius = Math.sqrt(aSinTheta * aSinTheta + bCosTheta * bCosTheta); if (!circleRadius) { return { x: 0, y: 0 }; } return { x: a * (bCosTheta / circleRadius), y: b * (aSinTheta / circleRadius) }; } function getPaths(ctx) { const { width, height, pathLst, avLst = [], gdLst = [], strokeWidth } = ctx; const prestVars = [...avLst, ...gdLst].reduce( (vars, gd) => { const name = gd.name; const fmla = gd.fmla; if (name && fmla) { const index = fmla.startsWith("val ") ? 0 : 1; vars[index].push([name, fmla]); } return vars; }, [[], []] ); const variables = parseVariables(width, height, [ ...prestVars[0] || [], ...prestVars[1] || [] ]); return pathLst?.get("*[(self::a:path or self::path)]").map((path) => { path.attr("@extrusionOk", "boolean"); const needsFill = path.attr("@fill", "boolean") ?? true; const needsStroke = path.attr("@stroke", "boolean") ?? true; const w = path.attr("@w", "ST_PositiveCoordinate"); const h = path.attr("@h", "ST_PositiveCoordinate"); const rateX = w ? width / w : 1; const rateY = h ? height / h : 1; function convert(value, isX, type = "emu") { let newValue; value = variables[value] ?? value; if (Number.isNaN(value)) { newValue = Number( // eslint-disable-next-line no-new-func new Function( [`const width=${width}`, `const height=${height}`, `return (${value})`].join(";") )() ); } else { if (type === "emu") { newValue = OOXMLValue.decode(value, "emu"); } else { return OOXMLValue.decode(value, "degree") / 180 * Math.PI; } } return isX ? newValue * rateX : newValue * rateY; } let currentPoint; const commands = path.get("*").map((child) => { const name = child.name; if (name.endsWith("moveTo")) { const pt = child.query("*[self::a:pt or self::pt]"); const x = convert(pt?.attr("@x"), true); const y = convert(pt?.attr("@y"), false); currentPoint = { x, y }; return { type: "M", x, y }; } else if (name.endsWith("lnTo")) { const pt = child.query("*[self::a:pt or self::pt]"); const x = convert(pt.attr("@x"), true); const y = convert(pt.attr("@y"), false); currentPoint = { x, y }; return { type: "L", x, y }; } else if (name.endsWith("arcTo")) { const wr = convert(child.attr("@wR"), true); const hr = convert(child.attr("@hR"), false); const stAng = convert(child.attr("@stAng"), true, "degree"); const swAng = convert(child.attr("@swAng"), false, "degree"); const p1 = getEllipsePoint(wr, hr, stAng); const p2 = getEllipsePoint(wr, hr, stAng + swAng); currentPoint = { x: currentPoint.x - p1.x + p2.x, y: currentPoint.y - p1.y + p2.y }; const xAxisRotation = 0; const largeArcFlag = Math.abs(swAng) >= Math.PI ? 1 : 0; const sweepFlag = swAng > 0 ? 1 : 0; return { type: "A", rx: wr, ry: hr, angle: xAxisRotation, largeArcFlag, sweepFlag, x: currentPoint.x, y: currentPoint.y }; } else if (name.endsWith("cubicBezTo")) { const points = child.get("*[self::a:pt or self::pt]").map((p) => ({ x: p.attr("@x"), y: p.attr("@y") })); const x = convert(points[2].x, true); const y = convert(points[2].y, false); currentPoint = { x, y }; return { type: "C", x1: convert(points[0].x, true), y1: convert(points[0].y, false), x2: convert(points[1].x, true), y2: convert(points[1].y, false), x, y }; } else if (name.endsWith("quadBezTo")) { const points = child.get("*[(self::a:pt or self::pt)]").map((p) => ({ x: p.attr("@x"), y: p.attr("@y") })); const x = convert(points[1].x, true); const y = convert(points[1].y, false); currentPoint = { x, y }; return { type: "Q", x1: convert(points[0].x, true), y1: convert(points[0].y, false), x, y }; } else { return { type: "Z" }; } }); return { fill: needsFill ? void 0 : "none", stroke: needsStroke ? void 0 : "none", strokeWidth, commands }; }) ?? []; } function _parseTransform2dStyle(position, ctx) { if (!ctx?.parents || !ctx.parents.length) return; const parent = ctx.parents[ctx.parents.length - 1]?.transform2d; if (!parent) return; const groupPosition = { left: Number(parent.offsetX ?? position.left), top: Number(parent.offsetY ?? position.top), width: Number(parent.extentsCx ?? position.width), height: Number(parent.extentsCy ?? position.height) }; const childPosition = { left: Number(parent.childOffsetX ?? (ctx.drawing ? 0 : groupPosition.left)), top: Number(parent.childOffsetY ?? (ctx.drawing ? 0 : groupPosition.top)), width: Number(parent.childExtentsCx ?? groupPosition.width), height: Number(parent.childExtentsCy ?? groupPosition.height) }; const scaleX = childPosition.width ? groupPosition.width / childPosition.width : 1; const scaleY = childPosition.height ? groupPosition.height / childPosition.height : 1; if (position.left !== void 0) position.left = (position.left - childPosition.left) * scaleX + groupPosition.left; if (position.top !== void 0) position.top = (position.top - childPosition.top) * scaleY + groupPosition.top; if (position.height !== void 0) position.height *= scaleY; if (position.width !== void 0) position.width *= scaleX; _parseTransform2dStyle(position, { ...ctx, parents: ctx.parents.slice(0, ctx.parents.length - 1) }); } function parseTransform2d(xfrm, ctx) { const query = ctx?.query ?? xfrm?.query; const offsetX = query("a:off/@x", "emu"); const offsetY = query("a:off/@y", "emu"); const extentsCx = query("a:ext/@cx", "emu"); const extentsCy = query("a:ext/@cy", "emu"); const style = { left: offsetX, top: offsetY, width: extentsCx, height: extentsCy, rotate: query("@rot", "ST_Angle") ?? 0, scaleX: query("@flipH", "boolean") ? -1 : 1, scaleY: query("@flipV", "boolean") ? -1 : 1 }; _parseTransform2dStyle(style, ctx); return { style, rawTransform2d: { offsetX, offsetY, extentsCx, extentsCy, childOffsetX: query("a:chOff/@x", "emu"), childOffsetY: query("a:chOff/@y", "emu"), childExtentsCx: query("a:chExt/@cx", "emu"), childExtentsCy: query("a:chExt/@cy", "emu") } }; } function stringifyTransform2d(xfrm, isGroup = false) { return `<a:xfrm${withAttrs([ withAttr("rot", OOXMLValue.encode(xfrm.style?.rotate, "ST_Angle")), withAttr("flipV", OOXMLValue.encode(xfrm.style?.scaleX === -1, "boolean")), withAttr("flipH", OOXMLValue.encode(xfrm.style?.scaleY === -1, "boolean")) ])}> <a:off${withAttrs([ withAttr("x", OOXMLValue.encode(xfrm.style?.left, "emu")), withAttr("y", OOXMLValue.encode(xfrm.style?.top, "emu")) ])}/> <a:ext${withAttrs([ withAttr("cx", OOXMLValue.encode(xfrm.style?.width, "emu")), withAttr("cy", OOXMLValue.encode(xfrm.style?.height, "emu")) ])}/> ${isGroup ? `<a:chOff${withAttrs([ withAttr("x", OOXMLValue.encode(xfrm.style?.left, "emu")), withAttr("y", OOXMLValue.encode(xfrm.style?.top, "emu")) ])}/> <a:chExt${withAttrs([ withAttr("cx", OOXMLValue.encode(xfrm.style?.width, "emu")), withAttr("cy", OOXMLValue.encode(xfrm.style?.height, "emu")) ])}/>` : ""} </a:xfrm>`; } function parseShapeProperties(spPr, ctx) { if (!spPr) return void 0; const query = ctx?.query ?? spPr.query; let fill = parseFill(query(`${fillXPath}`), ctx) ?? {}; if (!spPr.find(`${fillXPath}`)) { const fillRef = spPr.find("../p:style/a:fillRef"); const fillRefIdx = fillRef?.attr("@idx", "number") ?? 1; if (ctx?.theme?.fillStyleList?.[fillRefIdx - 1]) { fill.color = parseColor(fillRef, ctx); } } fill = clearUndef(fill); let outline = parseOutline(spPr, { ...ctx, query: (xpath, type) => query(`a:ln/${xpath}`, type) }) ?? {}; if (!spPr.find(`a:ln/${fillXPath}`)) { const lnRef = spPr.find("../p:style/a:lnRef"); const lnRefIdx = lnRef?.attr("@idx", "number") ?? 1; if (ctx?.theme?.outlineStyleList?.[lnRefIdx - 1]) { outline.color = parseColor(lnRef, ctx); } } outline = clearUndef(outline); const xfrm = parseTransform2d(spPr.find("a:xfrm"), { ...ctx, query: (xpath, type) => query(`a:xfrm/${xpath}`, type) }); const geometry = parseGeometry(query("*[(self::a:prstGeom or self::a:custGeom)]"), { ...ctx, width: xfrm?.style?.width || outline.width || 1, height: xfrm?.style?.height || outline.width || 1, fill: fill.color, stroke: outline.color, strokeWidth: outline.width }); return { ...xfrm, geometry, fill: Object.keys(fill).length > 0 ? fill : void 0, outline: Object.keys(outline).length > 0 ? outline : void 0, effect: parseEffectList(query("a:effectLst"), ctx) }; } function stringifyShapeProperties(spPr, isPic = false) { const xfrm = stringifyTransform2d(spPr.style); const geom = stringifyGeometry(spPr.geometry); const fill = isPic ? void 0 : stringifyFill(spPr.fill); const ln = stringifyOutline(spPr.outline); return `<p:spPr> ${withIndents(xfrm)} ${withIndents(geom)} ${withIndents(fill)} ${withIndents(ln)} </p:spPr>`; } function parseConnectionShape(node, ctx) { if (!node) return void 0; const { placeholder, ...nvPr } = parseNonVisualProperties(node.find("p:nvCxnSpPr/p:nvPr"), ctx) ?? {}; const cNvPr = parseNonVisualDrawingProperties(node.find("p:nvCxnSpPr/p:cNvPr")); ctx = { ...ctx, placeholder }; const query = (xpath, type = "node") => { return node.query(xpath, type) ?? node.query(`p:style/${xpath}`, type) ?? placeholder?.node?.query(xpath, type); }; const { rawTransform2d: _, ...spPr } = parseShapeProperties(node.find("p:spPr"), { ...ctx, query: (xpath, type) => query(`p:spPr/${xpath}`, type) }) ?? {}; return { ...nvPr, ...cNvPr, ...spPr, style: { ...cNvPr?.style, ...spPr?.style }, meta: { ...cNvPr?.meta, type: "connection-shape", placeholderType: placeholder?.type, placeholderIndex: placeholder?.index } }; } function parseGroupShape(node, ctx, parseElement) { const { placeholder, ...nvPr } = parseNonVisualProperties(node.find("p:nvGrpSpPr/p:nvPr"), ctx) ?? {}; const cNvPr = parseNonVisualDrawingProperties(node.find("p:nvGrpSpPr/p:cNvPr")); ctx = { ...ctx, placeholder }; const queryGrpSp = (xpath, type = "node") => { return node.query(xpath, type) ?? node.query(`p:style/${xpath}`, type) ?? placeholder?.node?.query(xpath, type); }; const { rawTransform2d, ...grpSpPr } = parseShapeProperties(node.find("p:grpSpPr"), { ...ctx, query: (xpath, type) => queryGrpSp(`p:grpSpPr/${xpath}`, type) }) ?? {}; const groupShape = { ...nvPr, ...cNvPr, ...grpSpPr, style: { ...cNvPr?.style, ...grpSpPr?.style }, children: [], meta: { ...cNvPr?.meta, type: "group-shape", placeholderType: placeholder?.type, placeholderIndex: placeholder?.index } }; ctx = { ...ctx, parents: [