UNPKG

modern-openxml

Version:
1,648 lines (1,626 loc) 161 kB
import { zipSync, unzipSync } from 'fflate'; import { normalizeColor, idGenerator, isGradient } from 'modern-idoc'; import { svgPathCommandsToData, svgPathDataToCommands, Path2D } from 'modern-path2d'; let customDomParser; function useCustomDomParser(parser) { customDomParser = parser; } function parseDomFromString(string, type) { if (customDomParser) { return customDomParser(string, type); } return new globalThis.DOMParser().parseFromString(string, type); } const namespaces = { asvg: "http://schemas.microsoft.com/office/drawing/2016/SVG/main", p: "http://schemas.openxmlformats.org/presentationml/2006/main", r: "http://schemas.openxmlformats.org/officeDocument/2006/relationships", a: "http://schemas.openxmlformats.org/drawingml/2006/main", a14: "http://schemas.microsoft.com/office/drawing/2010/main", dgm: "http://schemas.openxmlformats.org/drawingml/2006/diagram", dsp: "http://schemas.microsoft.com/office/drawing/2008/diagram", mc: "http://schemas.openxmlformats.org/markup-compatibility/2006", p14: "http://schemas.microsoft.com/office/powerpoint/2010/main", p15: "http://schemas.microsoft.com/office/powerpoint/2012/main", p159: "http://schemas.microsoft.com/office/powerpoint/2015/09/main", cp: "http://schemas.openxmlformats.org/package/2006/metadata/core-properties", dc: "http://purl.org/dc/elements/1.1/", dcterms: "http://purl.org/dc/terms/", dcmitype: "http://purl.org/dc/dcmitype/", xsi: "http://www.w3.org/2001/XMLSchema-instance" }; class OoxmlValue { static DPI = 72; static encode(value, type) { value ??= 0; switch (type) { case "boolean": return value ? "1" : "0"; case "degree": case "ST_Angle": case "ST_PositiveFixedAngle": case "positiveFixedAngle": return String(~~(Number(value) * 6e4)); case "fontSize": return String(~~(Number(value) * 100)); case "int": case "unsignedInt": case "number": case "SByteValue": case "ST_TLTimeNodeID": case "ST_ShapeID": return String(~~value); case "string": case "HexBinaryValue": case "StringValue": case "ST_LineEndLength": case "ST_LineEndWidth": return String(~~value); case "emu": case "ST_PositiveCoordinate": case "ST_LineWidth": case "ST_Coordinate32": case "ST_AdjCoordinate": return String(~~(Number(value) / this.DPI * 914400)); case "dxa": return String(~~(Number(value) / this.DPI * 1440)); case "percentage": case "ST_Percentage": case "ST_PositivePercentage": case "CT_PositiveFixedPercentage": case "ST_PositiveFixedPercentage": case "positiveFixedPercentage": case "ST_TextSpacingPercentOrPercentString": case "rate": return String(~~(Number(value) * 1e5)); case "ST_TextSpacingPoint": return String(~~(value * 100)); 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": case "ST_LineEndLength": case "ST_LineEndWidth": 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 "ST_TextSpacingPercentOrPercentString": case "rate": return Number(value) / 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, namespaces2) { this.dom = dom; this.namespaces = namespaces2; 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 = namespaces) { xml = xml.replace(/xmlns=".*?"/g, ""); for (const key in fixtures) { xml = xml.replace(new RegExp(key, "gi"), fixtures[key]); } const doc = parseDomFromString(xml, "text/xml"); const namespaces2 = {}; for (const [, key, value] of xml.matchAll(/xmlns:(\w)="(.+?)"/g)) { namespaces2[key] = value; } return new OoxmlNode( doc.documentElement, { ...namespaces2, ...userNamespaces } ); } getDOM() { return this.dom; } evaluate(xpath, type = 0) { return this.doc.evaluate( xpath, this.dom, this.resolver, type, null ); } query(xpath, type = "node") { switch (type) { case "node": { const result = this.evaluate( xpath, 9 // XPathResult.FIRST_ORDERED_NODE_TYPE ).singleNodeValue; return result ? new OoxmlNode(result, this.namespaces) : void 0; } case "nodes": { const result = this.evaluate( xpath, 5 // XPathResult.ORDERED_NODE_ITERATOR_TYPE ); const value = []; let node; while (node = result.iterateNext()) { value.push(new OoxmlNode(node, this.namespaces)); } return value; } default: { let value; if (xpath[0] === "@" && "getAttribute" in this.dom) { value = this.dom.getAttribute(xpath.substring(1)); } else { value = this.evaluate( xpath, 2 // XPathResult.STRING_TYPE ).stringValue; } return OoxmlValue.decode(value || 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 parseCoreProperties(node) { return { title: node.attr("dc:title/text()", "string"), subject: node.attr("dc:subject/text()", "string"), creator: node.attr("dc:creator/text()", "string"), lastModifiedBy: node.attr("dc:lastModifiedBy/text()", "string"), revision: node.attr("dc:revision/text()", "string"), modified: node.attr("dcterms:modified/text()", "string") }; } function stringifyCoreProperties(props) { 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>${props.title ?? "modern-openxml"}</dc:title> <dc:subject>${props.subject ?? "modern-openxml"}</dc:subject> <dc:creator>${props.creator ?? "modern-openxml"}</dc:creator> <cp:lastModifiedBy>${props.lastModifiedBy ?? "modern-openxml"}</cp:lastModifiedBy> <cp:revision>${props.revision ?? 1}</cp:revision> <dcterms:modified xsi:type="dcterms:W3CDTF">${str}</dcterms:modified> </cp:coreProperties>`; } 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 parseHex6Color(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 hex6 = parseHex6Color(node, ctx); if (!hex6 || !hex6.startsWith("#")) { return { color: hex6 }; } const rgba = { ...hexToRgb(hex6), 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 { color: normalizeColor(rgba) // hex8 }; } 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) => { map[color.name.replace("a:", "")] = parseColor(color)?.color; }); return map; } function parseInnerShadow(innerShdw, ctx) { if (!innerShdw) return void 0; const color = parseColor(innerShdw, ctx); if (!color) return void 0; const blurRadius = innerShdw.attr("@blurRad", "ST_PositiveCoordinate") ?? 0; const dir = innerShdw.attr("@dir", "ST_PositiveFixedAngle") ?? 0; const dist = innerShdw.attr("@dist", "ST_PositiveCoordinate") ?? 0; const radian = (dir + 90) / 180 * Math.PI; return { ...color, offsetX: dist * Math.sin(radian), offsetY: dist * -Math.cos(radian), blurRadius }; } function parseOuterShadow(outerShdw, ctx) { const base = parseInnerShadow(outerShdw, ctx); if (!base) { return void 0; } const scaleX = outerShdw.attr("@sx", "ST_Percentage") ?? 1; const scaleY = outerShdw.attr("@sy", "ST_Percentage") ?? 1; return { ...base, scaleX, scaleY }; } 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")) }; } class BiMap { forward = /* @__PURE__ */ new Map(); reverse = /* @__PURE__ */ new Map(); constructor(source) { for (const key in source) { this.set(key, source[key]); } } set(key, value) { this.forward.set(key, value); this.reverse.set(value, key); } getValue(key) { return this.forward.get(key); } getKey(value) { return this.reverse.get(value); } deleteByKey(key) { const value = this.forward.get(key); this.forward.delete(key); if (value !== void 0) { this.reverse.delete(value); } } deleteByValue(value) { const key = this.reverse.get(value); this.reverse.delete(value); if (key !== void 0) { this.forward.delete(key); } } } 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) { return ""; } const spaces = Array.from({ length: deep }).map(() => " ").join(""); return (typeof str === "string" ? str.split("\n") : str).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 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; } function pathJoin(...segments) { const parts = []; for (let segment of segments) { if (typeof segment !== "string") { throw new TypeError("All arguments to pathJoin must be strings"); } segment = segment.trim(); if (segment === "") continue; segment = segment.replace(/^\/+|\/+$/g, ""); if (segment) { parts.push(segment); } } const joined = parts.join("/"); return normalizePath(joined); } function normalizePath(path) { const isAbsolute = path.startsWith("/"); const segments = path.split("/"); const stack = []; for (const segment of segments) { if (segment === "" || segment === ".") { continue; } else if (segment === "..") { if (stack.length > 0 && stack[stack.length - 1] !== "..") { stack.pop(); } else if (!isAbsolute) { stack.push(".."); } } else { stack.push(segment); } } const normalized = (isAbsolute ? "/" : "") + stack.join("/"); return normalized || (isAbsolute ? "/" : "."); } 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 { ...parseColor(fill, ctx) }; case "a:gradFill": return 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 image; if (ctx?.drawing) { image = ctx?.drawing.rels.find((v) => v.id === embed)?.path; } else { image = ctx?.rels?.find((v) => v.id === embed)?.path; } image = image ?? embed; const srcRectNode = fill.find("a:srcRect"); const cropRect = 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 stretchRect = 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 { image, cropRect: cropRect && Object.keys(cropRect).length > 0 ? cropRect : void 0, stretchRect: stretchRect && Object.keys(stretchRect).length > 0 ? stretchRect : void 0, dpi: fill.attr("@dpi", "number"), opacity: fill.attr("a:blip/a:alphaModFix/@amt", "ST_PositivePercentage"), tile: tile && Object.keys(tile).length > 0 ? tile : void 0, rotateWithShape: fill.attr("@rotWithShape", "boolean") }; } function parseGradientFill(gradFill, ctx) { if (!gradFill) return void 0; const stops = gradFill.get("a:gsLst/a:gs").map((gs) => { return { ...parseColor(gs, ctx), offset: gs.attr("@pos", "positiveFixedPercentage") ?? 0 }; }).filter(({ color }) => color).sort((a, b) => a.offset - b.offset); if (!stops.length) return void 0; if (gradFill.attr("a:path/@path") === "circle") { return { radialGradient: { stops } }; } return { linearGradient: { angle: ((gradFill.attr("a:lin/@ang", "positiveFixedAngle") ?? 0) + 90) % 360, stops } }; } function stringifyFill(fill, isPic = false) { if (!fill) return void 0; if (Boolean(fill.image) || isPic) { const tagName = isPic ? "p:blipFill" : "a:blipFill"; const url = fill.image ?? fill.image; return `<${tagName}${withAttrs([ withAttr("dpi", OoxmlValue.encode(fill.dpi, "number")), withAttr("rotWithShape", OoxmlValue.encode(fill.rotateWithShape, "boolean")) ])}> <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${withAttrs([ !!fill.cropRect?.top && withAttr("t", OoxmlValue.encode(fill.cropRect?.top, "ST_Percentage")), !!fill.cropRect?.right && withAttr("r", OoxmlValue.encode(fill.cropRect?.right, "ST_Percentage")), !!fill.cropRect?.bottom && withAttr("b", OoxmlValue.encode(fill.cropRect?.bottom, "ST_Percentage")), !!fill.cropRect?.left && withAttr("l", OoxmlValue.encode(fill.cropRect?.left, "ST_Percentage")) ])}/> <a:stretch> <a:fillRect${withAttrs([ !!fill.stretchRect?.top && withAttr("t", OoxmlValue.encode(fill.stretchRect?.top, "ST_Percentage")), !!fill.stretchRect?.right && withAttr("r", OoxmlValue.encode(fill.stretchRect?.right, "ST_Percentage")), !!fill.stretchRect?.bottom && withAttr("b", OoxmlValue.encode(fill.stretchRect?.bottom, "ST_Percentage")), !!fill.stretchRect?.left && withAttr("l", OoxmlValue.encode(fill.stretchRect?.left, "ST_Percentage")) ])}/> </a:stretch> </${tagName}>`; } else if (Boolean(fill.linearGradient) || Boolean(fill.radialGradient)) { return stringifyGradientFill(fill); } else if (fill.color) { return stringifySolidFill(fill.color); } return `<a:noFill/>`; } function stringifySolidFill(color) { return `<a:solidFill> ${withIndents(stringifyColor(color))} </a:solidFill>`; } function stringifyGradientFill(fill) { const { linearGradient } = fill; if (linearGradient) { const { angle, stops } = linearGradient; let degree = angle; degree = degree ? (degree + 270) % 360 : degree; const ang = OoxmlValue.encode(degree, "positiveFixedAngle"); const gs = stops.map((stop) => { const { offset, color } = stop; return `<a:gs pos="${offset * 1e5}"> ${withIndents(stringifyColor(color))} </a:gs>`; }); return `<a:gradFill> <a:gsLst> ${withIndents(gs, 2)} </a:gsLst> <a:lin${withAttrs([withAttr("ang", ang), withAttr("scaled", 0)])}/> </a:gradFill>`; } else { 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; }, {}); } function parseRectangle(rect) { const res = { left: rect?.attr("@l"), top: rect?.attr("@t"), right: rect?.attr("@r"), bottom: rect?.attr("@b") }; return Object.keys(res).length ? res : void 0; } function parseAdjustValueList(avLst) { return avLst.get("*[(self::a:gd or self::gd)]").map((gd) => parseAdjustValue(gd)); } function parseShapeGuideList(gdLst) { return gdLst.get("*[(self::a:gd or self::gd)]").map((gd) => parseShapeGuide(gd)); } function parseAdjustValue(gd) { const fmla = gd.attr("@fmla"); if (!fmla.startsWith("val ")) { console.warn("Failed to parse constant shape guide"); } const value = parseShapeGuideFmla(fmla, { width: 0, height: 0, variables: {} }); if (Number.isNaN(value)) { console.warn("Failed to parse constant shape guide"); } return { name: gd.attr("@name"), value }; } function parseShapeGuide(gd) { return { name: gd.attr("@name"), fmla: gd.attr("@fmla") }; } function parseAdjustHandleList(ahLst) { return ahLst.get("*[(self::a:ahXY or self::ahXY)]").map((ahXY) => clearUndef({ gdRefX: ahXY.attr("@gdRefX"), gdRefY: ahXY.attr("@gdRefY"), minX: ahXY.attr("@minX"), maxX: ahXY.attr("@maxX"), minY: ahXY.attr("@minY"), maxY: ahXY.attr("@maxY"), posX: ahXY.attr("pos/@x"), posY: ahXY.attr("pos/@y") })); } function parseShapeGuideValue(value, ctx, parent) { if (!Number.isNaN(Number(value))) { return Number(value); } if (value in ctx.variables) { return ctx.variables[value]; } const fmla = parent?.find(`gd[@name='${value}']`)?.attr("@fmla") ?? value; return ctx.variables[value] = parseShapeGuideFmla(fmla, ctx, parent); } function parseShapeGuideFmla(fmla, ctx, parent) { switch (fmla) { case "l": case "t": return 0; case "r": case "w": return ctx.width; case "b": case "h": return ctx.height; case "hc": return ctx.width / 2; case "vc": return ctx.height / 2; case "ls": return Math.max(ctx.width, ctx.height); case "ss": return Math.min(ctx.width, ctx.height); } if (fmla.startsWith("val")) { return parseShapeGuideValue(fmla.substring(4), ctx, parent); } else if (fmla.startsWith("wd")) { return ctx.width / Number(fmla.substring(2)); } else if (fmla.startsWith("hd")) { return ctx.height / Number(fmla.substring(2)); } else if (fmla.startsWith("ssd")) { return Math.min(ctx.width, ctx.height) / Number(fmla.substring(3)); } else if (fmla.startsWith("cd")) { return Number(OoxmlValue.encode(360 / Number(fmla.substring(2)), "degree")); } else if (/^\d+cd\d+$/.test(fmla)) { const match = fmla.match(/^(\d+)cd(\d+)$/); if (match && match[1] && match[2]) { return Number(OoxmlValue.encode(360 * Number(match[1]) / Number(match[2]), "degree")); } } const [op, ..._args] = fmla.split(" "); const args = _args.map((v) => v.trim()).filter((v) => v !== "").map((arg) => parseShapeGuideValue(arg, ctx, parent)); switch (op) { case "*/": return args[0] * args[1] / args[2]; case "+-": return args[0] + args[1] - args[2]; case "+/": return (args[0] + args[1]) / args[2]; case "?:": return args[0] > 0 ? args[1] : args[2]; case "abs": return Math.abs(args[0]); case "max": return Math.max(args[0], args[1]); case "min": return Math.min(args[0], args[1]); case "mod": return Math.sqrt(args[0] * args[0] + args[1] * args[1] + args[2] * args[2]); case "pin": return args[1] < args[0] ? args[0] : args[1] > args[2] ? args[2] : args[1]; case "sqrt": return Math.sqrt(args[0]); case "val": return args[0]; case "at2": return Number(OoxmlValue.encode(Math.atan2(args[1], args[0]) / Math.PI * 180, "degree")); case "cat2": return args[0] * Math.cos(Math.atan2(args[2], args[1])); case "sat2": return args[0] * Math.sin(Math.atan2(args[2], args[1])); case "cos": return args[0] * Math.cos(OoxmlValue.decode(String(args[1]), "degree") / 180 * Math.PI); case "sin": return args[0] * Math.sin(OoxmlValue.decode(String(args[1]), "degree") / 180 * Math.PI); case "tan": return args[0] * Math.tan(OoxmlValue.decode(String(args[1]), "degree") / 180 * Math.PI); default: return Number(fmla); } } 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 parsePaths(pathLst, ctx) { const { width, height } = ctx; 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", "number"); const h = path.attr("@h", "number"); const rateX = w ? width / w : 1; const rateY = h ? height / h : 1; function convert(gdValue, isX, type = "emu") { const value = parseShapeGuideValue(gdValue, ctx); if (type === "emu") { return OoxmlValue.decode(isX ? value * rateX : value * rateY, "emu"); } else { return OoxmlValue.decode(value, "degree") / 180 * Math.PI; } } 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"); let swAng = convert(child.attr("@swAng"), false, "degree"); if (Math.abs(swAng) === 2 * Math.PI) { swAng = swAng - swAng / 360; } 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 clearUndef({ data: svgPathCommandsToData(commands), fill: needsFill ? void 0 : "none", fillRule: "evenodd", stroke: needsStroke ? void 0 : "none" }); }) ?? []; } 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); const avLst = node?.find("avLst"); const gdLst = node?.find("gdLst"); const overlayAvLst = prstGeom?.find("a:avLst"); const _ctx = { width: Number(OoxmlValue.encode(ctx?.width ?? 0, "emu")), height: Number(OoxmlValue.encode(ctx?.height ?? 0, "emu")), variables: {} }; if (avLst) { parseAdjustValueList(avLst).forEach((gd) => { _ctx.variables[gd.name] = gd.value; }); } if (overlayAvLst) { parseAdjustValueList(overlayAvLst).forEach((gd) => { _ctx.variables[gd.name] = gd.value; }); } if (gdLst) { parseShapeGuideList(gdLst).forEach((gd) => { _ctx.variables[gd.name] = parseShapeGuideFmla(gd.fmla, _ctx); }); } return { preset, paths: parsePaths(node?.find("pathLst"), _ctx) }; } else if (custGeom) { const avLst = custGeom?.find("avLst"); const gdLst = custGeom?.find("gdLst"); const _ctx = { width: Number(OoxmlValue.encode(ctx?.width ?? 0, "emu")), height: Number(OoxmlValue.encode(ctx?.height ?? 0, "emu")), variables: {} }; if (avLst) { parseAdjustValueList(avLst).forEach((gd) => { _ctx.variables[gd.name] = gd.value; }); } if (gdLst) { parseShapeGuideList(gdLst).forEach((gd) => { _ctx.variables[gd.name] = parseShapeGuideFmla(gd.fmla, _ctx); }); } return { paths: parsePaths(custGeom.find("a:pathLst"), _ctx) }; } } } function stringifyGeometry(shape) { if (shape?.paths?.length) { return `<a:custGeom> <a:avLst/> <a:gdLst/> <a:ahLst/> <a:cxnLst/> <a:rect l="l" t="t" r="r" b="b"/> <a:pathLst> ${withIndents(shape.paths.map((path) => { let currentPoint; return `<a:path> ${withIndents(svgPathDataToCommands(path.data).map((cmd) => { switch (cmd.type) { case "m": case "M": currentPoint = { x: cmd.x, y: cmd.y }; return `<a:moveTo> <a:pt${withAttrs([ withAttr("x", OoxmlValue.encode(cmd.x, "emu")), withAttr("y", OoxmlValue.encode(cmd.y, "emu")) ])}/> </a:moveTo>`; case "l": case "L": currentPoint = { x: cmd.x, y: cmd.y }; return `<a:lnTo> <a:pt${withAttrs([ withAttr("x", OoxmlValue.encode(cmd.x, "emu")), withAttr("y", OoxmlValue.encode(cmd.y, "emu")) ])}/> </a:lnTo>`; case "a": case "A": { const startX = currentPoint.x; const startY = currentPoint.y; let { rx, ry, angle, largeArcFlag, sweepFlag, x: endX, y: endY } = cmd; const phi = angle * (Math.PI / 180); const dx = (startX - endX) / 2; const dy = (startY - endY) / 2; const x1p = Math.cos(phi) * dx + Math.sin(phi) * dy; const y1p = -Math.sin(phi) * dx + Math.cos(phi) * dy; let rx_sq = rx * rx; let ry_sq = ry * ry; const x1p_sq = x1p * x1p; const y1p_sq = y1p * y1p; const lambda = x1p_sq / rx_sq + y1p_sq / ry_sq; if (lambda > 1) { const factor = Math.sqrt(lambda); rx *= factor; ry *= factor; rx_sq = rx * rx; ry_sq = ry * ry; } const sign = largeArcFlag === sweepFlag ? -1 : 1; const coef = sign * Math.sqrt( (rx_sq * ry_sq - rx_sq * y1p_sq - ry_sq * x1p_sq) / (rx_sq * y1p_sq + ry_sq * x1p_sq) ); const cxp = coef * (rx * y1p) / ry; const cyp = coef * (-ry * x1p) / rx; const vectorU = [(x1p - cxp) / rx, (y1p - cyp) / ry]; const vectorV = [(-x1p - cxp) / rx, (-y1p - cyp) / ry]; const startAngle = Math.atan2(vectorU[1], vectorU[0]); let deltaAngle = Math.atan2( vectorU[0] * vectorV[1] - vectorU[1] * vectorV[0], vectorU[0] * vectorV[0] + vectorU[1] * vectorV[1] ); if (!sweepFlag && deltaAngle > 0) { deltaAngle -= 2 * Math.PI; } else if (sweepFlag && deltaAngle < 0) { deltaAngle += 2 * Math.PI; } const stAng = startAngle * 180 / Math.PI; const swAng = deltaAngle * 180 / Math.PI; currentPoint = { x: cmd.x, y: cmd.y }; return `<a:arcTo${withAttrs([ withAttr("wR", OoxmlValue.encode(rx, "emu")), withAttr("hR", OoxmlValue.encode(ry, "emu")), withAttr("stAng", OoxmlValue.encode(stAng, "degree")), withAttr("swAng", OoxmlValue.encode(swAng, "degree")) ])}/>`; } case "c": case "C": currentPoint = { x: cmd.x, y: cmd.y }; return `<a:cubicBezTo> <a:pt${withAttrs([ withAttr("x", OoxmlValue.encode(cmd.x1, "emu")), withAttr("y", OoxmlValue.encode(cmd.y1, "emu")) ])}/> <a:pt${withAttrs([ withAttr("x", OoxmlValue.encode(cmd.x2, "emu")), withAttr("y", OoxmlValue.encode(cmd.y2, "emu")) ])}/> <a:pt${withAttrs([ withAttr("x", OoxmlValue.encode(cmd.x, "emu")), withAttr("y", OoxmlValue.encode(cmd.y, "emu")) ])}/> </a:cubicBezTo>`; case "q": case "Q": currentPoint = { x: cmd.x, y: cmd.y }; return `<a:quadBezTo> <a:pt${withAttrs([ withAttr("x", OoxmlValue.encode(cmd.x1, "emu")), withAttr("y", OoxmlValue.encode(cmd.y1, "emu")) ])}/> <a:pt${withAttrs([ withAttr("x", OoxmlValue.encode(cmd.x, "emu")), withAttr("y", OoxmlValue.encode(cmd.y, "emu")) ])}/> </a:quadBezTo>`; case "z": case "Z": return `<a:close/>`; } return ""; }), 2)} </a:path>`; }), 2)} </a:pathLst> </a:custGeom>`; } else { return `<a:prstGeom prst="${shape?.preset ?? "rect"}"> <a:avLst/> </a:prstGeom>`; } } const lineCapMap = new BiMap({ flat: "butt", // default rnd: "round", sq: "square" }); 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 _headEnd = node.find("a:headEnd"); const _tailEnd = node.find("a:tailEnd"); let lineJoin = "miter"; if (node.find("a:round")) { lineJoin = "round"; } else if (node.find("a:bevel")) { lineJoin = "bevel"; } function toWH(val) { return val === "med" ? "md" : val; } return { style: prstDash ? prstDash !== "solid" ? "dashed" : "solid" : void 0, width: node.attr("@w", "ST_LineWidth"), lineCap: lineCapMap.getValue(node.attr("@cap", "string")) ?? "butt", lineJoin, color: parseFill(query(fillXPath), ctx)?.color, headEnd: _headEnd ? { type: _headEnd.attr("@type"), width: toWH(_headEnd.attr("@w", "ST_LineEndWidth")), height: toWH(_headEnd.attr("@len", "ST_LineEndLength")) } : void 0, tailEnd: _tailEnd ? { type: _tailEnd.attr("@type"), width: toWH(_tailEnd.attr("@w", "ST_LineEndWidth")), height: toWH(_tailEnd.attr("@len", "ST_LineEndLength")) } : void 0 }; } function stringifyOutline(ln) { if (!ln) return void 0; return `<a:ln${withAttrs([ ln.width !== void 0 && withAttr("w", OoxmlValue.encode(ln.width, "ST_LineWidth")), ln.lineCap !== void 0 && ln.lineCap !== "butt" && withAttr("cap", lineCapMap.getKey(ln.lineCap)) ])}> ${withIndents([ ln.lineJoin !== void 0 && ln.lineJoin !== "miter" && `<a:${ln.lineJoin}/>`, ln.color !== void 0 && stringifySolidFill(String(ln.color)), ln.color === void 0 && "<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 { ...parseColor(bgRef, ctx), fillWithShape: true }; } return bgFill; } else { return { ...parseFill(bg.find("p:bgPr"), ctx), fillWithShape: true }; } } 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: cNvP