@opengeni/artifact-tool 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +104 -0
  3. package/dist/bounded-zip.d.ts +28 -0
  4. package/dist/chunk-4BSENMAE.js +39 -0
  5. package/dist/chunk-4BSENMAE.js.map +1 -0
  6. package/dist/chunk-4FRFNNBM.js +276 -0
  7. package/dist/chunk-4FRFNNBM.js.map +1 -0
  8. package/dist/chunk-5XIA6KHZ.js +49 -0
  9. package/dist/chunk-5XIA6KHZ.js.map +1 -0
  10. package/dist/chunk-66CT2ZRP.js +2229 -0
  11. package/dist/chunk-66CT2ZRP.js.map +1 -0
  12. package/dist/chunk-72SYDU4O.js +319 -0
  13. package/dist/chunk-72SYDU4O.js.map +1 -0
  14. package/dist/chunk-B2ZJRZDA.js +279 -0
  15. package/dist/chunk-B2ZJRZDA.js.map +1 -0
  16. package/dist/chunk-DCTLTNPW.js +2458 -0
  17. package/dist/chunk-DCTLTNPW.js.map +1 -0
  18. package/dist/chunk-FGZM4FQ6.js +473 -0
  19. package/dist/chunk-FGZM4FQ6.js.map +1 -0
  20. package/dist/chunk-HTJBLBDE.js +591 -0
  21. package/dist/chunk-HTJBLBDE.js.map +1 -0
  22. package/dist/chunk-IYFXZQ34.js +5306 -0
  23. package/dist/chunk-IYFXZQ34.js.map +1 -0
  24. package/dist/chunk-J6N7IR44.js +172 -0
  25. package/dist/chunk-J6N7IR44.js.map +1 -0
  26. package/dist/chunk-N2IHNWBS.js +1002 -0
  27. package/dist/chunk-N2IHNWBS.js.map +1 -0
  28. package/dist/chunk-NAG44K67.js +549 -0
  29. package/dist/chunk-NAG44K67.js.map +1 -0
  30. package/dist/chunk-O24KTEGP.js +33 -0
  31. package/dist/chunk-O24KTEGP.js.map +1 -0
  32. package/dist/chunk-PI2AZVGK.js +2074 -0
  33. package/dist/chunk-PI2AZVGK.js.map +1 -0
  34. package/dist/chunk-Q2FFHEY7.js +40 -0
  35. package/dist/chunk-Q2FFHEY7.js.map +1 -0
  36. package/dist/chunk-RBGQADXA.js +57 -0
  37. package/dist/chunk-RBGQADXA.js.map +1 -0
  38. package/dist/chunk-U5G2BKM2.js +613 -0
  39. package/dist/chunk-U5G2BKM2.js.map +1 -0
  40. package/dist/chunk-VOJHMSM3.js +39 -0
  41. package/dist/chunk-VOJHMSM3.js.map +1 -0
  42. package/dist/chunk-VUNV25KB.js +16 -0
  43. package/dist/chunk-VUNV25KB.js.map +1 -0
  44. package/dist/chunk-XIFUNVBO.js +101 -0
  45. package/dist/chunk-XIFUNVBO.js.map +1 -0
  46. package/dist/chunk-ZCJYRJGB.js +489 -0
  47. package/dist/chunk-ZCJYRJGB.js.map +1 -0
  48. package/dist/chunk-ZZXPEE4S.js +2260 -0
  49. package/dist/chunk-ZZXPEE4S.js.map +1 -0
  50. package/dist/document-docx-api.d.ts +49 -0
  51. package/dist/document-docx-codec.d.ts +9 -0
  52. package/dist/document-docx-codec.js +4765 -0
  53. package/dist/document-docx-codec.js.map +1 -0
  54. package/dist/document-docx-import.d.ts +261 -0
  55. package/dist/document-docx-packer.d.ts +18 -0
  56. package/dist/document-docx-state.d.ts +12 -0
  57. package/dist/document-render.d.ts +3 -0
  58. package/dist/document-render.js +492 -0
  59. package/dist/document-render.js.map +1 -0
  60. package/dist/document.d.ts +432 -0
  61. package/dist/document.js +37 -0
  62. package/dist/document.js.map +1 -0
  63. package/dist/errors.d.ts +23 -0
  64. package/dist/file-blob.d.ts +11 -0
  65. package/dist/index.d.ts +4 -0
  66. package/dist/index.js +186 -0
  67. package/dist/index.js.map +1 -0
  68. package/dist/inspection.d.ts +12 -0
  69. package/dist/kernel.d.ts +101 -0
  70. package/dist/lib-GQLPACFK.js +9746 -0
  71. package/dist/lib-GQLPACFK.js.map +1 -0
  72. package/dist/materializer-cli-entry.d.ts +2 -0
  73. package/dist/materializer-cli-entry.js +29 -0
  74. package/dist/materializer-cli-entry.js.map +1 -0
  75. package/dist/materializer-cli.d.ts +24 -0
  76. package/dist/materializer-cli.js +24 -0
  77. package/dist/materializer-cli.js.map +1 -0
  78. package/dist/native-raster.d.ts +10 -0
  79. package/dist/native.d.ts +129 -0
  80. package/dist/native.js +31 -0
  81. package/dist/native.js.map +1 -0
  82. package/dist/office-import.d.ts +48 -0
  83. package/dist/office-import.js +154 -0
  84. package/dist/office-import.js.map +1 -0
  85. package/dist/presentation-pptx-api.d.ts +60 -0
  86. package/dist/presentation-pptx-import.d.ts +5 -0
  87. package/dist/presentation-pptx-state-digest-765CZZED.js +10 -0
  88. package/dist/presentation-pptx-state-digest-765CZZED.js.map +1 -0
  89. package/dist/presentation-pptx-state-digest.d.ts +3 -0
  90. package/dist/presentation-pptx-state.d.ts +4 -0
  91. package/dist/presentation-pptx-xml.d.ts +29 -0
  92. package/dist/presentation-pptx.d.ts +5 -0
  93. package/dist/presentation-pptx.js +2365 -0
  94. package/dist/presentation-pptx.js.map +1 -0
  95. package/dist/presentation-render.d.ts +29 -0
  96. package/dist/presentation-render.js +375 -0
  97. package/dist/presentation-render.js.map +1 -0
  98. package/dist/presentation.d.ts +565 -0
  99. package/dist/presentation.js +71 -0
  100. package/dist/presentation.js.map +1 -0
  101. package/dist/production-composite.d.ts +68 -0
  102. package/dist/production-document.d.ts +2 -0
  103. package/dist/production-document.js +176 -0
  104. package/dist/production-document.js.map +1 -0
  105. package/dist/production-native-codecs.d.ts +94 -0
  106. package/dist/production-presentation.d.ts +2 -0
  107. package/dist/production-presentation.js +176 -0
  108. package/dist/production-presentation.js.map +1 -0
  109. package/dist/production-sha256.d.ts +2 -0
  110. package/dist/production-spreadsheet-native.d.ts +12 -0
  111. package/dist/production-spreadsheet.d.ts +2 -0
  112. package/dist/production-spreadsheet.js +176 -0
  113. package/dist/production-spreadsheet.js.map +1 -0
  114. package/dist/production.d.ts +96 -0
  115. package/dist/raster-image.d.ts +34 -0
  116. package/dist/reference.d.ts +17 -0
  117. package/dist/reference.js +574 -0
  118. package/dist/reference.js.map +1 -0
  119. package/dist/runtime-cli-entry.d.ts +2 -0
  120. package/dist/runtime-cli-entry.js +21 -0
  121. package/dist/runtime-cli-entry.js.map +1 -0
  122. package/dist/runtime-cli.d.ts +76 -0
  123. package/dist/runtime-cli.js +26 -0
  124. package/dist/runtime-cli.js.map +1 -0
  125. package/dist/runtime-development.d.ts +73 -0
  126. package/dist/runtime-development.js +30 -0
  127. package/dist/runtime-development.js.map +1 -0
  128. package/dist/runtime-receipt.d.ts +19 -0
  129. package/dist/runtime.d.ts +355 -0
  130. package/dist/runtime.js +42 -0
  131. package/dist/runtime.js.map +1 -0
  132. package/dist/snapshot.d.ts +26 -0
  133. package/dist/snapshot.js +61 -0
  134. package/dist/snapshot.js.map +1 -0
  135. package/dist/spreadsheet-address.d.ts +18 -0
  136. package/dist/spreadsheet-file.d.ts +17 -0
  137. package/dist/spreadsheet-formula.d.ts +67 -0
  138. package/dist/spreadsheet-image.d.ts +17 -0
  139. package/dist/spreadsheet-render.d.ts +5 -0
  140. package/dist/spreadsheet-render.js +752 -0
  141. package/dist/spreadsheet-render.js.map +1 -0
  142. package/dist/spreadsheet-types.d.ts +155 -0
  143. package/dist/spreadsheet-xlsx-api.d.ts +88 -0
  144. package/dist/spreadsheet-xlsx-codec.d.ts +16 -0
  145. package/dist/spreadsheet-xlsx-codec.js +16 -0
  146. package/dist/spreadsheet-xlsx-codec.js.map +1 -0
  147. package/dist/spreadsheet-xlsx-state.d.ts +16 -0
  148. package/dist/spreadsheet.d.ts +724 -0
  149. package/dist/spreadsheet.js +67 -0
  150. package/dist/spreadsheet.js.map +1 -0
  151. package/package.json +129 -0
  152. package/src/bounded-zip.ts +450 -0
  153. package/src/document-docx-api.ts +64 -0
  154. package/src/document-docx-codec.ts +2253 -0
  155. package/src/document-docx-import.ts +3814 -0
  156. package/src/document-docx-packer.ts +72 -0
  157. package/src/document-docx-state.ts +59 -0
  158. package/src/document-render.ts +615 -0
  159. package/src/document.ts +2800 -0
  160. package/src/errors.ts +45 -0
  161. package/src/file-blob.ts +43 -0
  162. package/src/index.ts +4 -0
  163. package/src/inspection.ts +50 -0
  164. package/src/kernel.ts +525 -0
  165. package/src/materializer-cli-entry.ts +11 -0
  166. package/src/materializer-cli.ts +743 -0
  167. package/src/native-raster.ts +141 -0
  168. package/src/native.ts +390 -0
  169. package/src/office-import.ts +243 -0
  170. package/src/presentation-pptx-api.ts +107 -0
  171. package/src/presentation-pptx-import.ts +2065 -0
  172. package/src/presentation-pptx-state-digest.ts +46 -0
  173. package/src/presentation-pptx-state.ts +18 -0
  174. package/src/presentation-pptx-xml.ts +473 -0
  175. package/src/presentation-pptx.ts +447 -0
  176. package/src/presentation-render.ts +532 -0
  177. package/src/presentation.ts +2599 -0
  178. package/src/production-composite.ts +805 -0
  179. package/src/production-document.ts +2 -0
  180. package/src/production-native-codecs.ts +1141 -0
  181. package/src/production-presentation.ts +2 -0
  182. package/src/production-sha256.ts +150 -0
  183. package/src/production-spreadsheet-native.ts +686 -0
  184. package/src/production-spreadsheet.ts +2 -0
  185. package/src/production.ts +1022 -0
  186. package/src/raster-image.ts +586 -0
  187. package/src/reference.ts +17 -0
  188. package/src/runtime-cli-entry.ts +13 -0
  189. package/src/runtime-cli.ts +670 -0
  190. package/src/runtime-development.ts +689 -0
  191. package/src/runtime-receipt.ts +169 -0
  192. package/src/runtime.ts +1463 -0
  193. package/src/snapshot.ts +88 -0
  194. package/src/spreadsheet-address.ts +90 -0
  195. package/src/spreadsheet-file.ts +71 -0
  196. package/src/spreadsheet-formula.ts +1212 -0
  197. package/src/spreadsheet-image.ts +242 -0
  198. package/src/spreadsheet-render.ts +985 -0
  199. package/src/spreadsheet-types.ts +171 -0
  200. package/src/spreadsheet-xlsx-api.ts +123 -0
  201. package/src/spreadsheet-xlsx-codec.ts +3063 -0
  202. package/src/spreadsheet-xlsx-state.ts +191 -0
  203. package/src/spreadsheet.ts +5195 -0
@@ -0,0 +1,2365 @@
1
+ import {
2
+ presentationModelDigest,
3
+ sha256Hex
4
+ } from "./chunk-5XIA6KHZ.js";
5
+ import {
6
+ inflateBoundedZipEntry,
7
+ parseBoundedZip,
8
+ verifyBoundedZipEntry
9
+ } from "./chunk-72SYDU4O.js";
10
+ import {
11
+ PPTX_MEDIA_TYPE,
12
+ Presentation,
13
+ PresentationFidelityError,
14
+ PresentationFile,
15
+ PresentationSecurityError,
16
+ colorValue,
17
+ presentationLossState,
18
+ setPresentationLossState
19
+ } from "./chunk-PI2AZVGK.js";
20
+ import "./chunk-FGZM4FQ6.js";
21
+ import {
22
+ FileBlob
23
+ } from "./chunk-4BSENMAE.js";
24
+ import "./chunk-Q2FFHEY7.js";
25
+ import "./chunk-VUNV25KB.js";
26
+
27
+ // src/presentation-pptx-xml.ts
28
+ function parsePptxXmlPart(bytes, partName, budget) {
29
+ if (bytes.byteLength > budget.limits.xmlBytes) {
30
+ throw pptxXmlLimit("XML part exceeds its byte limit", partName);
31
+ }
32
+ budget.totalBytes += bytes.byteLength;
33
+ if (budget.totalBytes > budget.limits.totalXmlBytes) {
34
+ throw pptxXmlLimit("PPTX exceeds its total XML byte limit", partName);
35
+ }
36
+ let xml;
37
+ try {
38
+ if (bytes[0] === 255 && bytes[1] === 254) {
39
+ xml = new TextDecoder("utf-16le", { fatal: true }).decode(bytes);
40
+ } else if (bytes[0] === 254 && bytes[1] === 255) {
41
+ xml = new TextDecoder("utf-16be", { fatal: true }).decode(bytes);
42
+ } else {
43
+ xml = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
44
+ }
45
+ } catch {
46
+ throw pptxXmlSecurity("OOXML text is not valid UTF-8 or UTF-16", "unsafe-xml", partName);
47
+ }
48
+ if (/<!\s*(?:DOCTYPE|ENTITY)\b/i.test(xml)) {
49
+ throw pptxXmlSecurity("DOCTYPE and entity declarations are forbidden", "unsafe-xml", partName);
50
+ }
51
+ const parsed = parseXml(xml, partName, budget);
52
+ validateNamespaceBindings(parsed, partName, /* @__PURE__ */ new Map());
53
+ return parsed;
54
+ }
55
+ function parseXml(xml, partName, budget) {
56
+ const stack = [];
57
+ let root;
58
+ let cursor = 0;
59
+ let sawDeclaration = false;
60
+ const addNode = (node) => {
61
+ budget.totalNodes += 1;
62
+ if (budget.totalNodes > budget.limits.xmlNodes) {
63
+ throw pptxXmlLimit("PPTX exceeds its XML node limit", partName);
64
+ }
65
+ const parent = stack.at(-1);
66
+ if (parent) parent.children.push(node);
67
+ else if (node.type === "text" && isXmlWhitespace(node.value)) return;
68
+ else if (node.type === "element" && !root) root = node;
69
+ else
70
+ throw pptxXmlSecurity(
71
+ "XML has text outside its root or multiple roots",
72
+ "unsafe-xml",
73
+ partName
74
+ );
75
+ };
76
+ while (cursor < xml.length) {
77
+ const opening = xml.indexOf("<", cursor);
78
+ if (opening < 0) {
79
+ if (cursor < xml.length)
80
+ addNode({ type: "text", value: decodeEntities(xml.slice(cursor), partName) });
81
+ cursor = xml.length;
82
+ break;
83
+ }
84
+ if (opening > cursor) {
85
+ addNode({ type: "text", value: decodeEntities(xml.slice(cursor, opening), partName) });
86
+ }
87
+ if (xml.startsWith("<!--", opening)) {
88
+ const end2 = xml.indexOf("-->", opening + 4);
89
+ const body = end2 < 0 ? "" : xml.slice(opening + 4, end2);
90
+ if (end2 < 0 || body.includes("--") || body.endsWith("-")) {
91
+ throw pptxXmlSecurity("Malformed XML comment", "unsafe-xml", partName);
92
+ }
93
+ validateXmlCharacters(body, partName);
94
+ cursor = end2 + 3;
95
+ continue;
96
+ }
97
+ if (xml.startsWith("<?", opening)) {
98
+ const end2 = xml.indexOf("?>", opening + 2);
99
+ const instruction = end2 < 0 ? "" : xml.slice(opening + 2, end2);
100
+ const declaration = /^xml[\x20\t\r\n]+version=(?:"1\.[01]"|'1\.[01]')(?:[\x20\t\r\n]+encoding=(?:"[Uu][Tt][Ff]-(?:8|16)"|'[Uu][Tt][Ff]-(?:8|16)'))?(?:[\x20\t\r\n]+standalone=(?:"(?:yes|no)"|'(?:yes|no)'))?[\x20\t\r\n]*$/.test(
101
+ instruction
102
+ );
103
+ if (end2 < 0 || !declaration || opening !== 0 || root || stack.length > 0 || sawDeclaration) {
104
+ throw pptxXmlSecurity(
105
+ "XML processing instructions are unsupported",
106
+ "unsafe-xml",
107
+ partName
108
+ );
109
+ }
110
+ sawDeclaration = true;
111
+ cursor = end2 + 2;
112
+ continue;
113
+ }
114
+ if (xml.startsWith("<!", opening)) {
115
+ throw pptxXmlSecurity("XML declarations and CDATA are unsupported", "unsafe-xml", partName);
116
+ }
117
+ const end = findTagEnd(xml, opening + 1, partName);
118
+ const source = xml.slice(opening + 1, end);
119
+ if (source.startsWith("/")) {
120
+ const match = /^\/([^\x20\t\r\n]+)[\x20\t\r\n]*$/.exec(source);
121
+ const name = match?.[1];
122
+ if (!name || !XML_QNAME.test(name)) {
123
+ throw pptxXmlSecurity("Malformed XML closing tag", "unsafe-xml", partName);
124
+ }
125
+ const current = stack.pop();
126
+ if (!current || current.name !== name) {
127
+ throw pptxXmlSecurity("Mismatched XML closing tag", "unsafe-xml", partName);
128
+ }
129
+ } else {
130
+ if (isXmlSpace(source[0])) {
131
+ throw pptxXmlSecurity("XML start tag has leading whitespace", "unsafe-xml", partName);
132
+ }
133
+ const selfClosing = /\/[\x20\t\r\n]*$/.test(source);
134
+ const body = selfClosing ? source.replace(/\/[\x20\t\r\n]*$/, "") : source;
135
+ const element = parseStartTag(body, partName, budget.limits.xmlAttributesPerElement);
136
+ addNode(element);
137
+ if (!selfClosing) {
138
+ stack.push(element);
139
+ if (stack.length > budget.limits.xmlDepth) {
140
+ throw pptxXmlLimit("XML exceeds its depth limit", partName);
141
+ }
142
+ }
143
+ }
144
+ cursor = end + 1;
145
+ }
146
+ if (stack.length > 0 || !root) {
147
+ throw pptxXmlSecurity("XML is empty or ends with unclosed elements", "unsafe-xml", partName);
148
+ }
149
+ return root;
150
+ }
151
+ function findTagEnd(xml, start, partName) {
152
+ let quote;
153
+ for (let index = start; index < xml.length; index += 1) {
154
+ const character = xml[index];
155
+ if (quote) {
156
+ if (character === quote) quote = void 0;
157
+ } else if (character === '"' || character === "'") quote = character;
158
+ else if (character === ">") return index;
159
+ }
160
+ throw pptxXmlSecurity("Unterminated XML tag", "unsafe-xml", partName);
161
+ }
162
+ function parseStartTag(source, partName, maxAttributes) {
163
+ let cursor = 0;
164
+ const skipSpace = () => {
165
+ while (cursor < source.length && isXmlSpace(source[cursor])) cursor += 1;
166
+ };
167
+ const nameStart = cursor;
168
+ while (cursor < source.length && !isXmlSpace(source[cursor])) cursor += 1;
169
+ const name = source.slice(nameStart, cursor);
170
+ if (!XML_QNAME.test(name)) {
171
+ throw pptxXmlSecurity("Invalid XML element name", "unsafe-xml", partName);
172
+ }
173
+ const attributes = /* @__PURE__ */ new Map();
174
+ while (cursor < source.length) {
175
+ if (!isXmlSpace(source[cursor])) {
176
+ throw pptxXmlSecurity(
177
+ "XML attributes must be separated by whitespace",
178
+ "unsafe-xml",
179
+ partName
180
+ );
181
+ }
182
+ skipSpace();
183
+ if (cursor >= source.length) break;
184
+ const attributeStart = cursor;
185
+ while (cursor < source.length && !isXmlSpace(source[cursor]) && source[cursor] !== "=")
186
+ cursor += 1;
187
+ const attributeName = source.slice(attributeStart, cursor);
188
+ if (!XML_QNAME.test(attributeName)) {
189
+ throw pptxXmlSecurity("Invalid XML attribute name", "unsafe-xml", partName);
190
+ }
191
+ skipSpace();
192
+ if (source[cursor] !== "=") {
193
+ throw pptxXmlSecurity("XML attribute lacks a value", "unsafe-xml", partName);
194
+ }
195
+ cursor += 1;
196
+ skipSpace();
197
+ const quote = source[cursor];
198
+ if (quote !== '"' && quote !== "'") {
199
+ throw pptxXmlSecurity("XML attribute value must be quoted", "unsafe-xml", partName);
200
+ }
201
+ cursor += 1;
202
+ const valueStart = cursor;
203
+ const valueEnd = source.indexOf(quote, valueStart);
204
+ if (valueEnd < 0) {
205
+ throw pptxXmlSecurity("Unterminated XML attribute", "unsafe-xml", partName);
206
+ }
207
+ if (attributes.has(attributeName)) {
208
+ throw pptxXmlSecurity("Duplicate XML attribute", "unsafe-xml", partName);
209
+ }
210
+ if (attributes.size >= maxAttributes) {
211
+ throw pptxXmlLimit("XML element exceeds its attribute limit", partName);
212
+ }
213
+ const rawValue = source.slice(valueStart, valueEnd);
214
+ if (rawValue.includes("<")) {
215
+ throw pptxXmlSecurity(
216
+ "XML attribute contains an unescaped less-than sign",
217
+ "unsafe-xml",
218
+ partName
219
+ );
220
+ }
221
+ attributes.set(attributeName, decodeEntities(rawValue, partName));
222
+ cursor = valueEnd + 1;
223
+ }
224
+ return { type: "element", name, localName: xmlLocalName(name), attributes, children: [] };
225
+ }
226
+ function validateNamespaceBindings(element, partName, inherited) {
227
+ const bindings = new Map(inherited);
228
+ for (const [name, value] of element.attributes) {
229
+ if (name === "xmlns") bindings.set("", value);
230
+ else if (name.startsWith("xmlns:")) {
231
+ const declaredPrefix = name.slice(6);
232
+ const required = REQUIRED_NAMESPACE_URIS.get(declaredPrefix);
233
+ if (required && required !== value) {
234
+ throw pptxXmlSecurity(
235
+ "OOXML namespace prefix is rebound to an unexpected URI",
236
+ "unsafe-xml",
237
+ partName
238
+ );
239
+ }
240
+ bindings.set(declaredPrefix, value);
241
+ }
242
+ }
243
+ const prefix = xmlPrefix(element.name);
244
+ if (prefix && !bindings.has(prefix)) {
245
+ throw pptxXmlSecurity(
246
+ "XML element uses an undeclared namespace prefix",
247
+ "unsafe-xml",
248
+ partName
249
+ );
250
+ }
251
+ const namespaceUri = bindings.get(prefix ?? "");
252
+ if (namespaceUri === void 0) delete element.namespaceUri;
253
+ else element.namespaceUri = namespaceUri;
254
+ const attributeNamespaces = /* @__PURE__ */ new Map();
255
+ for (const name of element.attributes.keys()) {
256
+ if (name === "xmlns" || name.startsWith("xmlns:")) continue;
257
+ const attributePrefix = xmlPrefix(name);
258
+ if (attributePrefix && attributePrefix !== "xml" && !bindings.has(attributePrefix)) {
259
+ throw pptxXmlSecurity(
260
+ "XML attribute uses an undeclared namespace prefix",
261
+ "unsafe-xml",
262
+ partName
263
+ );
264
+ }
265
+ attributeNamespaces.set(
266
+ name,
267
+ attributePrefix === "xml" ? "http://www.w3.org/XML/1998/namespace" : attributePrefix ? bindings.get(attributePrefix) : void 0
268
+ );
269
+ }
270
+ element.attributeNamespaces = attributeNamespaces;
271
+ for (const child of pptxXmlChildren(element))
272
+ validateNamespaceBindings(child, partName, bindings);
273
+ }
274
+ function decodeEntities(value, partName) {
275
+ validateXmlCharacters(value, partName);
276
+ if (value.includes("]]>")) {
277
+ throw pptxXmlSecurity("XML contains malformed character data", "unsafe-xml", partName);
278
+ }
279
+ if (!value.includes("&")) return value;
280
+ const output = [];
281
+ let cursor = 0;
282
+ let ampersand = value.indexOf("&");
283
+ let references = 0;
284
+ while (ampersand >= 0) {
285
+ references += 1;
286
+ if (references > 65536) {
287
+ throw pptxXmlLimit("XML text exceeds its entity reference limit", partName);
288
+ }
289
+ output.push(value.slice(cursor, ampersand));
290
+ const semicolon = value.indexOf(";", ampersand + 1);
291
+ const nested = value.indexOf("&", ampersand + 1);
292
+ if (semicolon < 0 || nested >= 0 && nested < semicolon) {
293
+ throw pptxXmlSecurity(
294
+ "XML contains an unterminated entity reference",
295
+ "unsafe-xml",
296
+ partName
297
+ );
298
+ }
299
+ const entity = value.slice(ampersand + 1, semicolon);
300
+ if (entity.length === 0 || entity.length > 16) {
301
+ throw pptxXmlSecurity("XML contains a malformed entity reference", "unsafe-xml", partName);
302
+ }
303
+ output.push(decodeEntity(entity, partName));
304
+ cursor = semicolon + 1;
305
+ ampersand = value.indexOf("&", cursor);
306
+ }
307
+ output.push(value.slice(cursor));
308
+ return output.join("");
309
+ }
310
+ function decodeEntity(entity, partName) {
311
+ if (entity === "amp") return "&";
312
+ if (entity === "lt") return "<";
313
+ if (entity === "gt") return ">";
314
+ if (entity === "quot") return '"';
315
+ if (entity === "apos") return "'";
316
+ const decimal = /^#([0-9]+)$/.exec(entity);
317
+ const hexadecimal = /^#x([0-9a-f]+)$/i.exec(entity);
318
+ const codePoint = decimal ? Number(decimal[1]) : hexadecimal ? Number.parseInt(hexadecimal[1] ?? "", 16) : NaN;
319
+ if (!Number.isInteger(codePoint) || !isValidXmlCodePoint(codePoint)) {
320
+ throw pptxXmlSecurity(`Unsupported or invalid XML entity: &${entity};`, "unsafe-xml", partName);
321
+ }
322
+ return String.fromCodePoint(codePoint);
323
+ }
324
+ function validateXmlCharacters(value, partName) {
325
+ for (const character of value) {
326
+ if (!isValidXmlCodePoint(character.codePointAt(0) ?? 0)) {
327
+ throw pptxXmlSecurity("XML contains a forbidden character", "unsafe-xml", partName);
328
+ }
329
+ }
330
+ }
331
+ function isValidXmlCodePoint(codePoint) {
332
+ return codePoint === 9 || codePoint === 10 || codePoint === 13 || codePoint >= 32 && codePoint <= 55295 || codePoint >= 57344 && codePoint <= 65533 || codePoint >= 65536 && codePoint <= 1114111;
333
+ }
334
+ function pptxXmlChildren(element) {
335
+ return element?.children.filter((child) => child.type === "element") ?? [];
336
+ }
337
+ function pptxXmlChild(element, localName) {
338
+ return pptxXmlChildren(element).find((child) => child.localName === localName);
339
+ }
340
+ function pptxXmlDescendants(element, localName) {
341
+ const output = [];
342
+ const visit = (node) => {
343
+ for (const child of pptxXmlChildren(node)) {
344
+ if (child.localName === localName) output.push(child);
345
+ visit(child);
346
+ }
347
+ };
348
+ if (element) visit(element);
349
+ return output;
350
+ }
351
+ function pptxXmlAttribute(element, localName) {
352
+ if (!element) return void 0;
353
+ for (const [name, value] of element.attributes) {
354
+ if (xmlLocalName(name) === localName && name !== "xmlns" && !name.startsWith("xmlns:"))
355
+ return value;
356
+ }
357
+ return void 0;
358
+ }
359
+ function pptxXmlQualifiedAttribute(element, qualifiedName) {
360
+ return element?.attributes.get(qualifiedName);
361
+ }
362
+ function pptxXmlText(element) {
363
+ if (!element) return "";
364
+ let output = "";
365
+ const visit = (node) => {
366
+ if (node.type === "text") output += node.value;
367
+ else for (const child of node.children) visit(child);
368
+ };
369
+ visit(element);
370
+ return output;
371
+ }
372
+ function pptxXmlDirectText(element) {
373
+ return element?.children.filter((child) => child.type === "text").map((child) => child.value).join("") ?? "";
374
+ }
375
+ function pptxXmlSecurity(message, code, entryName) {
376
+ return new PresentationSecurityError(
377
+ entryName ? `${message}: ${entryName}` : message,
378
+ code,
379
+ entryName
380
+ );
381
+ }
382
+ function pptxXmlLimit(message, entryName) {
383
+ return pptxXmlSecurity(message, "limit-exceeded", entryName);
384
+ }
385
+ function xmlLocalName(value) {
386
+ return value.slice(value.indexOf(":") + 1);
387
+ }
388
+ function xmlPrefix(value) {
389
+ const index = value.indexOf(":");
390
+ return index < 0 ? void 0 : value.slice(0, index);
391
+ }
392
+ function isXmlSpace(value) {
393
+ return value === " " || value === " " || value === "\r" || value === "\n";
394
+ }
395
+ function isXmlWhitespace(value) {
396
+ for (const character of value) if (!isXmlSpace(character)) return false;
397
+ return true;
398
+ }
399
+ var XML_QNAME = /^(?:[A-Za-z_][A-Za-z0-9_.-]*:)?[A-Za-z_][A-Za-z0-9_.-]*$/;
400
+ var REQUIRED_NAMESPACE_URIS = /* @__PURE__ */ new Map([
401
+ ["a", "http://schemas.openxmlformats.org/drawingml/2006/main"],
402
+ ["c", "http://schemas.openxmlformats.org/drawingml/2006/chart"],
403
+ ["p", "http://schemas.openxmlformats.org/presentationml/2006/main"],
404
+ ["r", "http://schemas.openxmlformats.org/officeDocument/2006/relationships"]
405
+ ]);
406
+
407
+ // src/presentation-pptx-import.ts
408
+ var OFFICE_RELATIONSHIP_BASE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/";
409
+ var PACKAGE_RELATIONSHIP_BASE = "http://schemas.openxmlformats.org/package/2006/relationships/";
410
+ var PRESENTATION_MAIN_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml";
411
+ var PRESENTATION_NAMESPACE = "http://schemas.openxmlformats.org/presentationml/2006/main";
412
+ var DRAWING_NAMESPACE = "http://schemas.openxmlformats.org/drawingml/2006/main";
413
+ var CHART_NAMESPACE = "http://schemas.openxmlformats.org/drawingml/2006/chart";
414
+ var CONTENT_TYPES_NAMESPACE = "http://schemas.openxmlformats.org/package/2006/content-types";
415
+ var RELATIONSHIPS_NAMESPACE = "http://schemas.openxmlformats.org/package/2006/relationships";
416
+ var TABLE_GRAPHIC_URI = "http://schemas.openxmlformats.org/drawingml/2006/table";
417
+ var CHART_GRAPHIC_URI = "http://schemas.openxmlformats.org/drawingml/2006/chart";
418
+ var EMU_PER_PIXEL = 9525;
419
+ var DEFAULT_PPTX_IMPORT_LIMITS = Object.freeze({
420
+ compressedBytes: 128 * 1024 * 1024,
421
+ expandedBytes: 256 * 1024 * 1024,
422
+ entryBytes: 64 * 1024 * 1024,
423
+ entries: 3e4,
424
+ compressionRatio: 100,
425
+ totalXmlBytes: 32 * 1024 * 1024,
426
+ xmlBytes: 8 * 1024 * 1024,
427
+ xmlDepth: 192,
428
+ xmlNodes: 2e6,
429
+ xmlAttributesPerElement: 512,
430
+ relationshipsPerPart: 8192,
431
+ relationships: 5e4,
432
+ slides: 1e4,
433
+ elements: 1e6,
434
+ textCharacters: 16e6,
435
+ imageBytes: 128 * 1024 * 1024,
436
+ chartPoints: 5e6,
437
+ nestedPackageBytes: 32 * 1024 * 1024,
438
+ nestedExpandedBytes: 64 * 1024 * 1024,
439
+ nestedEntries: 2e4,
440
+ retainedBytes: 256 * 1024 * 1024
441
+ });
442
+ async function importPresentationPptx(input, options = {}) {
443
+ const limits = resolveLimits(options.limits);
444
+ const bytes = await toBytes(input);
445
+ if (bytes.byteLength === 0 || bytes.byteLength > limits.compressedBytes) {
446
+ throw pptxSecurity(
447
+ bytes.byteLength === 0 ? "PPTX input is empty" : "PPTX exceeds its compressed byte limit",
448
+ bytes.byteLength === 0 ? "invalid-package" : "limit-exceeded"
449
+ );
450
+ }
451
+ const directory = parseBoundedZip(
452
+ bytes,
453
+ {
454
+ entries: limits.entries,
455
+ compressedEntryBytes: limits.compressedBytes,
456
+ expandedEntryBytes: limits.entryBytes,
457
+ expandedBytes: limits.expandedBytes,
458
+ compressionRatio: limits.compressionRatio,
459
+ compressionRatioThreshold: 1048576
460
+ },
461
+ pptxZipFailure
462
+ );
463
+ const entries = new Map(directory.map((entry) => [entry.name, entry]));
464
+ const context = {
465
+ bytes,
466
+ limits,
467
+ entries,
468
+ xmlBudget: { limits, totalBytes: 0, totalNodes: 0 },
469
+ xmlCache: /* @__PURE__ */ new Map(),
470
+ bytesCache: /* @__PURE__ */ new Map(),
471
+ relationshipsCache: /* @__PURE__ */ new Map(),
472
+ issues: [],
473
+ unsupportedParts: /* @__PURE__ */ new Set(),
474
+ elements: 0,
475
+ textCharacters: 0,
476
+ imageBytes: 0,
477
+ chartPoints: 0,
478
+ relationships: 0,
479
+ nestedExpandedBytes: 0,
480
+ nestedEntries: 0,
481
+ retainedBytes: bytes.byteLength,
482
+ themeColors: DEFAULT_THEME_COLORS
483
+ };
484
+ await preflightPackage(context);
485
+ const rootRelationships = relationshipsFor(context, "");
486
+ const officeDocuments = rootRelationships.filter(
487
+ (relationship) => relationship.type === `${OFFICE_RELATIONSHIP_BASE}officeDocument`
488
+ );
489
+ if (officeDocuments.length !== 1 || officeDocuments[0]?.partName !== "ppt/presentation.xml") {
490
+ throw pptxSecurity(
491
+ "PPTX must contain exactly one canonical ppt/presentation.xml office document",
492
+ "invalid-package",
493
+ "_rels/.rels"
494
+ );
495
+ }
496
+ const presentationRoot = readXml(context, "ppt/presentation.xml");
497
+ requireRoot(presentationRoot, "presentation", PRESENTATION_NAMESPACE, "ppt/presentation.xml");
498
+ const presentationRelationships = relationshipsFor(context, "ppt/presentation.xml");
499
+ const presentationRelationshipById = byRelationshipId(presentationRelationships);
500
+ const slideSize = parseSlideSize(presentationRoot);
501
+ const themeRelationship = presentationRelationships.find(
502
+ (relationship) => relationship.type === `${OFFICE_RELATIONSHIP_BASE}theme`
503
+ );
504
+ if (themeRelationship) {
505
+ context.themeColors = parseThemeColors(
506
+ readXml(context, themeRelationship.partName),
507
+ themeRelationship.partName
508
+ );
509
+ }
510
+ const presentation = Presentation.create({ slideSize });
511
+ const masterByPart = /* @__PURE__ */ new Map();
512
+ const layoutByPart = /* @__PURE__ */ new Map();
513
+ importMastersAndLayouts(
514
+ context,
515
+ presentation,
516
+ presentationRoot,
517
+ presentationRelationshipById,
518
+ masterByPart,
519
+ layoutByPart
520
+ );
521
+ importSlides(context, presentation, presentationRoot, presentationRelationshipById, layoutByPart);
522
+ const deduplicatedIssues = deduplicateIssues(context.issues);
523
+ if (options.unsupportedContent === "error" && deduplicatedIssues.length > 0) {
524
+ throw new PresentationFidelityError(
525
+ "PPTX contains inert content that the editable presentation model cannot regenerate exactly",
526
+ deduplicatedIssues
527
+ );
528
+ }
529
+ const { presentationModelDigest: presentationModelDigest2, sha256Hex: sha256Hex2 } = await import("./presentation-pptx-state-digest-765CZZED.js");
530
+ setPresentationLossState(presentation, {
531
+ version: 1,
532
+ mediaType: PPTX_MEDIA_TYPE,
533
+ sourceBytes: bytes,
534
+ sourceDigest: await sha256Hex2(bytes),
535
+ unsupportedParts: [...context.unsupportedParts].sort(),
536
+ modelDigest: await presentationModelDigest2(presentation)
537
+ });
538
+ return presentation;
539
+ }
540
+ function resolveLimits(requested) {
541
+ const limits = { ...DEFAULT_PPTX_IMPORT_LIMITS, ...requested };
542
+ for (const [name, value] of Object.entries(limits)) {
543
+ if (!Number.isSafeInteger(value) || value <= 0) {
544
+ throw pptxSecurity(`Invalid PPTX import limit: ${name}`, "limit-exceeded");
545
+ }
546
+ const maximum = DEFAULT_PPTX_IMPORT_LIMITS[name];
547
+ if (value > maximum) {
548
+ throw pptxSecurity(
549
+ `PPTX import limit ${name} may only tighten the production maximum`,
550
+ "limit-exceeded"
551
+ );
552
+ }
553
+ }
554
+ return limits;
555
+ }
556
+ async function toBytes(input) {
557
+ if (input instanceof Uint8Array) return input.slice();
558
+ if (input instanceof ArrayBuffer) return new Uint8Array(input.slice(0));
559
+ if (input instanceof Blob) return new Uint8Array(await input.arrayBuffer());
560
+ throw pptxSecurity("PPTX input must be a Blob, ArrayBuffer, or Uint8Array", "invalid-package");
561
+ }
562
+ async function preflightPackage(context) {
563
+ if (!context.entries.has("[Content_Types].xml") || !context.entries.has("_rels/.rels")) {
564
+ throw pptxSecurity("PPTX is missing required OPC package parts", "invalid-package");
565
+ }
566
+ for (const entry of context.entries.values()) rejectActiveEntry(entry.name, entry.directory);
567
+ for (const entry of context.entries.values()) {
568
+ if (entry.directory) continue;
569
+ if (isXmlPart(entry.name)) {
570
+ const xmlBytes = await inflateBoundedZipEntry(
571
+ context.bytes,
572
+ entry,
573
+ context.limits.entryBytes,
574
+ pptxZipFailure
575
+ );
576
+ reserveRetained(context, xmlBytes.byteLength * 3, entry.name);
577
+ context.bytesCache.set(entry.name, xmlBytes);
578
+ readXml(context, entry.name);
579
+ } else if (isRasterMediaPart(entry.name)) {
580
+ const imageBytes = await inflateBoundedZipEntry(
581
+ context.bytes,
582
+ entry,
583
+ context.limits.entryBytes,
584
+ pptxZipFailure
585
+ );
586
+ reserveRetained(context, imageBytes.byteLength, entry.name);
587
+ context.bytesCache.set(entry.name, imageBytes);
588
+ } else if (/^ppt\/embeddings\/.*\.xlsx$/i.test(entry.name)) {
589
+ const nestedBytes = await inflateBoundedZipEntry(
590
+ context.bytes,
591
+ entry,
592
+ context.limits.nestedPackageBytes,
593
+ pptxZipFailure
594
+ );
595
+ await preflightEmbeddedXlsx(context, entry, nestedBytes);
596
+ } else {
597
+ await verifyBoundedZipEntry(context.bytes, entry, context.limits.entryBytes, pptxZipFailure);
598
+ }
599
+ }
600
+ validateContentTypes(context);
601
+ for (const entry of context.entries.values()) {
602
+ if (!entry.name.endsWith(".rels")) continue;
603
+ relationshipsForPart(context, entry.name);
604
+ }
605
+ validateRelationshipTargets(context);
606
+ classifySourceOnlyParts(context);
607
+ }
608
+ function isXmlPart(name) {
609
+ return name === "[Content_Types].xml" || /(?:\.xml|\.rels)$/i.test(name);
610
+ }
611
+ function readXml(context, partName) {
612
+ const cached = context.xmlCache.get(partName);
613
+ if (cached) return cached;
614
+ const bytes = readPartBytes(context, partName);
615
+ const parsed = parsePptxXmlPart(bytes, partName, context.xmlBudget);
616
+ context.xmlCache.set(partName, parsed);
617
+ context.bytesCache.delete(partName);
618
+ return parsed;
619
+ }
620
+ function readPartBytes(context, partName) {
621
+ const cached = context.bytesCache.get(partName);
622
+ if (cached) return cached;
623
+ const entry = context.entries.get(partName);
624
+ if (!entry || entry.directory) {
625
+ throw pptxSecurity("Required PPTX part is missing", "invalid-package", partName);
626
+ }
627
+ throw pptxSecurity(
628
+ "PPTX part was not inflated during bounded preflight",
629
+ "invalid-package",
630
+ partName
631
+ );
632
+ }
633
+ function validateContentTypes(context) {
634
+ const root = readXml(context, "[Content_Types].xml");
635
+ requireRoot(root, "Types", CONTENT_TYPES_NAMESPACE, "[Content_Types].xml");
636
+ const defaults = /* @__PURE__ */ new Map();
637
+ const overrides = /* @__PURE__ */ new Map();
638
+ for (const child of pptxXmlChildren(root)) {
639
+ if (child.localName === "Default") {
640
+ const extension = requiredAttribute(child, "Extension", "[Content_Types].xml").toLowerCase();
641
+ const contentType = requiredAttribute(child, "ContentType", "[Content_Types].xml");
642
+ if (defaults.has(extension)) {
643
+ throw pptxSecurity(
644
+ "Duplicate OPC content-type extension",
645
+ "invalid-package",
646
+ "[Content_Types].xml"
647
+ );
648
+ }
649
+ defaults.set(extension, contentType);
650
+ if (!KNOWN_DEFAULT_CONTENT_TYPES.has(`${extension}\0${contentType}`)) {
651
+ markSourceOnly(context, ["[Content_Types].xml"]);
652
+ }
653
+ } else if (child.localName === "Override") {
654
+ const name = requiredAttribute(child, "PartName", "[Content_Types].xml");
655
+ if (!name.startsWith("/") || name.includes("\\") || name.includes("..")) {
656
+ throw pptxSecurity(
657
+ "Unsafe OPC content-type part name",
658
+ "invalid-package",
659
+ "[Content_Types].xml"
660
+ );
661
+ }
662
+ const normalized = name.slice(1);
663
+ const contentType = requiredAttribute(child, "ContentType", "[Content_Types].xml");
664
+ if (overrides.has(normalized.toLowerCase())) {
665
+ throw pptxSecurity("Duplicate OPC content-type override", "invalid-package", normalized);
666
+ }
667
+ overrides.set(normalized.toLowerCase(), contentType);
668
+ if (!isKnownPptxPart(normalized)) {
669
+ markSourceOnly(context, ["[Content_Types].xml", normalized]);
670
+ }
671
+ } else {
672
+ throw pptxSecurity(
673
+ "Unknown OPC content-type declaration",
674
+ "invalid-package",
675
+ "[Content_Types].xml"
676
+ );
677
+ }
678
+ }
679
+ const presentationType = overrides.get("ppt/presentation.xml");
680
+ if (presentationType !== PRESENTATION_MAIN_TYPE) {
681
+ throw pptxSecurity(
682
+ presentationType?.includes("macroEnabled") ? "Macro-enabled presentations are forbidden" : "ppt/presentation.xml has the wrong content type",
683
+ presentationType?.includes("macroEnabled") ? "active-content" : "invalid-package",
684
+ "[Content_Types].xml"
685
+ );
686
+ }
687
+ for (const entry of context.entries.values()) {
688
+ if (entry.directory || entry.name === "[Content_Types].xml") continue;
689
+ const extension = entry.name.includes(".") ? entry.name.slice(entry.name.lastIndexOf(".") + 1).toLowerCase() : "";
690
+ const contentType = overrides.get(entry.name.toLowerCase()) ?? defaults.get(extension);
691
+ if (!contentType) {
692
+ throw pptxSecurity("OPC package part lacks a content type", "invalid-package", entry.name);
693
+ }
694
+ if (isActiveContentType(contentType)) {
695
+ throw pptxSecurity("Active OPC content type is forbidden", "active-content", entry.name);
696
+ }
697
+ }
698
+ }
699
+ function relationshipsFor(context, sourcePart) {
700
+ return relationshipsForPart(context, relationshipPartName(sourcePart));
701
+ }
702
+ function relationshipsForPart(context, relationshipsPart) {
703
+ const cached = context.relationshipsCache.get(relationshipsPart);
704
+ if (cached) return cached;
705
+ if (!context.entries.has(relationshipsPart)) {
706
+ if (relationshipsPart === "_rels/.rels") {
707
+ throw pptxSecurity(
708
+ "OPC root relationships are missing",
709
+ "invalid-package",
710
+ relationshipsPart
711
+ );
712
+ }
713
+ context.relationshipsCache.set(relationshipsPart, []);
714
+ return [];
715
+ }
716
+ const root = readXml(context, relationshipsPart);
717
+ requireRoot(root, "Relationships", RELATIONSHIPS_NAMESPACE, relationshipsPart);
718
+ const sourcePart = relationshipSourcePart(relationshipsPart);
719
+ const relationships = [];
720
+ const ids = /* @__PURE__ */ new Set();
721
+ for (const child of pptxXmlChildren(root)) {
722
+ if (child.localName !== "Relationship" || pptxXmlChildren(child).length > 0) {
723
+ throw pptxSecurity("Invalid OPC relationship element", "invalid-package", relationshipsPart);
724
+ }
725
+ if (relationships.length >= context.limits.relationshipsPerPart) {
726
+ throw pptxSecurity(
727
+ "Relationship part exceeds its relationship limit",
728
+ "limit-exceeded",
729
+ relationshipsPart
730
+ );
731
+ }
732
+ const id = requiredAttribute(child, "Id", relationshipsPart);
733
+ const type = requiredAttribute(child, "Type", relationshipsPart);
734
+ const target = requiredAttribute(child, "Target", relationshipsPart);
735
+ if (id.length > 512 || type.length > 2048 || target.length > 4096 || ids.has(id)) {
736
+ throw pptxSecurity(
737
+ "Duplicate or oversized OPC relationship",
738
+ "invalid-package",
739
+ relationshipsPart
740
+ );
741
+ }
742
+ ids.add(id);
743
+ const targetMode = pptxXmlAttribute(child, "TargetMode") ?? "Internal";
744
+ if (targetMode !== "Internal") {
745
+ throw pptxSecurity(
746
+ "External OPC relationships are forbidden",
747
+ "external-relationship",
748
+ relationshipsPart
749
+ );
750
+ }
751
+ if (/^(?:https?|ftp|file|data|javascript):/i.test(target)) {
752
+ throw pptxSecurity(
753
+ "External OPC relationship target is forbidden",
754
+ "external-relationship",
755
+ relationshipsPart
756
+ );
757
+ }
758
+ const partName = resolvePartTarget(sourcePart, target, relationshipsPart);
759
+ const relationship = { id, type, target, sourcePart, partName };
760
+ validateRelationshipKind(context, relationship, relationshipsPart);
761
+ relationships.push(relationship);
762
+ }
763
+ context.relationships += relationships.length;
764
+ if (context.relationships > context.limits.relationships) {
765
+ throw pptxSecurity(
766
+ "PPTX exceeds its total relationship limit",
767
+ "limit-exceeded",
768
+ relationshipsPart
769
+ );
770
+ }
771
+ context.relationshipsCache.set(relationshipsPart, relationships);
772
+ return relationships;
773
+ }
774
+ function validateRelationshipKind(context, relationship, relationshipsPart) {
775
+ const suffix = relationship.type.slice(relationship.type.lastIndexOf("/") + 1);
776
+ const standardOfficeRelationship = relationship.type === `${OFFICE_RELATIONSHIP_BASE}${suffix}`;
777
+ const standardPackageRelationship = relationship.type === `${PACKAGE_RELATIONSHIP_BASE}${suffix}`;
778
+ if (!standardOfficeRelationship && !standardPackageRelationship && !ALLOWED_EXACT_RELATIONSHIPS.has(relationship.type)) {
779
+ throw pptxSecurity(
780
+ `Unsupported or non-standard presentation relationship type: ${relationship.type}`,
781
+ "invalid-package",
782
+ relationshipsPart
783
+ );
784
+ }
785
+ if (ACTIVE_RELATIONSHIP_SUFFIX.test(suffix)) {
786
+ throw pptxSecurity(
787
+ "Active or remote presentation relationship is forbidden",
788
+ "active-content",
789
+ relationshipsPart
790
+ );
791
+ }
792
+ if (!KNOWN_RELATIONSHIP_SUFFIXES.has(suffix)) {
793
+ markSourceOnly(context, [relationshipsPart, relationship.partName]);
794
+ }
795
+ if (suffix === "package") {
796
+ const safeChartWorkbook = /^ppt\/charts\/chart\d+\.xml$/i.test(relationship.sourcePart) && /^ppt\/embeddings\/[^/]+\.xlsx$/i.test(relationship.partName);
797
+ if (!safeChartWorkbook) {
798
+ throw pptxSecurity(
799
+ "Embedded packages are forbidden outside chart data workbooks",
800
+ "active-content",
801
+ relationshipsPart
802
+ );
803
+ }
804
+ }
805
+ if (suffix === "hyperlink") {
806
+ context.unsupportedParts.add(relationshipsPart);
807
+ context.issues.push(fidelityIssue("hyperlink", [relationshipsPart]));
808
+ }
809
+ if (SOURCE_ONLY_RELATIONSHIP_SUFFIXES.has(suffix)) {
810
+ context.unsupportedParts.add(relationshipsPart);
811
+ context.issues.push(fidelityIssue("theme", [relationshipsPart, relationship.partName]));
812
+ }
813
+ }
814
+ function classifySourceOnlyParts(context) {
815
+ for (const entry of context.entries.values()) {
816
+ if (!entry.directory && !isKnownPptxPart(entry.name)) {
817
+ markSourceOnly(context, [entry.name, "[Content_Types].xml"]);
818
+ }
819
+ }
820
+ }
821
+ function markSourceOnly(context, parts) {
822
+ for (const part of parts) context.unsupportedParts.add(part);
823
+ context.issues.push(fidelityIssue("source-only", parts));
824
+ }
825
+ function isKnownPptxPart(name) {
826
+ return KNOWN_PPTX_PART_PATTERNS.some((pattern) => pattern.test(name));
827
+ }
828
+ function isActiveContentType(contentType) {
829
+ return /(?:macroEnabled|vbaProject|activeX|oleObject|javascript|ecmascript|x-msdownload|executable|powershell|shellscript|text\/html)/i.test(
830
+ contentType
831
+ );
832
+ }
833
+ function validateRelationshipTargets(context) {
834
+ for (const relationships of context.relationshipsCache.values()) {
835
+ for (const relationship of relationships) {
836
+ const suffix = relationship.type.slice(relationship.type.lastIndexOf("/") + 1);
837
+ if (suffix === "hyperlink") continue;
838
+ if (!context.entries.has(relationship.partName)) {
839
+ throw pptxSecurity(
840
+ "OPC relationship targets a missing package part",
841
+ "invalid-package",
842
+ relationship.partName
843
+ );
844
+ }
845
+ }
846
+ }
847
+ }
848
+ async function preflightEmbeddedXlsx(context, entry, bytes) {
849
+ if (entry.expandedSize > context.limits.nestedPackageBytes) {
850
+ throw pptxSecurity(
851
+ "Embedded chart workbook exceeds its byte limit",
852
+ "limit-exceeded",
853
+ entry.name
854
+ );
855
+ }
856
+ const nested = parseBoundedZip(
857
+ bytes,
858
+ {
859
+ entries: Math.min(context.limits.entries, 1e4),
860
+ compressedEntryBytes: context.limits.nestedPackageBytes,
861
+ expandedEntryBytes: context.limits.nestedPackageBytes,
862
+ expandedBytes: context.limits.nestedPackageBytes,
863
+ compressionRatio: context.limits.compressionRatio,
864
+ compressionRatioThreshold: 1048576
865
+ },
866
+ pptxZipFailure
867
+ );
868
+ context.nestedEntries += nested.length;
869
+ context.nestedExpandedBytes += nested.reduce((sum, part) => sum + part.expandedSize, 0);
870
+ if (context.nestedEntries > context.limits.nestedEntries || context.nestedExpandedBytes > context.limits.nestedExpandedBytes) {
871
+ throw pptxSecurity(
872
+ "Embedded chart workbooks exceed the global nested archive budget",
873
+ "limit-exceeded",
874
+ entry.name
875
+ );
876
+ }
877
+ for (const nestedEntry of nested) {
878
+ rejectNestedWorkbookEntry(nestedEntry.name, entry.name);
879
+ if (nestedEntry.directory) continue;
880
+ if (isXmlPart(nestedEntry.name)) {
881
+ const xmlBytes = await inflateBoundedZipEntry(
882
+ bytes,
883
+ nestedEntry,
884
+ context.limits.nestedPackageBytes,
885
+ pptxZipFailure
886
+ );
887
+ const root = parsePptxXmlPart(
888
+ xmlBytes,
889
+ `${entry.name}!/${nestedEntry.name}`,
890
+ context.xmlBudget
891
+ );
892
+ if (nestedEntry.name.endsWith(".rels")) {
893
+ for (const relationship of pptxXmlChildren(root)) {
894
+ if (relationship.localName !== "Relationship") {
895
+ throw pptxSecurity(
896
+ "Embedded workbook relationship XML is invalid",
897
+ "invalid-package",
898
+ entry.name
899
+ );
900
+ }
901
+ const targetMode = pptxXmlAttribute(relationship, "TargetMode") ?? "Internal";
902
+ const target = requiredAttribute(
903
+ relationship,
904
+ "Target",
905
+ `${entry.name}!/${nestedEntry.name}`
906
+ );
907
+ if (targetMode !== "Internal" || /^(?:https?|ftp|file|data|javascript):/i.test(target) || target.includes("\\") || /%(?:2e|2f|5c)/i.test(target)) {
908
+ throw pptxSecurity(
909
+ "Embedded chart workbook contains an external relationship",
910
+ "external-relationship",
911
+ entry.name
912
+ );
913
+ }
914
+ }
915
+ }
916
+ if (pptxXmlDescendants(root, "f").length > 0) {
917
+ throw pptxSecurity(
918
+ "Formula-bearing embedded chart workbooks are not retained",
919
+ "active-content",
920
+ entry.name
921
+ );
922
+ }
923
+ } else {
924
+ await verifyBoundedZipEntry(
925
+ bytes,
926
+ nestedEntry,
927
+ context.limits.nestedPackageBytes,
928
+ pptxZipFailure
929
+ );
930
+ }
931
+ }
932
+ }
933
+ function rejectNestedWorkbookEntry(name, container) {
934
+ if (/(?:^|\/)(?:vbaProject|vbaData)\.bin$/i.test(name) || /(?:^|\/)(?:activeX|embeddings|externalLinks|connections|queryTables|webextensions)(?:\/|$)/i.test(
935
+ name
936
+ ) || /\.(?:exe|dll|com|msi|js|vbs|bat|cmd|ps1|sh|scr|jar)$/i.test(name)) {
937
+ throw pptxSecurity(
938
+ `Embedded chart workbook contains active content: ${name}`,
939
+ "active-content",
940
+ container
941
+ );
942
+ }
943
+ }
944
+ function rejectActiveEntry(name, directory = false) {
945
+ const safeChartWorkbook = /^ppt\/embeddings\/[^/]+\.xlsx$/i.test(name);
946
+ if (/(?:^|\/)(?:vbaProject|vbaData)\.bin$/i.test(name) || /(?:^|\/)(?:activeX|oleObject|externalLinks|connections|webextensions)(?:\/|$)/i.test(name) || /^ppt\/(?:audio|video)\//i.test(name) || /^ppt\/embeddings\//i.test(name) && !directory && !safeChartWorkbook || !directory && /\.(?:zip|docx|dotx|xlsx|xltx|pptx|ppsx)$/i.test(name) && !safeChartWorkbook || /\.(?:exe|dll|com|msi|js|vbs|vbe|bat|cmd|ps1|sh|scr|jar|ppam|pptm|svg|svgz|html?|mhtml)$/i.test(
947
+ name
948
+ )) {
949
+ throw pptxSecurity("Active presentation content is forbidden", "active-content", name);
950
+ }
951
+ }
952
+ function isRasterMediaPart(name) {
953
+ return /^ppt\/media\/[^/]+\.(?:png|jpe?g|gif|webp)$/i.test(name);
954
+ }
955
+ function reserveRetained(context, bytes, partName) {
956
+ context.retainedBytes += bytes;
957
+ if (context.retainedBytes > context.limits.retainedBytes) {
958
+ throw pptxSecurity("PPTX exceeds its retained-memory budget", "limit-exceeded", partName);
959
+ }
960
+ }
961
+ function importMastersAndLayouts(context, presentation, presentationRoot, presentationRelationships, masterByPart, layoutByPart) {
962
+ const masterIds = pptxXmlChildren(pptxXmlChild(presentationRoot, "sldMasterIdLst"));
963
+ for (const masterId of masterIds) {
964
+ const relationshipId = requiredRelationshipId(masterId, "ppt/presentation.xml");
965
+ const relationship = presentationRelationships.get(relationshipId);
966
+ if (!relationship || relationship.type !== `${OFFICE_RELATIONSHIP_BASE}slideMaster`) {
967
+ throw pptxSecurity(
968
+ "Slide master id references the wrong relationship",
969
+ "invalid-package",
970
+ "ppt/presentation.xml"
971
+ );
972
+ }
973
+ const masterRoot = readXml(context, relationship.partName);
974
+ requireRoot(masterRoot, "sldMaster", PRESENTATION_NAMESPACE, relationship.partName);
975
+ const common = requiredChild(masterRoot, "cSld", relationship.partName);
976
+ const masterRelationships = byRelationshipId(relationshipsFor(context, relationship.partName));
977
+ const elements = parseTemplateTree(
978
+ context,
979
+ requiredChild(common, "spTree", relationship.partName),
980
+ masterRelationships,
981
+ relationship.partName
982
+ );
983
+ const master = presentation.masters.add({
984
+ name: pptxXmlAttribute(common, "name") ?? `Master ${presentation.masters.items.length + 1}`,
985
+ background: parseBackground(context, common),
986
+ elements
987
+ });
988
+ masterByPart.set(relationship.partName, master);
989
+ for (const layoutId of pptxXmlChildren(pptxXmlChild(masterRoot, "sldLayoutIdLst"))) {
990
+ const layoutRelationshipId = requiredRelationshipId(layoutId, relationship.partName);
991
+ const layoutRelationship = masterRelationships.get(layoutRelationshipId);
992
+ if (!layoutRelationship || layoutRelationship.type !== `${OFFICE_RELATIONSHIP_BASE}slideLayout`) {
993
+ throw pptxSecurity(
994
+ "Slide layout id references the wrong relationship",
995
+ "invalid-package",
996
+ relationship.partName
997
+ );
998
+ }
999
+ if (layoutByPart.has(layoutRelationship.partName)) continue;
1000
+ const layoutRoot = readXml(context, layoutRelationship.partName);
1001
+ requireRoot(layoutRoot, "sldLayout", PRESENTATION_NAMESPACE, layoutRelationship.partName);
1002
+ const layoutCommon = requiredChild(layoutRoot, "cSld", layoutRelationship.partName);
1003
+ const layoutRelationships = byRelationshipId(
1004
+ relationshipsFor(context, layoutRelationship.partName)
1005
+ );
1006
+ const parent = [...layoutRelationships.values()].find(
1007
+ (candidate) => candidate.type === `${OFFICE_RELATIONSHIP_BASE}slideMaster`
1008
+ );
1009
+ if (!parent || parent.partName !== relationship.partName) {
1010
+ throw pptxSecurity(
1011
+ "Slide layout does not link back to its master",
1012
+ "invalid-package",
1013
+ layoutRelationship.partName
1014
+ );
1015
+ }
1016
+ const layout = presentation.layouts.add({
1017
+ name: pptxXmlAttribute(layoutCommon, "name") ?? `Layout ${presentation.layouts.items.length + 1}`,
1018
+ masterId: master.id,
1019
+ background: parseBackground(context, layoutCommon),
1020
+ elements: parseTemplateTree(
1021
+ context,
1022
+ requiredChild(layoutCommon, "spTree", layoutRelationship.partName),
1023
+ layoutRelationships,
1024
+ layoutRelationship.partName
1025
+ )
1026
+ });
1027
+ layoutByPart.set(layoutRelationship.partName, layout);
1028
+ }
1029
+ }
1030
+ }
1031
+ function importSlides(context, presentation, presentationRoot, presentationRelationships, layoutByPart) {
1032
+ const slideIds = pptxXmlChildren(pptxXmlChild(presentationRoot, "sldIdLst"));
1033
+ if (slideIds.length > context.limits.slides) {
1034
+ throw pptxSecurity("PPTX exceeds its slide limit", "limit-exceeded", "ppt/presentation.xml");
1035
+ }
1036
+ const seenParts = /* @__PURE__ */ new Set();
1037
+ for (const slideId of slideIds) {
1038
+ const relationshipId = requiredRelationshipId(slideId, "ppt/presentation.xml");
1039
+ const relationship = presentationRelationships.get(relationshipId);
1040
+ if (!relationship || relationship.type !== `${OFFICE_RELATIONSHIP_BASE}slide` || seenParts.has(relationship.partName)) {
1041
+ throw pptxSecurity(
1042
+ "Slide id references an invalid or duplicate slide",
1043
+ "invalid-package",
1044
+ "ppt/presentation.xml"
1045
+ );
1046
+ }
1047
+ seenParts.add(relationship.partName);
1048
+ const root = readXml(context, relationship.partName);
1049
+ requireRoot(root, "sld", PRESENTATION_NAMESPACE, relationship.partName);
1050
+ const common = requiredChild(root, "cSld", relationship.partName);
1051
+ const relationships = relationshipsFor(context, relationship.partName);
1052
+ const relationshipById = byRelationshipId(relationships);
1053
+ const slide = presentation.slides.add();
1054
+ slide.title = pptxXmlAttribute(common, "name") ?? "";
1055
+ slide.background.fill = parseBackground(context, common);
1056
+ const layoutRelationship = relationships.find(
1057
+ (candidate) => candidate.type === `${OFFICE_RELATIONSHIP_BASE}slideLayout`
1058
+ );
1059
+ if (!layoutRelationship) {
1060
+ throw pptxSecurity(
1061
+ "Slide lacks a slide-layout relationship",
1062
+ "invalid-package",
1063
+ relationship.partName
1064
+ );
1065
+ }
1066
+ const layout = layoutByPart.get(layoutRelationship.partName);
1067
+ if (!layout) {
1068
+ throw pptxSecurity(
1069
+ "Slide references an unknown slide layout",
1070
+ "invalid-package",
1071
+ relationship.partName
1072
+ );
1073
+ }
1074
+ slide.setLayout(layout);
1075
+ const parsed = parseSceneTree(
1076
+ context,
1077
+ requiredChild(common, "spTree", relationship.partName),
1078
+ relationshipById,
1079
+ relationship.partName
1080
+ );
1081
+ let derivedTitle;
1082
+ for (const element of parsed) {
1083
+ switch (element.kind) {
1084
+ case "shape": {
1085
+ const shape = slide.shapes.add(element.config);
1086
+ if (!derivedTitle && (shape.placeholder?.type === "title" || shape.placeholder?.type === "ctrTitle") && shape.text.toString().length > 0)
1087
+ derivedTitle = shape.text.toString();
1088
+ break;
1089
+ }
1090
+ case "chart":
1091
+ slide.charts.add(element.type, element.config ?? {});
1092
+ break;
1093
+ case "image":
1094
+ slide.images.add(element.config);
1095
+ break;
1096
+ case "table":
1097
+ slide.tables.add(element.config);
1098
+ break;
1099
+ case "group":
1100
+ slide.groups.add(element.config);
1101
+ break;
1102
+ }
1103
+ }
1104
+ if (derivedTitle) slide.title = derivedTitle;
1105
+ const notesRelationship = relationships.find(
1106
+ (candidate) => candidate.type === `${OFFICE_RELATIONSHIP_BASE}notesSlide`
1107
+ );
1108
+ if (notesRelationship) slide.notes.set(parseNotes(context, notesRelationship.partName));
1109
+ if (pptxXmlChild(root, "transition"))
1110
+ addUnsupported(context, "transition", relationship.partName);
1111
+ if (pptxXmlChild(root, "timing")) addUnsupported(context, "animation", relationship.partName);
1112
+ }
1113
+ }
1114
+ function parseTemplateTree(context, tree, relationships, partName) {
1115
+ const output = [];
1116
+ for (const element of parseSceneTree(context, tree, relationships, partName)) {
1117
+ if (element.kind === "group") {
1118
+ addUnsupported(context, "group", partName);
1119
+ } else output.push(element);
1120
+ }
1121
+ return output;
1122
+ }
1123
+ function parseSceneTree(context, tree, relationships, partName, transform = IDENTITY_TRANSFORM, depth = 0) {
1124
+ if (depth > 32)
1125
+ throw pptxSecurity("Presentation group nesting exceeds its limit", "limit-exceeded", partName);
1126
+ const output = [];
1127
+ for (const element of pptxXmlChildren(tree)) {
1128
+ if (element.localName === "nvGrpSpPr" || element.localName === "grpSpPr") continue;
1129
+ consumeElement(context, partName);
1130
+ switch (element.localName) {
1131
+ case "sp":
1132
+ case "cxnSp":
1133
+ if (element.localName === "cxnSp") {
1134
+ addUnsupported(context, "connector", partName, "style-approximated");
1135
+ }
1136
+ output.push({ kind: "shape", config: parseShape(context, element, partName, transform) });
1137
+ break;
1138
+ case "pic":
1139
+ {
1140
+ const image = parseImage(context, element, relationships, partName, transform);
1141
+ if (image) output.push({ kind: "image", config: image });
1142
+ }
1143
+ break;
1144
+ case "graphicFrame": {
1145
+ const graphicData = pptxXmlDescendants(element, "graphicData")[0];
1146
+ const uri = pptxXmlAttribute(graphicData, "uri");
1147
+ if (uri === TABLE_GRAPHIC_URI) {
1148
+ output.push({ kind: "table", config: parseTable(context, element, partName, transform) });
1149
+ } else if (uri === CHART_GRAPHIC_URI) {
1150
+ const chartRef = pptxXmlDescendants(graphicData, "chart")[0];
1151
+ const relationshipId = requiredRelationshipId(chartRef, partName);
1152
+ const relationship = relationships.get(relationshipId);
1153
+ if (!relationship || relationship.type !== `${OFFICE_RELATIONSHIP_BASE}chart`) {
1154
+ throw pptxSecurity(
1155
+ "Chart frame references the wrong relationship",
1156
+ "invalid-package",
1157
+ partName
1158
+ );
1159
+ }
1160
+ const parsed = parseChart(context, relationship.partName);
1161
+ if (parsed) {
1162
+ const frameName = nonVisualName(element);
1163
+ if (frameName) parsed.config.name = frameName;
1164
+ parsed.config.position = parseFramePosition(element, partName, transform);
1165
+ output.push({ kind: "chart", ...parsed });
1166
+ }
1167
+ } else {
1168
+ addUnsupported(context, "smart-art", partName);
1169
+ }
1170
+ break;
1171
+ }
1172
+ case "grpSp": {
1173
+ const group = parseGroup(context, element, relationships, partName, transform, depth + 1);
1174
+ output.push({ kind: "group", config: group });
1175
+ break;
1176
+ }
1177
+ case "contentPart":
1178
+ case "alternateContent":
1179
+ case "extLst":
1180
+ addUnsupported(context, "custom-geometry", partName);
1181
+ break;
1182
+ default:
1183
+ addUnsupported(context, "custom-geometry", partName);
1184
+ }
1185
+ }
1186
+ return output;
1187
+ }
1188
+ function parseGroup(context, group, relationships, partName, parentTransform, depth) {
1189
+ const properties = requiredChild(group, "grpSpPr", partName);
1190
+ const xfrm = requiredChild(properties, "xfrm", partName);
1191
+ const rawPosition = rawPositionFromXfrm(xfrm, partName);
1192
+ const childOffsetElement = requiredChild(xfrm, "chOff", partName);
1193
+ const childExtentElement = requiredChild(xfrm, "chExt", partName);
1194
+ const childOffset = {
1195
+ left: emuNumber(requiredAttribute(childOffsetElement, "x", partName), partName),
1196
+ top: emuNumber(requiredAttribute(childOffsetElement, "y", partName), partName)
1197
+ };
1198
+ const childExtent = {
1199
+ width: positiveEmu(requiredAttribute(childExtentElement, "cx", partName), partName),
1200
+ height: positiveEmu(requiredAttribute(childExtentElement, "cy", partName), partName)
1201
+ };
1202
+ const position = applyTransform(rawPosition, parentTransform);
1203
+ const name = nonVisualName(group) ?? `Group ${context.elements}`;
1204
+ return {
1205
+ name,
1206
+ position,
1207
+ childOffset,
1208
+ childExtent,
1209
+ rotation: rotationFromXfrm(xfrm, partName),
1210
+ flipHorizontal: booleanAttribute(xfrm, "flipH"),
1211
+ flipVertical: booleanAttribute(xfrm, "flipV"),
1212
+ children: parseSceneTree(context, group, relationships, partName, IDENTITY_TRANSFORM, depth)
1213
+ };
1214
+ }
1215
+ function parseShape(context, element, partName, transform) {
1216
+ const properties = pptxXmlChild(element, "spPr");
1217
+ const xfrm = pptxXmlChild(properties, "xfrm");
1218
+ const position = xfrm ? applyTransform(rawPositionFromXfrm(xfrm, partName), transform) : applyTransform({ left: 0, top: 0, width: 1, height: 1 }, transform);
1219
+ const preset = pptxXmlAttribute(pptxXmlChild(properties, "prstGeom"), "prst") ?? (element.localName === "cxnSp" ? "line" : "rect");
1220
+ const geometry = mapGeometry(context, preset, partName);
1221
+ const text = parseTextBody(context, pptxXmlChild(element, "txBody"), partName);
1222
+ const placeholderElement = pptxXmlDescendants(
1223
+ pptxXmlChild(element, element.localName === "cxnSp" ? "nvCxnSpPr" : "nvSpPr"),
1224
+ "ph"
1225
+ )[0];
1226
+ const placeholderType = pptxXmlAttribute(placeholderElement, "type");
1227
+ const placeholderIndex = optionalInteger(pptxXmlAttribute(placeholderElement, "idx"), partName);
1228
+ return {
1229
+ geometry,
1230
+ name: nonVisualName(element) ?? `Shape ${context.elements}`,
1231
+ position,
1232
+ fill: parseFill(context, properties, "white") ?? "white",
1233
+ line: parseLine(context, pptxXmlChild(properties, "ln"), partName),
1234
+ text: text.value,
1235
+ textStyle: text.style,
1236
+ rotation: xfrm ? rotationFromXfrm(xfrm, partName) : 0,
1237
+ ...placeholderType ? {
1238
+ placeholder: {
1239
+ type: placeholderType,
1240
+ ...placeholderIndex === void 0 ? {} : { index: placeholderIndex }
1241
+ }
1242
+ } : {}
1243
+ };
1244
+ }
1245
+ function parseImage(context, element, relationships, partName, transform) {
1246
+ const blip = pptxXmlDescendants(element, "blip")[0];
1247
+ const relationshipId = requiredRelationshipId(blip, partName, "embed");
1248
+ const relationship = relationships.get(relationshipId);
1249
+ if (!relationship || relationship.type !== `${OFFICE_RELATIONSHIP_BASE}image`) {
1250
+ throw pptxSecurity("Picture references the wrong relationship", "invalid-package", partName);
1251
+ }
1252
+ if (!isRasterMediaPart(relationship.partName)) {
1253
+ addUnsupported(context, "media", relationship.partName);
1254
+ return void 0;
1255
+ }
1256
+ const bytes = readPartBytes(context, relationship.partName);
1257
+ context.imageBytes += bytes.byteLength;
1258
+ if (context.imageBytes > context.limits.imageBytes) {
1259
+ throw pptxSecurity(
1260
+ "PPTX exceeds its decoded image byte limit",
1261
+ "limit-exceeded",
1262
+ relationship.partName
1263
+ );
1264
+ }
1265
+ const properties = requiredChild(element, "spPr", partName);
1266
+ const xfrm = requiredChild(properties, "xfrm", partName);
1267
+ const cropElement = pptxXmlDescendants(pptxXmlChild(element, "blipFill"), "srcRect")[0];
1268
+ const crop = cropElement ? {
1269
+ left: cropPercent(pptxXmlAttribute(cropElement, "l"), partName),
1270
+ top: cropPercent(pptxXmlAttribute(cropElement, "t"), partName),
1271
+ right: cropPercent(pptxXmlAttribute(cropElement, "r"), partName),
1272
+ bottom: cropPercent(pptxXmlAttribute(cropElement, "b"), partName)
1273
+ } : void 0;
1274
+ const geometry = mapImageGeometry(
1275
+ pptxXmlAttribute(pptxXmlChild(properties, "prstGeom"), "prst") ?? "rect"
1276
+ );
1277
+ const name = nonVisualName(element) ?? `Image ${context.elements}`;
1278
+ const description = nonVisualDescription(element) ?? "";
1279
+ const imageConfig = {
1280
+ blob: bytes.slice(),
1281
+ contentType: rasterContentType(relationship.partName),
1282
+ name,
1283
+ alt: description,
1284
+ position: applyTransform(rawPositionFromXfrm(xfrm, partName), transform),
1285
+ fit: crop ? "cover" : "contain",
1286
+ geometry,
1287
+ rotation: rotationFromXfrm(xfrm, partName),
1288
+ flipHorizontal: booleanAttribute(xfrm, "flipH"),
1289
+ flipVertical: booleanAttribute(xfrm, "flipV"),
1290
+ lockAspectRatio: booleanAttribute(pptxXmlDescendants(element, "picLocks")[0], "noChangeAspect"),
1291
+ ...crop ? { crop } : {}
1292
+ };
1293
+ return imageConfig;
1294
+ }
1295
+ function parseTable(context, frame, partName, transform) {
1296
+ const table = pptxXmlDescendants(frame, "tbl")[0];
1297
+ if (!table) throw pptxSecurity("Table frame lacks table data", "invalid-package", partName);
1298
+ const grid = requiredChild(table, "tblGrid", partName);
1299
+ const columnWidths = pptxXmlChildren(grid).map(
1300
+ (column) => positiveEmu(requiredAttribute(column, "w", partName), partName)
1301
+ );
1302
+ if (columnWidths.length === 0) {
1303
+ throw pptxSecurity("Presentation table has no columns", "invalid-package", partName);
1304
+ }
1305
+ const rowElements = pptxXmlChildren(table).filter((child) => child.localName === "tr");
1306
+ const rows = [];
1307
+ const rowHeights = [];
1308
+ const occupied = Array.from(
1309
+ { length: rowElements.length },
1310
+ () => new Uint8Array(columnWidths.length)
1311
+ );
1312
+ for (const [rowIndex, rowElement] of rowElements.entries()) {
1313
+ rowHeights.push(positiveEmu(requiredAttribute(rowElement, "h", partName), partName));
1314
+ const row = Array.from(
1315
+ { length: columnWidths.length },
1316
+ () => null
1317
+ );
1318
+ let column = 0;
1319
+ for (const cellElement of pptxXmlChildren(rowElement).filter(
1320
+ (child) => child.localName === "tc"
1321
+ )) {
1322
+ if (column >= columnWidths.length) {
1323
+ throw pptxSecurity("Presentation table row exceeds its grid", "invalid-package", partName);
1324
+ }
1325
+ const properties = pptxXmlChild(cellElement, "tcPr");
1326
+ const horizontalMerge = booleanAttribute(cellElement, "hMerge") || booleanAttribute(properties, "hMerge");
1327
+ const verticalMerge = booleanAttribute(cellElement, "vMerge") || booleanAttribute(properties, "vMerge");
1328
+ if (horizontalMerge || verticalMerge) {
1329
+ if (occupied[rowIndex][column] === 0) {
1330
+ throw pptxSecurity(
1331
+ "Presentation table merge continuation has no spanning anchor",
1332
+ "invalid-package",
1333
+ partName
1334
+ );
1335
+ }
1336
+ column += 1;
1337
+ continue;
1338
+ }
1339
+ if (occupied[rowIndex][column] !== 0) {
1340
+ throw pptxSecurity("Presentation table spans overlap", "invalid-package", partName);
1341
+ }
1342
+ const colSpan = optionalPositiveInteger(
1343
+ pptxXmlAttribute(cellElement, "gridSpan") ?? pptxXmlAttribute(properties, "gridSpan"),
1344
+ partName
1345
+ ) ?? 1;
1346
+ const rowSpan = optionalPositiveInteger(
1347
+ pptxXmlAttribute(cellElement, "rowSpan") ?? pptxXmlAttribute(properties, "rowSpan"),
1348
+ partName
1349
+ ) ?? 1;
1350
+ if (column + colSpan > columnWidths.length || rowIndex + rowSpan > rowElements.length) {
1351
+ throw pptxSecurity(
1352
+ "Presentation table cell span exceeds its grid",
1353
+ "invalid-package",
1354
+ partName
1355
+ );
1356
+ }
1357
+ const text = parseTextBody(context, pptxXmlChild(cellElement, "txBody"), partName);
1358
+ row[column] = {
1359
+ text: text.value,
1360
+ textStyle: text.style,
1361
+ fill: parseFill(context, properties, "white") ?? "white",
1362
+ colSpan,
1363
+ rowSpan
1364
+ };
1365
+ for (let occupiedRow = rowIndex; occupiedRow < rowIndex + rowSpan; occupiedRow += 1) {
1366
+ for (let occupiedColumn = column; occupiedColumn < column + colSpan; occupiedColumn += 1) {
1367
+ if (occupiedRow === rowIndex && occupiedColumn === column) continue;
1368
+ if (occupied[occupiedRow][occupiedColumn] !== 0) {
1369
+ throw pptxSecurity("Presentation table spans overlap", "invalid-package", partName);
1370
+ }
1371
+ occupied[occupiedRow][occupiedColumn] = 1;
1372
+ }
1373
+ }
1374
+ column += 1;
1375
+ }
1376
+ if (column !== columnWidths.length) {
1377
+ throw pptxSecurity(
1378
+ "Presentation table row does not cover its grid",
1379
+ "invalid-package",
1380
+ partName
1381
+ );
1382
+ }
1383
+ rows.push(row);
1384
+ }
1385
+ for (let row = 0; row < rows.length; row += 1) {
1386
+ for (let column = 0; column < columnWidths.length; column += 1) {
1387
+ if (rows[row][column] === null && occupied[row][column] === 0) {
1388
+ throw pptxSecurity(
1389
+ "Presentation table has an uncovered grid slot",
1390
+ "invalid-package",
1391
+ partName
1392
+ );
1393
+ }
1394
+ }
1395
+ }
1396
+ if (pptxXmlChild(table, "tblPr")) {
1397
+ const styleId = pptxXmlText(pptxXmlChild(pptxXmlChild(table, "tblPr"), "tableStyleId"));
1398
+ if (styleId) addUnsupported(context, "table-style", partName);
1399
+ }
1400
+ return {
1401
+ name: nonVisualName(frame) ?? `Table ${context.elements}`,
1402
+ position: parseFramePosition(frame, partName, transform),
1403
+ rows,
1404
+ columnWidths,
1405
+ rowHeights
1406
+ };
1407
+ }
1408
+ function parseChart(context, partName) {
1409
+ const root = readXml(context, partName);
1410
+ requireRoot(root, "chartSpace", CHART_NAMESPACE, partName);
1411
+ const chart = requiredChild(root, "chart", partName);
1412
+ const plot = requiredChild(chart, "plotArea", partName);
1413
+ const chartElement = pptxXmlChildren(plot).find(
1414
+ (candidate) => CHART_ELEMENT_TYPES.has(candidate.localName)
1415
+ );
1416
+ if (!chartElement) {
1417
+ addUnsupported(context, "unsupported-chart", partName);
1418
+ return void 0;
1419
+ }
1420
+ const type = CHART_ELEMENT_TYPES.get(chartElement.localName);
1421
+ if (chartElement.localName.includes("3D")) {
1422
+ addUnsupported(context, "unsupported-chart", partName, "style-approximated");
1423
+ }
1424
+ const series = [];
1425
+ for (const seriesElement of pptxXmlChildren(chartElement).filter(
1426
+ (candidate) => candidate.localName === "ser"
1427
+ )) {
1428
+ const name = parseChartString(pptxXmlChild(seriesElement, "tx")) || `Series ${series.length + 1}`;
1429
+ const categories2 = parseChartCategories(pptxXmlChild(seriesElement, "cat"));
1430
+ const values = parseChartNumbers(
1431
+ pptxXmlChild(seriesElement, type === "scatter" || type === "bubble" ? "yVal" : "val"),
1432
+ context,
1433
+ partName
1434
+ );
1435
+ const xValues = parseChartNumbers(pptxXmlChild(seriesElement, "xVal"), context, partName);
1436
+ const bubbleSizes = parseChartNumbers(
1437
+ pptxXmlChild(seriesElement, "bubbleSize"),
1438
+ context,
1439
+ partName
1440
+ );
1441
+ const fill = parseFill(context, pptxXmlChild(seriesElement, "spPr"), void 0);
1442
+ series.push({
1443
+ name,
1444
+ categories: categories2,
1445
+ values,
1446
+ ...xValues.length > 0 ? { xValues } : {},
1447
+ ...bubbleSizes.length > 0 ? { bubbleSizes } : {},
1448
+ ...fill ? { fill } : {}
1449
+ });
1450
+ }
1451
+ const categories = series.find((candidate) => (candidate.categories?.length ?? 0) > 0)?.categories ?? [];
1452
+ const legend = pptxXmlChild(chart, "legend");
1453
+ const labels = pptxXmlChild(chartElement, "dLbls");
1454
+ const axes = pptxXmlChildren(plot).filter((candidate) => candidate.localName.endsWith("Ax"));
1455
+ const categoryAxis = axes.find(
1456
+ (axis) => axis.localName === "catAx" || axis.localName === "dateAx"
1457
+ );
1458
+ const valueAxis = axes.find((axis) => axis.localName === "valAx");
1459
+ return {
1460
+ type,
1461
+ config: {
1462
+ name: `Chart ${context.elements}`,
1463
+ title: parseChartString(pptxXmlChild(chart, "title")),
1464
+ categories,
1465
+ series,
1466
+ hasLegend: Boolean(legend) && !booleanAttribute(pptxXmlChild(legend, "delete"), "val"),
1467
+ ...legend ? {
1468
+ legend: {
1469
+ position: chartLegendPosition(
1470
+ pptxXmlAttribute(pptxXmlChild(legend, "legendPos"), "val")
1471
+ )
1472
+ }
1473
+ } : {},
1474
+ ...parseChartAxis(categoryAxis) ? { xAxis: parseChartAxis(categoryAxis) } : {},
1475
+ ...parseChartAxis(valueAxis) ? { yAxis: parseChartAxis(valueAxis) } : {},
1476
+ dataLabels: {
1477
+ showValue: booleanAttribute(pptxXmlChild(labels, "showVal"), "val"),
1478
+ showSeriesName: booleanAttribute(pptxXmlChild(labels, "showSerName"), "val"),
1479
+ showCategoryName: booleanAttribute(pptxXmlChild(labels, "showCatName"), "val"),
1480
+ showPercent: booleanAttribute(pptxXmlChild(labels, "showPercent"), "val")
1481
+ }
1482
+ }
1483
+ };
1484
+ }
1485
+ function parseNotes(context, partName) {
1486
+ const root = readXml(context, partName);
1487
+ requireRoot(root, "notes", PRESENTATION_NAMESPACE, partName);
1488
+ const common = requiredChild(root, "cSld", partName);
1489
+ const tree = requiredChild(common, "spTree", partName);
1490
+ const paragraphs = [];
1491
+ for (const shape of pptxXmlChildren(tree).filter((child) => child.localName === "sp")) {
1492
+ const placeholder = pptxXmlDescendants(shape, "ph")[0];
1493
+ const type = pptxXmlAttribute(placeholder, "type");
1494
+ if (type !== "body") continue;
1495
+ const text = parseTextBody(context, pptxXmlChild(shape, "txBody"), partName).value;
1496
+ if (text.length > 0) paragraphs.push(text);
1497
+ }
1498
+ return paragraphs.join("\n");
1499
+ }
1500
+ function parseTextBody(context, body, partName) {
1501
+ if (!body) return { value: "", style: {} };
1502
+ const paragraphs = [];
1503
+ const styles = [];
1504
+ let paragraphAlignment;
1505
+ for (const paragraph of pptxXmlChildren(body).filter((child) => child.localName === "p")) {
1506
+ const parts = [];
1507
+ const paragraphProperties = pptxXmlChild(paragraph, "pPr");
1508
+ const alignment = mapTextAlignment(pptxXmlAttribute(paragraphProperties, "algn"));
1509
+ if (alignment && !paragraphAlignment) paragraphAlignment = alignment;
1510
+ for (const child of pptxXmlChildren(paragraph)) {
1511
+ if (child.localName === "r" || child.localName === "fld") {
1512
+ const text = pptxXmlDirectText(pptxXmlChild(child, "t"));
1513
+ consumeText(context, text.length, partName);
1514
+ parts.push(text);
1515
+ const properties = pptxXmlChild(child, "rPr");
1516
+ if (properties) styles.push(parseTextStyle(context, properties, body));
1517
+ } else if (child.localName === "br") parts.push("\n");
1518
+ }
1519
+ paragraphs.push(parts.join(""));
1520
+ }
1521
+ const value = paragraphs.join("\n");
1522
+ const firstStyle = styles[0] ?? {};
1523
+ if (styles.some((style) => JSON.stringify(style) !== JSON.stringify(firstStyle))) {
1524
+ addUnsupported(context, "theme", partName, "style-approximated");
1525
+ }
1526
+ const verticalAlignment = mapVerticalAlignment(
1527
+ pptxXmlAttribute(pptxXmlChild(body, "bodyPr"), "anchor")
1528
+ );
1529
+ return {
1530
+ value,
1531
+ style: {
1532
+ ...firstStyle,
1533
+ ...paragraphAlignment ? { alignment: paragraphAlignment } : {},
1534
+ ...verticalAlignment ? { verticalAlignment } : {}
1535
+ }
1536
+ };
1537
+ }
1538
+ function parseTextStyle(context, properties, body) {
1539
+ const size = optionalNumber(pptxXmlAttribute(properties, "sz"));
1540
+ const font = pptxXmlAttribute(pptxXmlChild(properties, "latin"), "typeface");
1541
+ const fill = parseFill(context, properties, void 0);
1542
+ const underline = pptxXmlAttribute(properties, "u");
1543
+ const verticalAlignment = mapVerticalAlignment(
1544
+ pptxXmlAttribute(pptxXmlChild(body, "bodyPr"), "anchor")
1545
+ );
1546
+ return {
1547
+ ...font && !font.startsWith("+") ? { fontFamily: font } : {},
1548
+ ...size !== void 0 ? { fontSize: size / 75 } : {},
1549
+ bold: booleanAttribute(properties, "b"),
1550
+ italic: booleanAttribute(properties, "i"),
1551
+ underline: underline !== void 0 && underline !== "none",
1552
+ ...fill && fill !== "none" ? { color: typeof fill === "string" ? fill : fill.color } : {},
1553
+ ...verticalAlignment ? { verticalAlignment } : {}
1554
+ };
1555
+ }
1556
+ function parseBackground(context, common) {
1557
+ const background = pptxXmlChild(common, "bg");
1558
+ if (!background) return "white";
1559
+ const properties = pptxXmlChild(background, "bgPr");
1560
+ if (properties) return parseFill(context, properties, "white") ?? "white";
1561
+ const reference = pptxXmlChild(background, "bgRef");
1562
+ const scheme = pptxXmlAttribute(pptxXmlChild(reference, "schemeClr"), "val");
1563
+ return scheme ? context.themeColors.get(scheme) ?? "white" : "white";
1564
+ }
1565
+ function parseFill(context, parent, fallback) {
1566
+ if (!parent) return fallback;
1567
+ if (pptxXmlChild(parent, "noFill")) return "none";
1568
+ const solid = pptxXmlChild(parent, "solidFill");
1569
+ if (!solid) return fallback;
1570
+ const srgb = pptxXmlChild(solid, "srgbClr");
1571
+ const scheme = pptxXmlChild(solid, "schemeClr");
1572
+ const system = pptxXmlChild(solid, "sysClr");
1573
+ const alpha = optionalNumber(
1574
+ pptxXmlAttribute(pptxXmlChild(srgb ?? scheme ?? system, "alpha"), "val")
1575
+ );
1576
+ if (alpha === 0) return "none";
1577
+ const srgbValue = pptxXmlAttribute(srgb, "val");
1578
+ if (srgbValue && /^[0-9A-Fa-f]{6}$/.test(srgbValue)) return `#${srgbValue.toLowerCase()}`;
1579
+ const schemeValue = pptxXmlAttribute(scheme, "val");
1580
+ if (schemeValue) return context.themeColors.get(schemeValue) ?? fallback ?? "#000000";
1581
+ const systemValue = pptxXmlAttribute(system, "lastClr");
1582
+ if (systemValue && /^[0-9A-Fa-f]{6}$/.test(systemValue)) return `#${systemValue.toLowerCase()}`;
1583
+ return fallback;
1584
+ }
1585
+ function parseLine(context, line, partName) {
1586
+ if (!line) return { style: "none", fill: "none", width: 0 };
1587
+ const width = optionalNumber(pptxXmlAttribute(line, "w"));
1588
+ const dash = pptxXmlAttribute(pptxXmlChild(line, "prstDash"), "val");
1589
+ const style = dash === "dot" || dash === "sysDot" ? "dot" : dash && dash !== "solid" ? "dash" : "solid";
1590
+ if (dash && !["solid", "dot", "sysDot", "dash", "sysDash", "lgDash"].includes(dash)) {
1591
+ addUnsupported(context, "theme", partName, "style-approximated");
1592
+ }
1593
+ const fill = parseFill(context, line, "#000000") ?? "#000000";
1594
+ return fill === "none" ? { style: "none", fill: "none", width: 0 } : { style, fill, width: width === void 0 ? 1 : width / EMU_PER_PIXEL };
1595
+ }
1596
+ function parseFramePosition(frame, partName, transform) {
1597
+ const xfrm = requiredChild(frame, "xfrm", partName);
1598
+ return applyTransform(rawPositionFromXfrm(xfrm, partName), transform);
1599
+ }
1600
+ function rawPositionFromXfrm(xfrm, partName) {
1601
+ const offset = requiredChild(xfrm, "off", partName);
1602
+ const extent = requiredChild(xfrm, "ext", partName);
1603
+ return {
1604
+ left: emuNumber(requiredAttribute(offset, "x", partName), partName),
1605
+ top: emuNumber(requiredAttribute(offset, "y", partName), partName),
1606
+ width: positiveEmu(requiredAttribute(extent, "cx", partName), partName),
1607
+ height: positiveEmu(requiredAttribute(extent, "cy", partName), partName)
1608
+ };
1609
+ }
1610
+ function applyTransform(position, transform) {
1611
+ return {
1612
+ left: transform.offsetX + position.left * transform.scaleX,
1613
+ top: transform.offsetY + position.top * transform.scaleY,
1614
+ width: position.width * transform.scaleX,
1615
+ height: position.height * transform.scaleY
1616
+ };
1617
+ }
1618
+ function rotationFromXfrm(xfrm, partName) {
1619
+ const raw = pptxXmlAttribute(xfrm, "rot");
1620
+ if (raw === void 0) return 0;
1621
+ const value = Number(raw);
1622
+ if (!Number.isSafeInteger(value) || Math.abs(value) > 216e5) {
1623
+ throw pptxSecurity("Shape rotation is invalid", "invalid-package", partName);
1624
+ }
1625
+ return value / 6e4;
1626
+ }
1627
+ function mapGeometry(context, preset, partName) {
1628
+ const mapped = GEOMETRY_MAP.get(preset);
1629
+ if (mapped) return mapped;
1630
+ addUnsupported(context, "custom-geometry", partName);
1631
+ return "rect";
1632
+ }
1633
+ function mapImageGeometry(value) {
1634
+ return value === "ellipse" ? "ellipse" : value === "roundRect" ? "roundRect" : "rect";
1635
+ }
1636
+ function parseChartString(element) {
1637
+ if (!element) return "";
1638
+ const points = pptxXmlDescendants(element, "pt").map((point) => ({
1639
+ index: optionalInteger(pptxXmlAttribute(point, "idx"), "chart") ?? 0,
1640
+ value: pptxXmlText(pptxXmlChild(point, "v"))
1641
+ })).sort((left, right) => left.index - right.index);
1642
+ if (points.length > 0) return points.map((point) => point.value).join("");
1643
+ return pptxXmlDescendants(element, "t").map(pptxXmlText).join("");
1644
+ }
1645
+ function parseChartCategories(element) {
1646
+ if (!element) return [];
1647
+ return pptxXmlDescendants(element, "pt").map((point) => ({
1648
+ index: optionalInteger(pptxXmlAttribute(point, "idx"), "chart") ?? 0,
1649
+ value: pptxXmlText(pptxXmlChild(point, "v"))
1650
+ })).sort((left, right) => left.index - right.index).map((point) => point.value);
1651
+ }
1652
+ function parseChartNumbers(element, context, partName) {
1653
+ if (!element) return [];
1654
+ const points = pptxXmlDescendants(element, "pt").map((point) => {
1655
+ const index = optionalInteger(pptxXmlAttribute(point, "idx"), partName) ?? 0;
1656
+ const raw = pptxXmlText(pptxXmlChild(point, "v"));
1657
+ const value = Number(raw);
1658
+ if (!Number.isFinite(value)) {
1659
+ throw pptxSecurity("Chart cache contains a non-finite number", "invalid-package", partName);
1660
+ }
1661
+ return { index, value };
1662
+ }).sort((left, right) => left.index - right.index);
1663
+ context.chartPoints += points.length;
1664
+ if (context.chartPoints > context.limits.chartPoints) {
1665
+ throw pptxSecurity("PPTX exceeds its chart point limit", "limit-exceeded", partName);
1666
+ }
1667
+ return points.map((point) => point.value);
1668
+ }
1669
+ function parseChartAxis(axis) {
1670
+ if (!axis) return void 0;
1671
+ const scaling = pptxXmlChild(axis, "scaling");
1672
+ const minimum = optionalNumber(pptxXmlAttribute(pptxXmlChild(scaling, "min"), "val"));
1673
+ const maximum = optionalNumber(pptxXmlAttribute(pptxXmlChild(scaling, "max"), "val"));
1674
+ return {
1675
+ visible: !booleanAttribute(pptxXmlChild(axis, "delete"), "val"),
1676
+ title: parseChartString(pptxXmlChild(axis, "title")),
1677
+ ...minimum === void 0 ? {} : { min: minimum },
1678
+ ...maximum === void 0 ? {} : { max: maximum }
1679
+ };
1680
+ }
1681
+ function chartLegendPosition(value) {
1682
+ switch (value) {
1683
+ case "l":
1684
+ return "left";
1685
+ case "t":
1686
+ return "top";
1687
+ case "tr":
1688
+ return "topRight";
1689
+ case "r":
1690
+ return "right";
1691
+ default:
1692
+ return "bottom";
1693
+ }
1694
+ }
1695
+ function parseThemeColors(root, partName) {
1696
+ requireRoot(root, "theme", DRAWING_NAMESPACE, partName);
1697
+ const colors = new Map(DEFAULT_THEME_COLORS);
1698
+ const scheme = pptxXmlDescendants(root, "clrScheme")[0];
1699
+ for (const entry of pptxXmlChildren(scheme)) {
1700
+ const color = pptxXmlChildren(entry)[0];
1701
+ const value = pptxXmlAttribute(color, color?.localName === "sysClr" ? "lastClr" : "val");
1702
+ if (value && /^[0-9A-Fa-f]{6}$/.test(value))
1703
+ colors.set(entry.localName, `#${value.toLowerCase()}`);
1704
+ }
1705
+ return colors;
1706
+ }
1707
+ function parseSlideSize(root) {
1708
+ const size = requiredChild(root, "sldSz", "ppt/presentation.xml");
1709
+ return {
1710
+ width: positiveEmu(
1711
+ requiredAttribute(size, "cx", "ppt/presentation.xml"),
1712
+ "ppt/presentation.xml"
1713
+ ),
1714
+ height: positiveEmu(
1715
+ requiredAttribute(size, "cy", "ppt/presentation.xml"),
1716
+ "ppt/presentation.xml"
1717
+ )
1718
+ };
1719
+ }
1720
+ function mapTextAlignment(value) {
1721
+ if (value === "ctr") return "center";
1722
+ if (value === "r") return "right";
1723
+ if (value === "just" || value === "justLow" || value === "dist") return "justify";
1724
+ return value === "l" ? "left" : void 0;
1725
+ }
1726
+ function mapVerticalAlignment(value) {
1727
+ if (value === "ctr") return "middle";
1728
+ if (value === "b") return "bottom";
1729
+ return value === "t" ? "top" : void 0;
1730
+ }
1731
+ function rasterContentType(partName) {
1732
+ const extension = partName.slice(partName.lastIndexOf(".") + 1).toLowerCase();
1733
+ if (extension === "png") return "image/png";
1734
+ if (extension === "jpg" || extension === "jpeg") return "image/jpeg";
1735
+ if (extension === "gif") return "image/gif";
1736
+ return "image/webp";
1737
+ }
1738
+ function cropPercent(value, partName) {
1739
+ if (value === void 0) return 0;
1740
+ const parsed = Number(value);
1741
+ if (!Number.isFinite(parsed) || parsed < 0 || parsed > 1e5) {
1742
+ throw pptxSecurity("Picture crop percentage is invalid", "invalid-package", partName);
1743
+ }
1744
+ return parsed / 1e3;
1745
+ }
1746
+ function nonVisualName(element) {
1747
+ return pptxXmlAttribute(pptxXmlDescendants(element, "cNvPr")[0], "name");
1748
+ }
1749
+ function nonVisualDescription(element) {
1750
+ return pptxXmlAttribute(pptxXmlDescendants(element, "cNvPr")[0], "descr");
1751
+ }
1752
+ function requiredRelationshipId(element, partName, localName = "id") {
1753
+ if (!element)
1754
+ throw pptxSecurity("Relationship reference is missing", "invalid-package", partName);
1755
+ const direct = pptxXmlQualifiedAttribute(element, `r:${localName}`);
1756
+ if (direct) return direct;
1757
+ const candidates = [...element.attributes].filter(
1758
+ ([name]) => name.includes(":") && name.slice(name.indexOf(":") + 1) === localName && element.attributeNamespaces?.get(name) === OFFICE_RELATIONSHIP_BASE.slice(0, -1)
1759
+ );
1760
+ if (candidates.length !== 1 || !candidates[0]?.[1]) {
1761
+ throw pptxSecurity(
1762
+ "Relationship reference is missing or ambiguous",
1763
+ "invalid-package",
1764
+ partName
1765
+ );
1766
+ }
1767
+ return candidates[0][1];
1768
+ }
1769
+ function requiredChild(parent, localName, partName) {
1770
+ const candidates = pptxXmlChildren(parent).filter((child) => child.localName === localName);
1771
+ if (candidates.length !== 1 || !candidates[0]) {
1772
+ throw pptxSecurity(
1773
+ `Required ${localName} element is missing or duplicated`,
1774
+ "invalid-package",
1775
+ partName
1776
+ );
1777
+ }
1778
+ return candidates[0];
1779
+ }
1780
+ function requireRoot(element, localName, namespaceUri, partName) {
1781
+ if (element.localName !== localName || element.namespaceUri !== namespaceUri) {
1782
+ throw pptxSecurity(
1783
+ `OOXML root must be ${localName} in ${namespaceUri}`,
1784
+ "invalid-package",
1785
+ partName
1786
+ );
1787
+ }
1788
+ }
1789
+ function requiredAttribute(element, localName, partName) {
1790
+ const value = pptxXmlAttribute(element, localName);
1791
+ if (value === void 0 || value.length === 0) {
1792
+ throw pptxSecurity(`Required ${localName} attribute is missing`, "invalid-package", partName);
1793
+ }
1794
+ return value;
1795
+ }
1796
+ function byRelationshipId(relationships) {
1797
+ return new Map(relationships.map((relationship) => [relationship.id, relationship]));
1798
+ }
1799
+ function relationshipPartName(sourcePart) {
1800
+ if (sourcePart === "") return "_rels/.rels";
1801
+ const slash = sourcePart.lastIndexOf("/");
1802
+ const directory = slash < 0 ? "" : sourcePart.slice(0, slash + 1);
1803
+ const file = slash < 0 ? sourcePart : sourcePart.slice(slash + 1);
1804
+ return `${directory}_rels/${file}.rels`;
1805
+ }
1806
+ function relationshipSourcePart(relationshipPart) {
1807
+ if (relationshipPart === "_rels/.rels") return "";
1808
+ const match = /^(.*\/)?_rels\/([^/]+)\.rels$/.exec(relationshipPart);
1809
+ if (!match) {
1810
+ throw pptxSecurity("Invalid relationships part path", "invalid-package", relationshipPart);
1811
+ }
1812
+ return `${match[1] ?? ""}${match[2] ?? ""}`;
1813
+ }
1814
+ function resolvePartTarget(sourcePart, target, relationshipPart) {
1815
+ if (/[\u0000-\u001f\u007f]/.test(target) || target.includes("\\") || target.startsWith("//") || /%(?:2e|2f|5c)/i.test(target)) {
1816
+ throw pptxSecurity("Unsafe OPC relationship target", "invalid-package", relationshipPart);
1817
+ }
1818
+ const packageAbsolute = target.startsWith("/");
1819
+ const relative = packageAbsolute ? target.slice(1) : target;
1820
+ const base = !packageAbsolute && sourcePart.includes("/") ? sourcePart.slice(0, sourcePart.lastIndexOf("/") + 1) : "";
1821
+ const combined = `${base}${relative}`;
1822
+ if (combined.length === 0 || combined.length > 4096 || combined.includes("//")) {
1823
+ throw pptxSecurity("Invalid OPC relationship target", "invalid-package", relationshipPart);
1824
+ }
1825
+ const output = [];
1826
+ for (const segment of combined.split("/")) {
1827
+ if (segment === "" || segment === ".") continue;
1828
+ if (segment === "..") {
1829
+ if (output.length === 0) {
1830
+ throw pptxSecurity(
1831
+ "OPC relationship escapes the package root",
1832
+ "invalid-package",
1833
+ relationshipPart
1834
+ );
1835
+ }
1836
+ output.pop();
1837
+ } else output.push(segment);
1838
+ if (output.length > 256) {
1839
+ throw pptxSecurity("OPC relationship path is too deep", "limit-exceeded", relationshipPart);
1840
+ }
1841
+ }
1842
+ return output.join("/");
1843
+ }
1844
+ function emuNumber(value, partName) {
1845
+ const parsed = Number(value);
1846
+ if (!Number.isSafeInteger(parsed) || Math.abs(parsed) > 9525e6) {
1847
+ throw pptxSecurity(
1848
+ "Presentation geometry is outside supported bounds",
1849
+ "invalid-package",
1850
+ partName
1851
+ );
1852
+ }
1853
+ return parsed / EMU_PER_PIXEL;
1854
+ }
1855
+ function positiveEmu(value, partName) {
1856
+ const parsed = emuNumber(value, partName);
1857
+ if (parsed <= 0) {
1858
+ throw pptxSecurity("Presentation extent must be positive", "invalid-package", partName);
1859
+ }
1860
+ return parsed;
1861
+ }
1862
+ function optionalNumber(value) {
1863
+ if (value === void 0) return void 0;
1864
+ const parsed = Number(value);
1865
+ return Number.isFinite(parsed) ? parsed : void 0;
1866
+ }
1867
+ function optionalInteger(value, partName) {
1868
+ if (value === void 0) return void 0;
1869
+ const parsed = Number(value);
1870
+ if (!Number.isSafeInteger(parsed) || parsed < 0) {
1871
+ throw pptxSecurity("OOXML integer is invalid", "invalid-package", partName);
1872
+ }
1873
+ return parsed;
1874
+ }
1875
+ function optionalPositiveInteger(value, partName) {
1876
+ const parsed = optionalInteger(value, partName);
1877
+ if (parsed === void 0) return void 0;
1878
+ if (parsed <= 0 || parsed > 1024) {
1879
+ throw pptxSecurity("OOXML span is outside supported bounds", "invalid-package", partName);
1880
+ }
1881
+ return parsed;
1882
+ }
1883
+ function booleanAttribute(element, name) {
1884
+ const value = pptxXmlAttribute(element, name);
1885
+ return value === "1" || value === "true" || value === "on";
1886
+ }
1887
+ function consumeElement(context, partName) {
1888
+ context.elements += 1;
1889
+ if (context.elements > context.limits.elements) {
1890
+ throw pptxSecurity("PPTX exceeds its element limit", "limit-exceeded", partName);
1891
+ }
1892
+ }
1893
+ function consumeText(context, characters, partName) {
1894
+ context.textCharacters += characters;
1895
+ if (context.textCharacters > context.limits.textCharacters) {
1896
+ throw pptxSecurity("PPTX exceeds its text character limit", "limit-exceeded", partName);
1897
+ }
1898
+ }
1899
+ function addUnsupported(context, feature, partName, code = "content-preserved-in-source") {
1900
+ context.unsupportedParts.add(partName);
1901
+ context.issues.push({
1902
+ code,
1903
+ severity: code === "content-will-be-discarded" ? "error" : "warning",
1904
+ feature,
1905
+ message: code === "style-approximated" ? `${feature} styling is represented by the closest editable model style` : `${feature} content is retained in the validated source package but is not editable`,
1906
+ parts: [partName]
1907
+ });
1908
+ }
1909
+ function fidelityIssue(feature, parts) {
1910
+ return {
1911
+ code: "content-preserved-in-source",
1912
+ severity: "warning",
1913
+ feature,
1914
+ message: `${feature} content is retained in the validated source package but is not editable`,
1915
+ parts
1916
+ };
1917
+ }
1918
+ function deduplicateIssues(issues) {
1919
+ const result = /* @__PURE__ */ new Map();
1920
+ for (const issue of issues) {
1921
+ const key = `${issue.code}\0${issue.feature}\0${(issue.parts ?? []).join("\0")}`;
1922
+ result.set(key, issue);
1923
+ }
1924
+ return [...result.values()];
1925
+ }
1926
+ function pptxSecurity(message, code, entryName) {
1927
+ return new PresentationSecurityError(
1928
+ entryName ? `${message}: ${entryName}` : message,
1929
+ code,
1930
+ entryName
1931
+ );
1932
+ }
1933
+ function pptxZipFailure(kind, message, entryName) {
1934
+ throw pptxSecurity(
1935
+ message,
1936
+ kind === "limit" ? "limit-exceeded" : kind === "encrypted" ? "encrypted-content" : "invalid-package",
1937
+ entryName
1938
+ );
1939
+ }
1940
+ var IDENTITY_TRANSFORM = { offsetX: 0, offsetY: 0, scaleX: 1, scaleY: 1 };
1941
+ var GEOMETRY_MAP = /* @__PURE__ */ new Map([
1942
+ ["rect", "rect"],
1943
+ ["roundRect", "roundRect"],
1944
+ ["ellipse", "ellipse"],
1945
+ ["triangle", "triangle"],
1946
+ ["rtTriangle", "triangle"],
1947
+ ["rightArrow", "rightArrow"],
1948
+ ["line", "line"]
1949
+ ]);
1950
+ var CHART_ELEMENT_TYPES = /* @__PURE__ */ new Map([
1951
+ ["barChart", "bar"],
1952
+ ["bar3DChart", "bar"],
1953
+ ["lineChart", "line"],
1954
+ ["line3DChart", "line"],
1955
+ ["areaChart", "area"],
1956
+ ["area3DChart", "area"],
1957
+ ["pieChart", "pie"],
1958
+ ["pie3DChart", "pie"],
1959
+ ["doughnutChart", "doughnut"],
1960
+ ["scatterChart", "scatter"],
1961
+ ["bubbleChart", "bubble"],
1962
+ ["radarChart", "radar"]
1963
+ ]);
1964
+ var DEFAULT_THEME_COLORS = /* @__PURE__ */ new Map([
1965
+ ["dk1", "#000000"],
1966
+ ["lt1", "#ffffff"],
1967
+ ["dk2", "#1f497d"],
1968
+ ["lt2", "#eeeceed"],
1969
+ ["accent1", "#4472c4"],
1970
+ ["accent2", "#ed7d31"],
1971
+ ["accent3", "#a5a5a5"],
1972
+ ["accent4", "#ffc000"],
1973
+ ["accent5", "#5b9bd5"],
1974
+ ["accent6", "#70ad47"],
1975
+ ["hlink", "#0563c1"],
1976
+ ["folHlink", "#954f72"],
1977
+ ["tx1", "#000000"],
1978
+ ["bg1", "#ffffff"],
1979
+ ["tx2", "#1f497d"],
1980
+ ["bg2", "#eeeceed"]
1981
+ ]);
1982
+ var KNOWN_RELATIONSHIP_SUFFIXES = /* @__PURE__ */ new Set([
1983
+ "officeDocument",
1984
+ "core-properties",
1985
+ "extended-properties",
1986
+ "custom-properties",
1987
+ "slideMaster",
1988
+ "slideLayout",
1989
+ "slide",
1990
+ "notesMaster",
1991
+ "notesSlide",
1992
+ "theme",
1993
+ "presProps",
1994
+ "viewProps",
1995
+ "tableStyles",
1996
+ "chart",
1997
+ "image",
1998
+ "package",
1999
+ "hyperlink",
2000
+ "handoutMaster",
2001
+ "font",
2002
+ "tags",
2003
+ "customXml",
2004
+ "comments",
2005
+ "commentAuthors",
2006
+ "diagramData",
2007
+ "diagramLayout",
2008
+ "diagramColors",
2009
+ "diagramQuickStyle"
2010
+ ]);
2011
+ var ACTIVE_RELATIONSHIP_SUFFIX = /^(?:vbaProject|activeX|oleObject|audio|video|media|externalLink|attachedTemplate|control|webVideo|linkedData|externalData)$/i;
2012
+ var SOURCE_ONLY_RELATIONSHIP_SUFFIXES = /* @__PURE__ */ new Set([
2013
+ "handoutMaster",
2014
+ "font",
2015
+ "tags",
2016
+ "customXml",
2017
+ "comments",
2018
+ "commentAuthors",
2019
+ "diagramData",
2020
+ "diagramLayout",
2021
+ "diagramColors",
2022
+ "diagramQuickStyle"
2023
+ ]);
2024
+ var ALLOWED_EXACT_RELATIONSHIPS = /* @__PURE__ */ new Set([
2025
+ "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties",
2026
+ "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties",
2027
+ "http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties"
2028
+ ]);
2029
+ var KNOWN_DEFAULT_CONTENT_TYPES = /* @__PURE__ */ new Set([
2030
+ "rels\0application/vnd.openxmlformats-package.relationships+xml",
2031
+ "xml\0application/xml",
2032
+ "png\0image/png",
2033
+ "jpg\0image/jpeg",
2034
+ "jpeg\0image/jpeg",
2035
+ "jpg\0image/jpg",
2036
+ "gif\0image/gif",
2037
+ "webp\0image/webp",
2038
+ "svg\0image/svg+xml",
2039
+ "m4v\0video/mp4",
2040
+ "mp4\0video/mp4",
2041
+ "vml\0application/vnd.openxmlformats-officedocument.vmlDrawing",
2042
+ "xlsx\0application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
2043
+ ]);
2044
+ var KNOWN_PPTX_PART_PATTERNS = [
2045
+ /^\[Content_Types\]\.xml$/,
2046
+ /^_rels\/\.rels$/,
2047
+ /^docProps\/(?:app|core|custom)\.xml$/,
2048
+ /^ppt\/presentation\.xml$/,
2049
+ /^ppt\/(?:presProps|viewProps|tableStyles)\.xml$/,
2050
+ /^ppt\/(?:_rels\/presentation\.xml|slides\/_rels\/slide\d+\.xml|slideMasters\/_rels\/slideMaster\d+\.xml|slideLayouts\/_rels\/slideLayout\d+\.xml|notesSlides\/_rels\/notesSlide\d+\.xml|notesMasters\/_rels\/notesMaster\d+\.xml|charts\/_rels\/chart\d+\.xml)\.rels$/,
2051
+ /^ppt\/(?:slides\/slide\d+|slideMasters\/slideMaster\d+|slideLayouts\/slideLayout\d+|notesSlides\/notesSlide\d+|notesMasters\/notesMaster\d+|charts\/chart\d+|theme\/theme\d+)\.xml$/,
2052
+ /^ppt\/media\/[^/]+\.(?:png|jpe?g|gif|webp)$/i,
2053
+ /^ppt\/embeddings\/[^/]+\.xlsx$/i
2054
+ ];
2055
+
2056
+ // src/presentation-pptx.ts
2057
+ var colorValue2 = colorValue;
2058
+ async function exportPresentationPptx(presentation, options = {}) {
2059
+ const provenance = presentationLossState(presentation);
2060
+ if (provenance) {
2061
+ const [currentModelDigest, actualSourceDigest] = await Promise.all([
2062
+ presentationModelDigest(presentation),
2063
+ sha256Hex(provenance.sourceBytes)
2064
+ ]);
2065
+ const sourceBindingValid = provenance.version === 1 && provenance.mediaType === PPTX_MEDIA_TYPE && provenance.sourceDigest === actualSourceDigest;
2066
+ if (sourceBindingValid && provenance.modelDigest === currentModelDigest) {
2067
+ return FileBlob.fromBytes(provenance.sourceBytes, {
2068
+ name: options.fileName ?? "presentation.pptx",
2069
+ type: PPTX_MEDIA_TYPE
2070
+ });
2071
+ }
2072
+ if (!sourceBindingValid && options.unsupportedContent !== "discard") {
2073
+ throw new PresentationFidelityError(
2074
+ "PPTX source-preservation envelope is invalid or no longer matches its source bytes",
2075
+ [
2076
+ {
2077
+ code: "content-will-be-discarded",
2078
+ severity: "error",
2079
+ feature: "source-only",
2080
+ message: "The retained source package failed its digest or version binding",
2081
+ parts: [...provenance.unsupportedParts]
2082
+ }
2083
+ ]
2084
+ );
2085
+ }
2086
+ }
2087
+ const issues = PresentationFile.fidelityReport(presentation).map((issue) => ({
2088
+ ...issue,
2089
+ severity: "error",
2090
+ code: "content-will-be-discarded"
2091
+ }));
2092
+ if (issues.length > 0 && options.unsupportedContent !== "discard") {
2093
+ throw new PresentationFidelityError(
2094
+ 'PPTX export would discard source-only presentation content; use unsupportedContent: "discard" only when that loss is intended',
2095
+ issues
2096
+ );
2097
+ }
2098
+ const { default: PptxConstructor } = await import("pptxgenjs");
2099
+ const pptx = new PptxConstructor();
2100
+ pptx.defineLayout({
2101
+ name: "OPENGENI_CUSTOM",
2102
+ width: presentation.slideSize.width / 96,
2103
+ height: presentation.slideSize.height / 96
2104
+ });
2105
+ pptx.layout = "OPENGENI_CUSTOM";
2106
+ pptx.author = "OpenGeni";
2107
+ for (const slide of presentation.slides.items) {
2108
+ const target = pptx.addSlide();
2109
+ target.background = { color: pptxColor(slide.background.fill) };
2110
+ for (const element of slide.elements) {
2111
+ if ("geometry" in element && "text" in element) exportShapeToPptx(target, element);
2112
+ else if ("series" in element) exportChartToPptx(target, element);
2113
+ else if ("sourceForPptx" in element) exportImageToPptx(target, element);
2114
+ else if ("rows" in element) exportTableToPptx(target, element);
2115
+ else if ("children" in element) exportGroupToPptx(target, element);
2116
+ }
2117
+ if (slide.notes.toString().length > 0) target.addNotes(slide.notes.toString());
2118
+ }
2119
+ const output = await pptx.write({ outputType: "arraybuffer" });
2120
+ const bytes = output instanceof Uint8Array ? output : output instanceof ArrayBuffer ? new Uint8Array(output) : typeof output === "string" ? new TextEncoder().encode(output) : void 0;
2121
+ if (!bytes) throw new Error("PPTX writer returned an unsupported output type");
2122
+ const canonicalBytes = await canonicalizeGeneratedPptx(bytes);
2123
+ return FileBlob.fromBytes(canonicalBytes, {
2124
+ name: options.fileName ?? "presentation.pptx",
2125
+ type: PPTX_MEDIA_TYPE
2126
+ });
2127
+ }
2128
+ async function canonicalizeGeneratedPptx(bytes) {
2129
+ const { default: JSZip } = await import("./lib-GQLPACFK.js");
2130
+ const source = await JSZip.loadAsync(bytes);
2131
+ const chartIds = Object.keys(source.files).map((name) => /^ppt\/charts\/chart(\d+)\.xml$/.exec(name)?.[1]).filter((value) => value !== void 0).map(Number).sort((left, right) => left - right);
2132
+ const chartIdMap = new Map(chartIds.map((id, index) => [id, index + 1]));
2133
+ const rename = (value) => value.replace(/chart(\d+)\.xml/g, (match, raw) => {
2134
+ const replacement = chartIdMap.get(Number(raw));
2135
+ return replacement === void 0 ? match : `chart${replacement}.xml`;
2136
+ }).replace(/Microsoft_Excel_Worksheet(\d+)\.xlsx/g, (match, raw) => {
2137
+ const replacement = chartIdMap.get(Number(raw));
2138
+ return replacement === void 0 ? match : `Microsoft_Excel_Worksheet${replacement}.xlsx`;
2139
+ });
2140
+ return await canonicalizeGeneratedZip(source, rename, true);
2141
+ }
2142
+ async function canonicalizeGeneratedZip(source, rename, canonicalizeNestedWorkbooks) {
2143
+ const { default: JSZip } = await import("./lib-GQLPACFK.js");
2144
+ const output = new JSZip();
2145
+ const entries = Object.values(source.files).filter((entry) => !entry.dir).sort((left, right) => rename(left.name).localeCompare(rename(right.name)));
2146
+ for (const entry of entries) {
2147
+ let data = await entry.async("uint8array");
2148
+ if (canonicalizeNestedWorkbooks && /^ppt\/embeddings\/[^/]+\.xlsx$/i.test(entry.name)) {
2149
+ data = await canonicalizeGeneratedZip(await JSZip.loadAsync(data), (value) => value, false);
2150
+ } else if (/(?:\.xml|\.rels)$/i.test(entry.name) || entry.name === "[Content_Types].xml") {
2151
+ const xml = new TextDecoder().decode(data);
2152
+ data = new TextEncoder().encode(
2153
+ rename(xml).replace(
2154
+ /(<dcterms:(?:created|modified)\b[^>]*>)[^<]*(<\/dcterms:(?:created|modified)>)/g,
2155
+ "$11980-01-01T00:00:00Z$2"
2156
+ )
2157
+ );
2158
+ }
2159
+ output.file(rename(entry.name), data, {
2160
+ binary: true,
2161
+ createFolders: false,
2162
+ date: DETERMINISTIC_ZIP_DATE
2163
+ });
2164
+ }
2165
+ return await output.generateAsync({
2166
+ type: "uint8array",
2167
+ compression: "DEFLATE",
2168
+ compressionOptions: { level: 9 },
2169
+ platform: "DOS"
2170
+ });
2171
+ }
2172
+ var DETERMINISTIC_ZIP_DATE = /* @__PURE__ */ new Date("1980-01-01T00:00:00.000Z");
2173
+ function exportShapeToPptx(slide, shape, position = shape.position) {
2174
+ const box = pptxPosition(position);
2175
+ const fill = pptxFill(shape.fill);
2176
+ const line = pptxLine(shape.line);
2177
+ const text = shape.text.toString();
2178
+ if (shape.geometry === "line" || text.length === 0) {
2179
+ if (shape.geometry !== "textbox") {
2180
+ slide.addShape(shape.geometry, {
2181
+ ...box,
2182
+ fill,
2183
+ line,
2184
+ rotate: shape.rotation,
2185
+ objectName: shape.name
2186
+ });
2187
+ }
2188
+ }
2189
+ if (shape.geometry === "textbox" || text) {
2190
+ const style = shape.text.style;
2191
+ slide.addText(text, {
2192
+ ...box,
2193
+ margin: 0,
2194
+ breakLine: false,
2195
+ fontFace: style.fontFamily ?? "Aptos",
2196
+ fontSize: (style.fontSize ?? 18) * 0.75,
2197
+ bold: style.bold ?? false,
2198
+ italic: style.italic ?? false,
2199
+ underline: style.underline ? { style: "sng" } : void 0,
2200
+ color: pptxColor(style.color ?? "slate-950"),
2201
+ align: style.alignment ?? "left",
2202
+ valign: style.verticalAlignment ?? "top",
2203
+ shape: shape.geometry === "textbox" || shape.geometry === "line" ? void 0 : shape.geometry,
2204
+ fill: shape.geometry === "line" ? { color: "FFFFFF", transparency: 100 } : fill,
2205
+ line: shape.geometry === "line" ? { color: "FFFFFF", transparency: 100 } : line,
2206
+ rotate: shape.rotation,
2207
+ objectName: shape.name
2208
+ });
2209
+ }
2210
+ }
2211
+ function exportTableToPptx(slide, table, position = table.position) {
2212
+ const rows = table.rows.map(
2213
+ (row) => row.filter((cell) => cell !== null).map((cell) => ({
2214
+ text: cell.text.toString(),
2215
+ options: {
2216
+ fill: pptxFill(cell.fill),
2217
+ color: pptxColor(cell.text.style.color ?? table.textStyle.color ?? "slate-950"),
2218
+ fontFace: cell.text.style.fontFamily ?? table.textStyle.fontFamily ?? "Aptos",
2219
+ fontSize: (cell.text.style.fontSize ?? table.textStyle.fontSize ?? 16) * 0.75,
2220
+ bold: cell.text.style.bold ?? table.textStyle.bold ?? false,
2221
+ italic: cell.text.style.italic ?? table.textStyle.italic ?? false,
2222
+ align: cell.text.style.alignment ?? table.textStyle.alignment ?? "left",
2223
+ valign: cell.text.style.verticalAlignment ?? table.textStyle.verticalAlignment ?? "top",
2224
+ rowspan: cell.rowSpan > 1 ? cell.rowSpan : void 0,
2225
+ colspan: cell.colSpan > 1 ? cell.colSpan : void 0
2226
+ }
2227
+ }))
2228
+ );
2229
+ slide.addTable(rows, {
2230
+ ...pptxPosition(position),
2231
+ border: pptxTableBorder(table.line),
2232
+ fill: pptxFill(table.fill),
2233
+ margin: 2,
2234
+ objectName: table.name,
2235
+ ...table.columnWidths.length > 0 ? { colW: table.columnWidths.map((width) => width / 96) } : {},
2236
+ ...table.rowHeights.length > 0 ? { rowH: table.rowHeights.map((height) => height / 96) } : {}
2237
+ });
2238
+ }
2239
+ function exportGroupToPptx(slide, group, parentTransform = (position) => position) {
2240
+ const transform = (position) => parentTransform(groupChildPosition(group, position));
2241
+ for (const child of group.children) {
2242
+ if ("geometry" in child && "text" in child)
2243
+ exportShapeToPptx(slide, child, transform(child.position));
2244
+ else if ("series" in child) exportChartToPptx(slide, child, transform(child.position));
2245
+ else if ("sourceForPptx" in child) exportImageToPptx(slide, child, transform(child.position));
2246
+ else if ("rows" in child) exportTableToPptx(slide, child, transform(child.position));
2247
+ else if ("children" in child) exportGroupToPptx(slide, child, transform);
2248
+ }
2249
+ }
2250
+ function exportChartToPptx(slide, chart, position = chart.position) {
2251
+ const categories = chart.categories;
2252
+ const data = chart.type === "scatter" || chart.type === "bubble" ? [
2253
+ {
2254
+ name: "X Values",
2255
+ values: chart.series.items[0]?.xValues ?? []
2256
+ },
2257
+ ...chart.series.items.map((series) => ({
2258
+ name: series.name,
2259
+ values: series.values,
2260
+ sizes: series.bubbleSizes.length > 0 ? series.bubbleSizes : void 0
2261
+ }))
2262
+ ] : chart.series.items.map((series) => ({
2263
+ name: series.name,
2264
+ labels: series.categories.length > 0 ? series.categories : categories,
2265
+ values: series.values
2266
+ }));
2267
+ slide.addChart(chart.type, data, {
2268
+ ...pptxPosition(position),
2269
+ showTitle: chart.title.length > 0,
2270
+ title: chart.title,
2271
+ showLegend: chart.hasLegend,
2272
+ legendPos: pptxLegendPosition(chart.legend?.position),
2273
+ showValue: chart.dataLabels?.showValue ?? false,
2274
+ showCategoryName: chart.dataLabels?.showCategoryName ?? false,
2275
+ showPercent: chart.dataLabels?.showPercent ?? false,
2276
+ showCatName: chart.dataLabels?.showCategoryName ?? false,
2277
+ showSerName: chart.dataLabels?.showSeriesName ?? false,
2278
+ catAxisHidden: chart.xAxis?.visible === false,
2279
+ catAxisTitle: chart.xAxis?.title,
2280
+ catAxisMinVal: chart.xAxis?.min,
2281
+ catAxisMaxVal: chart.xAxis?.max,
2282
+ valAxisHidden: chart.yAxis?.visible === false,
2283
+ valAxisTitle: chart.yAxis?.title,
2284
+ valAxisMinVal: chart.yAxis?.min,
2285
+ valAxisMaxVal: chart.yAxis?.max
2286
+ });
2287
+ }
2288
+ function exportImageToPptx(slide, image, position = image.position) {
2289
+ slide.addImage({
2290
+ ...image.sourceForPptx(),
2291
+ ...pptxPosition(position),
2292
+ altText: image.alt,
2293
+ rotate: image.rotation,
2294
+ flipH: image.flipHorizontal,
2295
+ flipV: image.flipVertical,
2296
+ rounding: image.geometry === "ellipse" || image.geometry === "roundRect",
2297
+ objectName: image.name,
2298
+ sizing: {
2299
+ type: image.crop ? "crop" : image.fit,
2300
+ w: position.width / 96,
2301
+ h: position.height / 96
2302
+ }
2303
+ });
2304
+ }
2305
+ function groupChildPosition(group, position) {
2306
+ const scaleX = group.position.width / group.childExtent.width;
2307
+ const scaleY = group.position.height / group.childExtent.height;
2308
+ return {
2309
+ left: group.position.left + (position.left - group.childOffset.left) * scaleX,
2310
+ top: group.position.top + (position.top - group.childOffset.top) * scaleY,
2311
+ width: position.width * scaleX,
2312
+ height: position.height * scaleY
2313
+ };
2314
+ }
2315
+ function pptxLegendPosition(position) {
2316
+ switch (position) {
2317
+ case "left":
2318
+ return "l";
2319
+ case "top":
2320
+ return "t";
2321
+ case "topRight":
2322
+ return "tr";
2323
+ case "right":
2324
+ return "r";
2325
+ default:
2326
+ return "b";
2327
+ }
2328
+ }
2329
+ function pptxPosition(position) {
2330
+ return {
2331
+ x: position.left / 96,
2332
+ y: position.top / 96,
2333
+ w: position.width / 96,
2334
+ h: position.height / 96
2335
+ };
2336
+ }
2337
+ function pptxColor(fill) {
2338
+ const color = colorValue2(fill);
2339
+ return color === "none" ? "FFFFFF" : color.replace(/^#/, "").slice(0, 6).toUpperCase();
2340
+ }
2341
+ function pptxFill(fill) {
2342
+ return colorValue2(fill) === "none" ? { color: "FFFFFF", transparency: 100 } : { color: pptxColor(fill), transparency: 0 };
2343
+ }
2344
+ function pptxLine(line) {
2345
+ const none = line.style === "none" || colorValue2(line.fill ?? "none") === "none" || (line.width ?? 1) === 0;
2346
+ return none ? { color: "FFFFFF", transparency: 100, width: 0 } : {
2347
+ color: pptxColor(line.fill ?? "slate-900"),
2348
+ width: (line.width ?? 1) * 0.75,
2349
+ dashType: line.style
2350
+ };
2351
+ }
2352
+ function pptxTableBorder(line) {
2353
+ const shapeLine = pptxLine(line);
2354
+ return {
2355
+ color: shapeLine.color,
2356
+ transparency: shapeLine.transparency,
2357
+ pt: shapeLine.width,
2358
+ type: shapeLine.dashType
2359
+ };
2360
+ }
2361
+ export {
2362
+ exportPresentationPptx,
2363
+ importPresentationPptx
2364
+ };
2365
+ //# sourceMappingURL=presentation-pptx.js.map