modern-openxml
Version:
OpenXML for JavaScript
1,663 lines (1,638 loc) • 159 kB
JavaScript
import { zipSync, unzipSync } from 'fflate';
import { svgPathCommandsToData } from 'modern-path2d';
import { measureText } from '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 = {
"‚": "\u201A",
"„": "\u201E",
"…": "\u2026",
"‰": "\u2030",
"ˆ": "\u02C6",
"¢": "\uFFE0",
"£": "\xA3",
"¥": "\xA5",
"€": "\u20AC",
"§": "\xA7",
"©": "\xA9",
"®": "\xAE",
"™": "\u2122",
"×": "\xD7",
"÷": "\xF7",
"ƒ": "\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: 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: 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: [
...ctx.parents ?