modern-openxml
Version:
OpenXML for JavaScript
1,648 lines (1,626 loc) • 161 kB
JavaScript
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 = {
"‚": "\u201A",
"„": "\u201E",
"…": "\u2026",
"‰": "\u2030",
"ˆ": "\u02C6",
"¢": "\uFFE0",
"£": "\xA3",
"¥": "\xA5",
"€": "\u20AC",
"§": "\xA7",
"©": "\xA9",
"®": "\xAE",
"™": "\u2122",
"×": "\xD7",
"÷": "\xF7",
"ƒ": "\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