omni-viewer-core 0.1.0

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 (387) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +80 -0
  3. package/THIRD_PARTY_NOTICES.md +49 -0
  4. package/dist/assets/audio-engine/audio_engine.mjs +2 -0
  5. package/dist/assets/audio-engine/audio_engine.wasm +0 -0
  6. package/dist/assets/pdfjs/pdf.worker.min.mjs +21 -0
  7. package/dist/host/index.d.ts +133 -0
  8. package/dist/host/index.js +10 -0
  9. package/dist/i18n/catalog.en.d.ts +1 -0
  10. package/dist/i18n/catalog.en.js +411 -0
  11. package/dist/i18n/catalog.ko.d.ts +1 -0
  12. package/dist/i18n/catalog.ko.js +68 -0
  13. package/dist/i18n/index.d.ts +18 -0
  14. package/dist/i18n/index.js +41 -0
  15. package/dist/licenses/emscripten.txt +101 -0
  16. package/dist/licenses/kissfft.txt +28 -0
  17. package/dist/licenses/pdfjs-dist.txt +177 -0
  18. package/dist/parsers/a2l/index.d.ts +1 -0
  19. package/dist/parsers/a2l/index.js +1 -0
  20. package/dist/parsers/archive/index.d.ts +22 -0
  21. package/dist/parsers/archive/index.js +46 -0
  22. package/dist/parsers/archive/model.d.ts +29 -0
  23. package/dist/parsers/archive/model.js +1 -0
  24. package/dist/parsers/archive/worker.d.ts +21 -0
  25. package/dist/parsers/archive/worker.js +1 -0
  26. package/dist/parsers/arxml/index.d.ts +1 -0
  27. package/dist/parsers/arxml/index.js +1 -0
  28. package/dist/parsers/asc/index.d.ts +1 -0
  29. package/dist/parsers/asc/index.js +1 -0
  30. package/dist/parsers/audio/index.d.ts +9 -0
  31. package/dist/parsers/audio/index.js +46 -0
  32. package/dist/parsers/automotive/index.d.ts +33 -0
  33. package/dist/parsers/automotive/index.js +973 -0
  34. package/dist/parsers/blf/index.d.ts +1 -0
  35. package/dist/parsers/blf/index.js +1 -0
  36. package/dist/parsers/csv/delimiter.d.ts +31 -0
  37. package/dist/parsers/csv/delimiter.js +151 -0
  38. package/dist/parsers/csv/index.d.ts +30 -0
  39. package/dist/parsers/csv/index.js +108 -0
  40. package/dist/parsers/csv/parseText.d.ts +20 -0
  41. package/dist/parsers/csv/parseText.js +108 -0
  42. package/dist/parsers/csv/sort.d.ts +26 -0
  43. package/dist/parsers/csv/sort.js +224 -0
  44. package/dist/parsers/csv/statistics.d.ts +49 -0
  45. package/dist/parsers/csv/statistics.js +150 -0
  46. package/dist/parsers/dbc/index.d.ts +56 -0
  47. package/dist/parsers/dbc/index.js +94 -0
  48. package/dist/parsers/doc-binary/index.d.ts +35 -0
  49. package/dist/parsers/doc-binary/index.js +267 -0
  50. package/dist/parsers/doc-binary/vscode-parser.d.ts +142 -0
  51. package/dist/parsers/doc-binary/vscode-parser.js +3747 -0
  52. package/dist/parsers/excel/index.d.ts +17 -0
  53. package/dist/parsers/excel/index.js +406 -0
  54. package/dist/parsers/excel/model.d.ts +80 -0
  55. package/dist/parsers/excel/model.js +48 -0
  56. package/dist/parsers/excel/serialize.d.ts +3 -0
  57. package/dist/parsers/excel/serialize.js +78 -0
  58. package/dist/parsers/hdf5/index.d.ts +75 -0
  59. package/dist/parsers/hdf5/index.js +658 -0
  60. package/dist/parsers/hdf5/node.d.ts +21 -0
  61. package/dist/parsers/hdf5/node.js +95 -0
  62. package/dist/parsers/json/index.d.ts +15 -0
  63. package/dist/parsers/json/index.js +102 -0
  64. package/dist/parsers/json/model.d.ts +42 -0
  65. package/dist/parsers/json/model.js +140 -0
  66. package/dist/parsers/json/parse.d.ts +22 -0
  67. package/dist/parsers/json/parse.js +279 -0
  68. package/dist/parsers/json/tokenizer.d.ts +17 -0
  69. package/dist/parsers/json/tokenizer.js +175 -0
  70. package/dist/parsers/jsonl/index.d.ts +14 -0
  71. package/dist/parsers/jsonl/index.js +18 -0
  72. package/dist/parsers/jsonl/model.d.ts +23 -0
  73. package/dist/parsers/jsonl/model.js +1 -0
  74. package/dist/parsers/jsonl/parse.d.ts +7 -0
  75. package/dist/parsers/jsonl/parse.js +34 -0
  76. package/dist/parsers/markdown/index.d.ts +37 -0
  77. package/dist/parsers/markdown/index.js +59 -0
  78. package/dist/parsers/mat/index.d.ts +31 -0
  79. package/dist/parsers/mat/index.js +426 -0
  80. package/dist/parsers/mermaid/index.d.ts +5 -0
  81. package/dist/parsers/mermaid/index.js +3 -0
  82. package/dist/parsers/mf4/index.d.ts +1 -0
  83. package/dist/parsers/mf4/index.js +1 -0
  84. package/dist/parsers/parquet/index.d.ts +21 -0
  85. package/dist/parsers/parquet/index.js +68 -0
  86. package/dist/parsers/pcap/index.d.ts +1 -0
  87. package/dist/parsers/pcap/index.js +1 -0
  88. package/dist/parsers/pcapng/index.d.ts +1 -0
  89. package/dist/parsers/pcapng/index.js +1 -0
  90. package/dist/parsers/plantuml/index.d.ts +5 -0
  91. package/dist/parsers/plantuml/index.js +4 -0
  92. package/dist/parsers/ppt-binary/index.d.ts +5 -0
  93. package/dist/parsers/ppt-binary/index.js +62 -0
  94. package/dist/parsers/ppt-binary/pptBinaryBuildContext.d.ts +12 -0
  95. package/dist/parsers/ppt-binary/pptBinaryBuildContext.js +40 -0
  96. package/dist/parsers/ppt-binary/pptBinaryContainer.d.ts +4 -0
  97. package/dist/parsers/ppt-binary/pptBinaryContainer.js +174 -0
  98. package/dist/parsers/ppt-binary/pptBinaryTypes.d.ts +125 -0
  99. package/dist/parsers/ppt-binary/pptBinaryTypes.js +1 -0
  100. package/dist/parsers/ppt-binary/pptSlideLayouts.d.ts +9 -0
  101. package/dist/parsers/ppt-binary/pptSlideLayouts.js +510 -0
  102. package/dist/parsers/ppt-binary/vscode-parser.d.ts +116 -0
  103. package/dist/parsers/ppt-binary/vscode-parser.js +2853 -0
  104. package/dist/parsers/ppt-binary/vscode.d.ts +4 -0
  105. package/dist/parsers/ppt-binary/vscode.js +12 -0
  106. package/dist/parsers/pptx/index.d.ts +15 -0
  107. package/dist/parsers/pptx/index.js +88 -0
  108. package/dist/parsers/pptx/vscode-parser.d.ts +167 -0
  109. package/dist/parsers/pptx/vscode-parser.js +1503 -0
  110. package/dist/parsers/pptx/vscode.d.ts +4 -0
  111. package/dist/parsers/pptx/vscode.js +18 -0
  112. package/dist/parsers/pptx/zip-reader.d.ts +3 -0
  113. package/dist/parsers/pptx/zip-reader.js +20 -0
  114. package/dist/parsers/proto/index.d.ts +90 -0
  115. package/dist/parsers/proto/index.js +197 -0
  116. package/dist/parsers/psd/index.d.ts +16 -0
  117. package/dist/parsers/psd/index.js +10 -0
  118. package/dist/parsers/shapefile/dbf.d.ts +23 -0
  119. package/dist/parsers/shapefile/dbf.js +112 -0
  120. package/dist/parsers/shapefile/index.d.ts +44 -0
  121. package/dist/parsers/shapefile/index.js +119 -0
  122. package/dist/parsers/shapefile/reproject.d.ts +10 -0
  123. package/dist/parsers/shapefile/reproject.js +41 -0
  124. package/dist/parsers/slide-model.d.ts +106 -0
  125. package/dist/parsers/slide-model.js +1 -0
  126. package/dist/parsers/toml/index.d.ts +15 -0
  127. package/dist/parsers/toml/index.js +18 -0
  128. package/dist/parsers/toml/model.d.ts +15 -0
  129. package/dist/parsers/toml/model.js +1 -0
  130. package/dist/parsers/toml/parse.d.ts +16 -0
  131. package/dist/parsers/toml/parse.js +266 -0
  132. package/dist/parsers/types.d.ts +103 -0
  133. package/dist/parsers/types.js +106 -0
  134. package/dist/parsers/video/index.d.ts +6 -0
  135. package/dist/parsers/video/index.js +2 -0
  136. package/dist/parsers/yaml/index.d.ts +16 -0
  137. package/dist/parsers/yaml/index.js +58 -0
  138. package/dist/parsers/yaml/model.d.ts +22 -0
  139. package/dist/parsers/yaml/model.js +1 -0
  140. package/dist/parsers/yaml/self-loading.d.ts +3 -0
  141. package/dist/parsers/yaml/self-loading.js +19 -0
  142. package/dist/parsers/zip-scan.d.ts +8 -0
  143. package/dist/parsers/zip-scan.js +80 -0
  144. package/dist/registry/container.d.ts +17 -0
  145. package/dist/registry/container.js +49 -0
  146. package/dist/registry/index.d.ts +133 -0
  147. package/dist/registry/index.js +348 -0
  148. package/dist/registry/probe.d.ts +3 -0
  149. package/dist/registry/probe.js +177 -0
  150. package/dist/registry/sniff.d.ts +17 -0
  151. package/dist/registry/sniff.js +57 -0
  152. package/dist/styles/archive.css +10 -0
  153. package/dist/styles/audio.css +66 -0
  154. package/dist/styles/automotive.css +1 -0
  155. package/dist/styles/csv.css +262 -0
  156. package/dist/styles/excel.css +212 -0
  157. package/dist/styles/fallback.css +48 -0
  158. package/dist/styles/hdf5.css +3 -0
  159. package/dist/styles/hwp.css +12 -0
  160. package/dist/styles/image.css +115 -0
  161. package/dist/styles/json.css +342 -0
  162. package/dist/styles/jsonl.css +1 -0
  163. package/dist/styles/markdown.css +10 -0
  164. package/dist/styles/mat.css +2 -0
  165. package/dist/styles/mermaid.css +1 -0
  166. package/dist/styles/parquet.css +2 -0
  167. package/dist/styles/pdf.css +551 -0
  168. package/dist/styles/plantuml.css +1 -0
  169. package/dist/styles/ppt.css +1 -0
  170. package/dist/styles/proto.css +2 -0
  171. package/dist/styles/psd.css +1 -0
  172. package/dist/styles/shapefile.css +25 -0
  173. package/dist/styles/toml.css +1 -0
  174. package/dist/styles/video.css +74 -0
  175. package/dist/styles/word.css +685 -0
  176. package/dist/styles/yaml.css +1 -0
  177. package/dist/viewers/a2l/index.d.ts +15 -0
  178. package/dist/viewers/a2l/index.js +5 -0
  179. package/dist/viewers/archive/archive-preview-decoder.d.ts +4 -0
  180. package/dist/viewers/archive/archive-preview-decoder.js +223 -0
  181. package/dist/viewers/archive/controller.d.ts +24 -0
  182. package/dist/viewers/archive/controller.js +18 -0
  183. package/dist/viewers/archive/index.d.ts +25 -0
  184. package/dist/viewers/archive/index.js +230 -0
  185. package/dist/viewers/archive/preview.d.ts +2 -0
  186. package/dist/viewers/archive/preview.js +2 -0
  187. package/dist/viewers/archive/styles.d.ts +1 -0
  188. package/dist/viewers/archive/styles.js +11 -0
  189. package/dist/viewers/arxml/index.d.ts +15 -0
  190. package/dist/viewers/arxml/index.js +5 -0
  191. package/dist/viewers/asc/index.d.ts +15 -0
  192. package/dist/viewers/asc/index.js +5 -0
  193. package/dist/viewers/audio/controller.d.ts +33 -0
  194. package/dist/viewers/audio/controller.js +49 -0
  195. package/dist/viewers/audio/engine.d.ts +44 -0
  196. package/dist/viewers/audio/engine.js +108 -0
  197. package/dist/viewers/audio/index.d.ts +92 -0
  198. package/dist/viewers/audio/index.js +448 -0
  199. package/dist/viewers/audio/self-loading.d.ts +7 -0
  200. package/dist/viewers/audio/self-loading.js +42 -0
  201. package/dist/viewers/audio/styles.d.ts +1 -0
  202. package/dist/viewers/audio/styles.js +73 -0
  203. package/dist/viewers/automotive/index.d.ts +8 -0
  204. package/dist/viewers/automotive/index.js +152 -0
  205. package/dist/viewers/automotive/styles.d.ts +1 -0
  206. package/dist/viewers/automotive/styles.js +1 -0
  207. package/dist/viewers/avro/index.d.ts +15 -0
  208. package/dist/viewers/avro/index.js +5 -0
  209. package/dist/viewers/bag/index.d.ts +15 -0
  210. package/dist/viewers/bag/index.js +5 -0
  211. package/dist/viewers/blf/index.d.ts +15 -0
  212. package/dist/viewers/blf/index.js +5 -0
  213. package/dist/viewers/csv/controller.d.ts +126 -0
  214. package/dist/viewers/csv/controller.js +598 -0
  215. package/dist/viewers/csv/index.d.ts +48 -0
  216. package/dist/viewers/csv/index.js +846 -0
  217. package/dist/viewers/csv/styles.d.ts +1 -0
  218. package/dist/viewers/csv/styles.js +267 -0
  219. package/dist/viewers/db3/index.d.ts +15 -0
  220. package/dist/viewers/db3/index.js +5 -0
  221. package/dist/viewers/dbc/index.d.ts +15 -0
  222. package/dist/viewers/dbc/index.js +5 -0
  223. package/dist/viewers/diagram.d.ts +14 -0
  224. package/dist/viewers/diagram.js +95 -0
  225. package/dist/viewers/excel/controller.d.ts +108 -0
  226. package/dist/viewers/excel/controller.js +548 -0
  227. package/dist/viewers/excel/grid.d.ts +28 -0
  228. package/dist/viewers/excel/grid.js +99 -0
  229. package/dist/viewers/excel/index.d.ts +37 -0
  230. package/dist/viewers/excel/index.js +620 -0
  231. package/dist/viewers/excel/self-loading.d.ts +6 -0
  232. package/dist/viewers/excel/self-loading.js +13 -0
  233. package/dist/viewers/excel/styles.d.ts +1 -0
  234. package/dist/viewers/excel/styles.js +216 -0
  235. package/dist/viewers/fallback/index.d.ts +17 -0
  236. package/dist/viewers/fallback/index.js +130 -0
  237. package/dist/viewers/fallback/styles.d.ts +1 -0
  238. package/dist/viewers/fallback/styles.js +49 -0
  239. package/dist/viewers/hdf5/index.d.ts +19 -0
  240. package/dist/viewers/hdf5/index.js +170 -0
  241. package/dist/viewers/hdf5/styles.d.ts +1 -0
  242. package/dist/viewers/hdf5/styles.js +4 -0
  243. package/dist/viewers/hwp/controller.d.ts +19 -0
  244. package/dist/viewers/hwp/controller.js +22 -0
  245. package/dist/viewers/hwp/index.d.ts +39 -0
  246. package/dist/viewers/hwp/index.js +271 -0
  247. package/dist/viewers/hwp/self-loading.d.ts +2 -0
  248. package/dist/viewers/hwp/self-loading.js +6 -0
  249. package/dist/viewers/hwp/styles.d.ts +1 -0
  250. package/dist/viewers/hwp/styles.js +13 -0
  251. package/dist/viewers/image/controller.d.ts +210 -0
  252. package/dist/viewers/image/controller.js +336 -0
  253. package/dist/viewers/image/decode.d.ts +54 -0
  254. package/dist/viewers/image/decode.js +150 -0
  255. package/dist/viewers/image/index.d.ts +38 -0
  256. package/dist/viewers/image/index.js +749 -0
  257. package/dist/viewers/image/limits.d.ts +18 -0
  258. package/dist/viewers/image/limits.js +20 -0
  259. package/dist/viewers/image/styles.d.ts +1 -0
  260. package/dist/viewers/image/styles.js +119 -0
  261. package/dist/viewers/json/controller.d.ts +89 -0
  262. package/dist/viewers/json/controller.js +411 -0
  263. package/dist/viewers/json/index.d.ts +25 -0
  264. package/dist/viewers/json/index.js +503 -0
  265. package/dist/viewers/json/styles.d.ts +1 -0
  266. package/dist/viewers/json/styles.js +346 -0
  267. package/dist/viewers/json/transforms.d.ts +27 -0
  268. package/dist/viewers/json/transforms.js +253 -0
  269. package/dist/viewers/jsonl/controller.d.ts +48 -0
  270. package/dist/viewers/jsonl/controller.js +136 -0
  271. package/dist/viewers/jsonl/index.d.ts +22 -0
  272. package/dist/viewers/jsonl/index.js +184 -0
  273. package/dist/viewers/jsonl/styles.d.ts +1 -0
  274. package/dist/viewers/jsonl/styles.js +1 -0
  275. package/dist/viewers/markdown/controller.d.ts +50 -0
  276. package/dist/viewers/markdown/controller.js +62 -0
  277. package/dist/viewers/markdown/index.d.ts +64 -0
  278. package/dist/viewers/markdown/index.js +425 -0
  279. package/dist/viewers/markdown/math.d.ts +21 -0
  280. package/dist/viewers/markdown/math.js +100 -0
  281. package/dist/viewers/markdown/self-loading.d.ts +5 -0
  282. package/dist/viewers/markdown/self-loading.js +48 -0
  283. package/dist/viewers/markdown/styles.d.ts +1 -0
  284. package/dist/viewers/markdown/styles.js +11 -0
  285. package/dist/viewers/mat/index.d.ts +19 -0
  286. package/dist/viewers/mat/index.js +113 -0
  287. package/dist/viewers/mat/styles.d.ts +1 -0
  288. package/dist/viewers/mat/styles.js +3 -0
  289. package/dist/viewers/media.d.ts +9 -0
  290. package/dist/viewers/media.js +77 -0
  291. package/dist/viewers/mermaid/index.d.ts +17 -0
  292. package/dist/viewers/mermaid/index.js +6 -0
  293. package/dist/viewers/mermaid/self-loading.d.ts +2 -0
  294. package/dist/viewers/mermaid/self-loading.js +2 -0
  295. package/dist/viewers/mf4/index.d.ts +15 -0
  296. package/dist/viewers/mf4/index.js +5 -0
  297. package/dist/viewers/parquet/controller.d.ts +37 -0
  298. package/dist/viewers/parquet/controller.js +26 -0
  299. package/dist/viewers/parquet/index.d.ts +23 -0
  300. package/dist/viewers/parquet/index.js +134 -0
  301. package/dist/viewers/parquet/styles.d.ts +1 -0
  302. package/dist/viewers/parquet/styles.js +3 -0
  303. package/dist/viewers/pcap/index.d.ts +15 -0
  304. package/dist/viewers/pcap/index.js +5 -0
  305. package/dist/viewers/pcapng/index.d.ts +15 -0
  306. package/dist/viewers/pcapng/index.js +5 -0
  307. package/dist/viewers/pdf/controller.d.ts +120 -0
  308. package/dist/viewers/pdf/controller.js +191 -0
  309. package/dist/viewers/pdf/editing.d.ts +26 -0
  310. package/dist/viewers/pdf/editing.js +265 -0
  311. package/dist/viewers/pdf/index.d.ts +88 -0
  312. package/dist/viewers/pdf/index.js +1400 -0
  313. package/dist/viewers/pdf/self-loading.d.ts +6 -0
  314. package/dist/viewers/pdf/self-loading.js +14 -0
  315. package/dist/viewers/pdf/styles.d.ts +1 -0
  316. package/dist/viewers/pdf/styles.js +555 -0
  317. package/dist/viewers/plantuml/index.d.ts +17 -0
  318. package/dist/viewers/plantuml/index.js +6 -0
  319. package/dist/viewers/plantuml/self-loading.d.ts +2 -0
  320. package/dist/viewers/plantuml/self-loading.js +2 -0
  321. package/dist/viewers/ppt/chart.d.ts +8 -0
  322. package/dist/viewers/ppt/chart.js +83 -0
  323. package/dist/viewers/ppt/controller.d.ts +34 -0
  324. package/dist/viewers/ppt/controller.js +27 -0
  325. package/dist/viewers/ppt/index.d.ts +33 -0
  326. package/dist/viewers/ppt/index.js +101 -0
  327. package/dist/viewers/ppt/render.d.ts +5 -0
  328. package/dist/viewers/ppt/render.js +144 -0
  329. package/dist/viewers/ppt/self-loading.d.ts +9 -0
  330. package/dist/viewers/ppt/self-loading.js +4 -0
  331. package/dist/viewers/ppt/styles.d.ts +1 -0
  332. package/dist/viewers/ppt/styles.js +1 -0
  333. package/dist/viewers/proto/index.d.ts +17 -0
  334. package/dist/viewers/proto/index.js +170 -0
  335. package/dist/viewers/proto/styles.d.ts +1 -0
  336. package/dist/viewers/proto/styles.js +2 -0
  337. package/dist/viewers/psd/index.d.ts +27 -0
  338. package/dist/viewers/psd/index.js +39 -0
  339. package/dist/viewers/psd/self-loading.d.ts +10 -0
  340. package/dist/viewers/psd/self-loading.js +20 -0
  341. package/dist/viewers/psd/styles.d.ts +1 -0
  342. package/dist/viewers/psd/styles.js +1 -0
  343. package/dist/viewers/reqif/index.d.ts +15 -0
  344. package/dist/viewers/reqif/index.js +5 -0
  345. package/dist/viewers/shapefile/index.d.ts +35 -0
  346. package/dist/viewers/shapefile/index.js +336 -0
  347. package/dist/viewers/shapefile/self-loading.d.ts +7 -0
  348. package/dist/viewers/shapefile/self-loading.js +32 -0
  349. package/dist/viewers/shapefile/styles.d.ts +1 -0
  350. package/dist/viewers/shapefile/styles.js +28 -0
  351. package/dist/viewers/stp/index.d.ts +15 -0
  352. package/dist/viewers/stp/index.js +5 -0
  353. package/dist/viewers/structured-styles.d.ts +1 -0
  354. package/dist/viewers/structured-styles.js +1 -0
  355. package/dist/viewers/structured.d.ts +36 -0
  356. package/dist/viewers/structured.js +138 -0
  357. package/dist/viewers/toml/controller.d.ts +38 -0
  358. package/dist/viewers/toml/controller.js +61 -0
  359. package/dist/viewers/toml/index.d.ts +18 -0
  360. package/dist/viewers/toml/index.js +34 -0
  361. package/dist/viewers/types.d.ts +36 -0
  362. package/dist/viewers/types.js +9 -0
  363. package/dist/viewers/video/controller.d.ts +29 -0
  364. package/dist/viewers/video/controller.js +54 -0
  365. package/dist/viewers/video/index.d.ts +18 -0
  366. package/dist/viewers/video/index.js +305 -0
  367. package/dist/viewers/video/styles.d.ts +1 -0
  368. package/dist/viewers/video/styles.js +79 -0
  369. package/dist/viewers/word/controller.d.ts +19 -0
  370. package/dist/viewers/word/controller.js +22 -0
  371. package/dist/viewers/word/docx-preprocess.d.ts +63 -0
  372. package/dist/viewers/word/docx-preprocess.js +136 -0
  373. package/dist/viewers/word/index.d.ts +32 -0
  374. package/dist/viewers/word/index.js +187 -0
  375. package/dist/viewers/word/normalize-docx.d.ts +2 -0
  376. package/dist/viewers/word/normalize-docx.js +50 -0
  377. package/dist/viewers/word/paginate.d.ts +1 -0
  378. package/dist/viewers/word/paginate.js +1035 -0
  379. package/dist/viewers/word/self-loading.d.ts +2 -0
  380. package/dist/viewers/word/self-loading.js +7 -0
  381. package/dist/viewers/word/styles.d.ts +1 -0
  382. package/dist/viewers/word/styles.js +686 -0
  383. package/dist/viewers/yaml/controller.d.ts +42 -0
  384. package/dist/viewers/yaml/controller.js +41 -0
  385. package/dist/viewers/yaml/index.d.ts +21 -0
  386. package/dist/viewers/yaml/index.js +20 -0
  387. package/package.json +375 -0
@@ -0,0 +1,1503 @@
1
+ // @ts-nocheck -- upstream VS Code parser predates exactOptionalPropertyTypes.
2
+ import JSZip from 'jszip';
3
+ // Browser-safe subset of node:path used by the original VS Code parser.
4
+ const normalizePath = (value) => {
5
+ const output = [];
6
+ for (const part of value.split('/')) {
7
+ if (!part || part === '.')
8
+ continue;
9
+ if (part === '..')
10
+ output.pop();
11
+ else
12
+ output.push(part);
13
+ }
14
+ return output.join('/');
15
+ };
16
+ const dirname = (value) => value.includes('/') ? value.slice(0, value.lastIndexOf('/')) : '.';
17
+ const extname = (value) => /(^|\/)([^/]*?)(\.[^/.]+)$/.exec(value)?.[3] ?? '';
18
+ const basename = (value, suffix = '') => {
19
+ const name = value.slice(value.lastIndexOf('/') + 1);
20
+ return suffix && name.endsWith(suffix) ? name.slice(0, -suffix.length) : name;
21
+ };
22
+ const joinPath = (...parts) => normalizePath(parts.join('/'));
23
+ const path = { extname, posix: { dirname, basename, join: joinPath, normalize: normalizePath } };
24
+ const ZERO_TX = {
25
+ offX: 0,
26
+ offY: 0,
27
+ scaleX: 1,
28
+ scaleY: 1,
29
+ rotDeg: 0
30
+ };
31
+ export class PptxXmlParser {
32
+ static async parse(input) {
33
+ const buffer = input;
34
+ const zip = await JSZip.loadAsync(buffer);
35
+ const size = await this.getSlideSize(zip);
36
+ const slidePaths = await this.getOrderedSlidePaths(zip);
37
+ const slides = [];
38
+ for (let i = 0; i < slidePaths.length; i++) {
39
+ const parsed = await this.parseSingleSlide(zip, slidePaths[i], i + 1, size);
40
+ slides.push(parsed);
41
+ }
42
+ return {
43
+ slides,
44
+ totalSlides: slides.length
45
+ };
46
+ }
47
+ static async parseSingleSlide(zip, slidePath, slideNumber, size) {
48
+ const slideXml = await this.readZipText(zip, slidePath);
49
+ const slideRels = await this.getRelationships(zip, slidePath);
50
+ const layoutPath = slideRels.find((r) => r.type.includes('/slideLayout'))?.target;
51
+ const layoutXml = layoutPath ? await this.readZipText(zip, layoutPath) : '';
52
+ const layoutRels = layoutPath ? await this.getRelationships(zip, layoutPath) : [];
53
+ const masterPath = layoutRels.find((r) => r.type.includes('/slideMaster'))?.target;
54
+ const masterXml = masterPath ? await this.readZipText(zip, masterPath) : '';
55
+ const masterRels = masterPath ? await this.getRelationships(zip, masterPath) : [];
56
+ const themePath = masterRels.find((r) => r.type.includes('/theme'))?.target;
57
+ const themeXml = themePath ? await this.readZipText(zip, themePath) : '';
58
+ const theme = this.parseTheme(themeXml);
59
+ const colorCtx = this.buildColorContext(theme, masterXml, layoutXml, slideXml);
60
+ const backgroundColor = this.extractBackgroundColor(slideXml, colorCtx)
61
+ || this.extractBackgroundColor(layoutXml, colorCtx)
62
+ || this.extractBackgroundColor(masterXml, colorCtx)
63
+ || '#ffffff';
64
+ const masterElements = await this.extractElementsFromPart(zip, masterXml, masterRels, colorCtx, 1);
65
+ const layoutElements = await this.extractElementsFromPart(zip, layoutXml, layoutRels, colorCtx, 2);
66
+ const slideElements = await this.extractElementsFromPart(zip, slideXml, slideRels, colorCtx, 3);
67
+ const merged = this.mergeWithPlaceholderInheritance([
68
+ ...masterElements,
69
+ ...layoutElements,
70
+ ...slideElements
71
+ ]);
72
+ return {
73
+ slideNumber,
74
+ widthPx: size.widthPx,
75
+ heightPx: size.heightPx,
76
+ backgroundColor,
77
+ elements: merged
78
+ };
79
+ }
80
+ static mergeWithPlaceholderInheritance(elements) {
81
+ const placeholders = new Map();
82
+ const others = [];
83
+ const sorted = [...elements].sort((a, b) => {
84
+ if (a.sourcePriority !== b.sourcePriority) {
85
+ return a.sourcePriority - b.sourcePriority;
86
+ }
87
+ return a.zIndex - b.zIndex;
88
+ });
89
+ for (const element of sorted) {
90
+ if (!element.placeholderKey) {
91
+ others.push(element);
92
+ continue;
93
+ }
94
+ const prev = placeholders.get(element.placeholderKey);
95
+ if (!prev) {
96
+ placeholders.set(element.placeholderKey, element);
97
+ continue;
98
+ }
99
+ placeholders.set(element.placeholderKey, this.mergePlaceholderElement(prev, element));
100
+ }
101
+ const merged = [...others, ...Array.from(placeholders.values())]
102
+ .filter((element) => !element.hiddenPromptText);
103
+ merged.sort((a, b) => {
104
+ if (a.sourcePriority !== b.sourcePriority) {
105
+ return a.sourcePriority - b.sourcePriority;
106
+ }
107
+ return a.zIndex - b.zIndex;
108
+ });
109
+ return merged.map((el, idx) => ({ ...el, zIndex: idx }));
110
+ }
111
+ static mergePlaceholderElement(base, incoming) {
112
+ const incomingHasGeometry = this.hasValidGeometry(incoming);
113
+ const baseHasGeometry = this.hasValidGeometry(base);
114
+ const mergedParagraphs = incoming.paragraphs && incoming.paragraphs.length > 0
115
+ ? this.mergeParagraphStyles(base.paragraphs, incoming.paragraphs)
116
+ : base.paragraphs;
117
+ const incomingHasVisibleParagraphs = !!(incoming.paragraphs && incoming.paragraphs.length > 0 && !incoming.hiddenPromptText);
118
+ return {
119
+ ...base,
120
+ ...incoming,
121
+ x: incomingHasGeometry ? incoming.x : base.x,
122
+ y: incomingHasGeometry ? incoming.y : base.y,
123
+ width: incomingHasGeometry ? incoming.width : base.width,
124
+ height: incomingHasGeometry ? incoming.height : base.height,
125
+ rotateDeg: incomingHasGeometry ? incoming.rotateDeg : base.rotateDeg,
126
+ hasGeometry: incomingHasGeometry || baseHasGeometry || !!incoming.hasGeometry || !!base.hasGeometry,
127
+ paragraphs: mergedParagraphs,
128
+ src: incoming.src || base.src,
129
+ tableRows: incoming.tableRows && incoming.tableRows.length > 0 ? incoming.tableRows : base.tableRows,
130
+ chartKind: incoming.chartKind || base.chartKind,
131
+ chartTitle: incoming.chartTitle || base.chartTitle,
132
+ fillColor: incoming.fillColor || base.fillColor,
133
+ borderColor: incoming.borderColor || base.borderColor,
134
+ customSvgPath: incoming.customSvgPath || base.customSvgPath,
135
+ isTitle: incoming.isTitle || base.isTitle,
136
+ hiddenPromptText: incomingHasVisibleParagraphs ? false : !!(incoming.hiddenPromptText ?? base.hiddenPromptText)
137
+ };
138
+ }
139
+ static hasValidGeometry(el) {
140
+ return Number.isFinite(el.width) && Number.isFinite(el.height) && el.width > 0 && el.height > 0;
141
+ }
142
+ static mergeParagraphStyles(baseParagraphs, incomingParagraphs) {
143
+ if (!incomingParagraphs || incomingParagraphs.length === 0) {
144
+ return baseParagraphs;
145
+ }
146
+ if (!baseParagraphs || baseParagraphs.length === 0) {
147
+ return incomingParagraphs;
148
+ }
149
+ return incomingParagraphs.map((paragraph, index) => {
150
+ const fallback = baseParagraphs[index]
151
+ || baseParagraphs.find((candidate) => candidate.level === paragraph.level)
152
+ || baseParagraphs[0];
153
+ if (!fallback) {
154
+ return paragraph;
155
+ }
156
+ const incomingRuns = Array.isArray(paragraph.runs) ? paragraph.runs : [];
157
+ const fallbackRuns = Array.isArray(fallback.runs) ? fallback.runs : [];
158
+ const mergedRuns = incomingRuns.length > 0
159
+ ? incomingRuns.map((run, runIndex) => {
160
+ const fallbackRun = fallbackRuns[runIndex] || fallbackRuns[0];
161
+ return {
162
+ ...fallbackRun,
163
+ ...run,
164
+ fontSizePx: run.fontSizePx || fallbackRun?.fontSizePx,
165
+ bold: run.bold ?? fallbackRun?.bold,
166
+ italic: run.italic ?? fallbackRun?.italic,
167
+ color: run.color || fallbackRun?.color
168
+ };
169
+ })
170
+ : fallbackRuns;
171
+ return {
172
+ ...fallback,
173
+ ...paragraph,
174
+ text: paragraph.text,
175
+ level: Number.isFinite(paragraph.level) ? paragraph.level : fallback.level,
176
+ bullet: paragraph.bullet ?? fallback.bullet,
177
+ align: paragraph.align || fallback.align,
178
+ fontSizePx: paragraph.fontSizePx || fallback.fontSizePx,
179
+ bold: paragraph.bold ?? fallback.bold,
180
+ italic: paragraph.italic ?? fallback.italic,
181
+ color: paragraph.color || fallback.color,
182
+ runs: mergedRuns.length > 0 ? mergedRuns : undefined
183
+ };
184
+ });
185
+ }
186
+ static async extractElementsFromPart(zip, partXml, rels, colors, sourcePriority) {
187
+ if (!partXml) {
188
+ return [];
189
+ }
190
+ const tree = this.extractTagBlock(partXml, 'p:spTree');
191
+ if (!tree) {
192
+ return [];
193
+ }
194
+ const result = [];
195
+ await this.collectBlocks(zip, tree, rels, colors, sourcePriority, ZERO_TX, result, { value: 0 });
196
+ return result;
197
+ }
198
+ static async collectBlocks(zip, xml, rels, colors, sourcePriority, parentTx, out, zCounter) {
199
+ const tagNames = ['p:sp', 'p:pic', 'p:graphicFrame', 'p:grpSp', 'p:cxnSp'];
200
+ let cursor = 0;
201
+ while (cursor < xml.length) {
202
+ let nextIdx = -1;
203
+ let foundTag = '';
204
+ for (const tag of tagNames) {
205
+ const idx = this.findNextTagIndex(xml, tag, cursor);
206
+ if (idx !== -1 && (nextIdx === -1 || idx < nextIdx)) {
207
+ nextIdx = idx;
208
+ foundTag = tag;
209
+ }
210
+ }
211
+ if (nextIdx === -1) {
212
+ break;
213
+ }
214
+ const block = this.extractBalancedTag(xml, foundTag, nextIdx);
215
+ if (!block) {
216
+ cursor = nextIdx + foundTag.length;
217
+ continue;
218
+ }
219
+ if (foundTag === 'p:grpSp') {
220
+ const grpTx = this.combineTransforms(parentTx, this.parseGroupTransform(block.content));
221
+ await this.collectBlocks(zip, block.innerContent, rels, colors, sourcePriority, grpTx, out, zCounter);
222
+ }
223
+ else if (foundTag === 'p:sp' || foundTag === 'p:cxnSp') {
224
+ const element = await this.parseShapeBlock(zip, block.content, rels, colors, sourcePriority, parentTx, zCounter.value);
225
+ if (element) {
226
+ out.push(element);
227
+ zCounter.value += 1;
228
+ }
229
+ }
230
+ else if (foundTag === 'p:pic') {
231
+ const element = await this.parsePictureBlock(zip, block.content, rels, sourcePriority, parentTx, zCounter.value);
232
+ if (element) {
233
+ out.push(element);
234
+ zCounter.value += 1;
235
+ }
236
+ }
237
+ else if (foundTag === 'p:graphicFrame') {
238
+ const element = await this.parseGraphicFrameBlock(zip, block.content, rels, colors, sourcePriority, parentTx, zCounter.value);
239
+ if (element) {
240
+ out.push(element);
241
+ zCounter.value += 1;
242
+ }
243
+ }
244
+ cursor = block.end;
245
+ }
246
+ }
247
+ static findNextTagIndex(xml, tag, from) {
248
+ const escaped = tag.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
249
+ const re = new RegExp(`<${escaped}(?=[\\s>/])`, 'g');
250
+ re.lastIndex = from;
251
+ const m = re.exec(xml);
252
+ return m ? m.index : -1;
253
+ }
254
+ static async parseShapeBlock(zip, shapeXml, rels, colors, sourcePriority, parentTx, zIndex) {
255
+ const placeholderType = this.getPlaceholderType(shapeXml);
256
+ // Footer/date/slide-number placeholders in master/layout should not render unless slide overrides them.
257
+ if (sourcePriority < 3 && (placeholderType === 'dt' || placeholderType === 'ftr' || placeholderType === 'sldnum')) {
258
+ return null;
259
+ }
260
+ const localGeom = this.parseGeometry(shapeXml);
261
+ const geom = localGeom ? this.applyTransform(localGeom, parentTx) : null;
262
+ const placeholderKey = this.getPlaceholderKey(shapeXml);
263
+ const isTitle = this.isTitleShape(shapeXml);
264
+ const paragraphs = this.extractTextParagraphs(shapeXml, colors);
265
+ if (paragraphs.length > 0 && sourcePriority < 3) {
266
+ const hasOnlyPromptText = paragraphs.every((p) => this.isPlaceholderPromptText(p.text));
267
+ if (hasOnlyPromptText) {
268
+ if (placeholderKey && geom) {
269
+ return {
270
+ type: 'text',
271
+ x: geom.x,
272
+ y: geom.y,
273
+ width: geom.width,
274
+ height: geom.height,
275
+ rotateDeg: geom.rotateDeg,
276
+ zIndex,
277
+ sourcePriority,
278
+ placeholderKey,
279
+ hasGeometry: true,
280
+ paragraphs,
281
+ hiddenPromptText: true
282
+ };
283
+ }
284
+ return null;
285
+ }
286
+ }
287
+ const fillColor = this.extractFillColor(shapeXml, colors);
288
+ const borderColor = this.extractLineColor(shapeXml, colors);
289
+ const presetGeom = shapeXml.match(/<a:prstGeom\b[^>]*prst="([^"]+)"/)?.[1];
290
+ const customSvgPath = geom ? this.parseCustomGeometryPath(shapeXml, geom.width, geom.height) : undefined;
291
+ // Connector line arrow endpoints.
292
+ const headEndType = shapeXml.match(/<a:headEnd\b[^>]*type="([^"]+)"/)?.[1];
293
+ const tailEndType = shapeXml.match(/<a:tailEnd\b[^>]*type="([^"]+)"/)?.[1];
294
+ if (paragraphs.length > 0) {
295
+ return {
296
+ type: 'text',
297
+ x: geom ? geom.x : 0,
298
+ y: geom ? geom.y : 0,
299
+ width: geom ? geom.width : 0,
300
+ height: geom ? geom.height : 0,
301
+ rotateDeg: geom?.rotateDeg,
302
+ zIndex,
303
+ sourcePriority,
304
+ placeholderKey,
305
+ isTitle,
306
+ paragraphs,
307
+ fillColor,
308
+ borderColor,
309
+ customSvgPath,
310
+ presetGeom,
311
+ hasGeometry: !!geom
312
+ };
313
+ }
314
+ const blipEmbedId = shapeXml.match(/<a:blip[^>]*r:embed="([^"]+)"/)?.[1];
315
+ if (blipEmbedId && geom) {
316
+ const target = rels.find((r) => r.id === blipEmbedId)?.target;
317
+ if (target) {
318
+ const selectedTarget = this.resolveImageTarget(zip, target);
319
+ const media = zip.file(selectedTarget);
320
+ if (media) {
321
+ const base64 = await media.async('base64');
322
+ const mime = this.getMimeTypeByExtension(selectedTarget);
323
+ return {
324
+ type: 'image',
325
+ x: geom.x,
326
+ y: geom.y,
327
+ width: geom.width,
328
+ height: geom.height,
329
+ rotateDeg: geom.rotateDeg,
330
+ zIndex,
331
+ sourcePriority,
332
+ placeholderKey,
333
+ src: `data:${mime};base64,${base64}`,
334
+ hasGeometry: true
335
+ };
336
+ }
337
+ }
338
+ }
339
+ // Keep non-text shape so layout boxes/background elements are still visible.
340
+ if (fillColor || borderColor || customSvgPath) {
341
+ return {
342
+ type: 'shape',
343
+ x: geom ? geom.x : 0,
344
+ y: geom ? geom.y : 0,
345
+ width: geom ? geom.width : 0,
346
+ height: geom ? geom.height : 0,
347
+ rotateDeg: geom?.rotateDeg,
348
+ zIndex,
349
+ sourcePriority,
350
+ placeholderKey,
351
+ fillColor,
352
+ borderColor,
353
+ customSvgPath,
354
+ presetGeom,
355
+ headEnd: headEndType,
356
+ tailEnd: tailEndType,
357
+ flipH: geom?.flipH,
358
+ flipV: geom?.flipV,
359
+ hasGeometry: !!geom
360
+ };
361
+ }
362
+ return null;
363
+ }
364
+ static async parsePictureBlock(zip, picXml, rels, sourcePriority, parentTx, zIndex) {
365
+ const localGeom = this.parseGeometry(picXml);
366
+ const geom = localGeom ? this.applyTransform(localGeom, parentTx) : null;
367
+ const placeholderKey = this.getPlaceholderKey(picXml);
368
+ const picName = picXml.match(/<p:cNvPr[^>]*name="([^"]+)"/)?.[1] || '';
369
+ const picDescr = picXml.match(/<p:cNvPr[^>]*descr="([^"]+)"/)?.[1] || '';
370
+ if (sourcePriority < 3 && !placeholderKey && /placeholder/i.test(picName)) {
371
+ return null;
372
+ }
373
+ const embedId = picXml.match(/<a:blip[^>]*r:embed="([^"]+)"/)?.[1];
374
+ if (!embedId) {
375
+ if (!geom && !placeholderKey) {
376
+ return null;
377
+ }
378
+ return {
379
+ type: 'shape',
380
+ x: geom ? geom.x : 0,
381
+ y: geom ? geom.y : 0,
382
+ width: geom ? geom.width : 0,
383
+ height: geom ? geom.height : 0,
384
+ rotateDeg: geom?.rotateDeg,
385
+ zIndex,
386
+ sourcePriority,
387
+ placeholderKey,
388
+ hasGeometry: !!geom
389
+ };
390
+ }
391
+ const target = rels.find((r) => r.id === embedId)?.target;
392
+ if (!target) {
393
+ return geom || placeholderKey ? {
394
+ type: 'shape',
395
+ x: geom ? geom.x : 0,
396
+ y: geom ? geom.y : 0,
397
+ width: geom ? geom.width : 0,
398
+ height: geom ? geom.height : 0,
399
+ rotateDeg: geom?.rotateDeg,
400
+ zIndex,
401
+ sourcePriority,
402
+ placeholderKey,
403
+ hasGeometry: !!geom
404
+ } : null;
405
+ }
406
+ const selectedTarget = this.resolveImageTarget(zip, target);
407
+ const media = zip.file(selectedTarget);
408
+ if (!media) {
409
+ return null;
410
+ }
411
+ const base64 = await media.async('base64');
412
+ const mime = this.getMimeTypeByExtension(selectedTarget);
413
+ const sourceExt = path.extname(target).toLowerCase();
414
+ const selectedExt = path.extname(selectedTarget).toLowerCase();
415
+ const vectorFallback = (sourceExt === '.emf' || sourceExt === '.wmf')
416
+ && selectedTarget !== target
417
+ && (selectedExt === '.png' || selectedExt === '.jpg' || selectedExt === '.jpeg' || selectedExt === '.webp' || selectedExt === '.gif');
418
+ // Parse <a:srcRect l="" t="" r="" b=""/> — values are in 1/1000th percent.
419
+ const srcRectTag = picXml.match(/<a:srcRect\b[^>]*\/?>/)?.[0] || '';
420
+ let srcRect;
421
+ if (srcRectTag) {
422
+ const toPct = (name) => {
423
+ const v = Number(this.getAttr(srcRectTag, name) || 0);
424
+ return Number.isFinite(v) ? v / 1000 : 0;
425
+ };
426
+ const l = toPct('l');
427
+ const t = toPct('t');
428
+ const r = toPct('r');
429
+ const b = toPct('b');
430
+ if (l || t || r || b) {
431
+ srcRect = { l, t, r, b };
432
+ }
433
+ }
434
+ return {
435
+ type: 'image',
436
+ x: geom ? geom.x : 0,
437
+ y: geom ? geom.y : 0,
438
+ width: geom ? geom.width : 0,
439
+ height: geom ? geom.height : 0,
440
+ rotateDeg: geom?.rotateDeg,
441
+ zIndex,
442
+ sourcePriority,
443
+ placeholderKey,
444
+ src: `data:${mime};base64,${base64}`,
445
+ srcRect,
446
+ vectorFallback,
447
+ hasGeometry: !!geom
448
+ };
449
+ }
450
+ static async parseGraphicFrameBlock(zip, frameXml, rels, colors, sourcePriority, parentTx, zIndex) {
451
+ const localGeom = this.parseGeometry(frameXml);
452
+ if (!localGeom) {
453
+ return null;
454
+ }
455
+ const geom = this.applyTransform(localGeom, parentTx);
456
+ const uri = frameXml.match(/<a:graphicData[^>]*uri="([^"]+)"/)?.[1] || '';
457
+ if (uri.includes('/table')) {
458
+ const tableRows = this.extractTableRows(frameXml);
459
+ if (tableRows.length === 0) {
460
+ return null;
461
+ }
462
+ return {
463
+ type: 'table',
464
+ x: geom.x,
465
+ y: geom.y,
466
+ width: geom.width,
467
+ height: geom.height,
468
+ rotateDeg: geom.rotateDeg,
469
+ zIndex,
470
+ sourcePriority,
471
+ placeholderKey: this.getPlaceholderKey(frameXml),
472
+ tableRows
473
+ };
474
+ }
475
+ if (uri.includes('/chart')) {
476
+ const chartRelId = frameXml.match(/<c:chart[^>]*r:id="([^"]+)"/)?.[1] || '';
477
+ let chartTitle = 'Chart';
478
+ let chartData;
479
+ if (chartRelId) {
480
+ const chartTarget = rels.find((r) => r.id === chartRelId)?.target;
481
+ if (chartTarget) {
482
+ const chartXml = await this.readZipText(zip, chartTarget);
483
+ const chartText = chartXml.match(/<c:title[\s\S]*?<a:t(?=[\s>])[^>]*>([\s\S]*?)<\/a:t>/)?.[1];
484
+ if (chartText) {
485
+ chartTitle = this.decodeXmlEntities(chartText);
486
+ }
487
+ chartData = this.parseChartData(chartXml, colors);
488
+ }
489
+ }
490
+ return {
491
+ type: 'chart',
492
+ x: geom.x,
493
+ y: geom.y,
494
+ width: geom.width,
495
+ height: geom.height,
496
+ rotateDeg: geom.rotateDeg,
497
+ zIndex,
498
+ sourcePriority,
499
+ placeholderKey: this.getPlaceholderKey(frameXml),
500
+ chartKind: 'chart',
501
+ chartTitle,
502
+ chartData
503
+ };
504
+ }
505
+ if (uri.includes('/diagram')) {
506
+ return {
507
+ type: 'chart',
508
+ x: geom.x,
509
+ y: geom.y,
510
+ width: geom.width,
511
+ height: geom.height,
512
+ rotateDeg: geom.rotateDeg,
513
+ zIndex,
514
+ sourcePriority,
515
+ placeholderKey: this.getPlaceholderKey(frameXml),
516
+ chartKind: 'smartart',
517
+ chartTitle: 'SmartArt'
518
+ };
519
+ }
520
+ // Other embedded objects fallback as shape frame
521
+ return {
522
+ type: 'shape',
523
+ x: geom.x,
524
+ y: geom.y,
525
+ width: geom.width,
526
+ height: geom.height,
527
+ rotateDeg: geom.rotateDeg,
528
+ zIndex,
529
+ sourcePriority,
530
+ placeholderKey: this.getPlaceholderKey(frameXml),
531
+ fillColor: '#f7f7f7',
532
+ borderColor: '#c9c9c9'
533
+ };
534
+ }
535
+ static extractTableRows(xml) {
536
+ const rows = [];
537
+ const trMatches = xml.match(/<a:tr\b[\s\S]*?<\/a:tr>/g) || [];
538
+ for (const tr of trMatches) {
539
+ const row = [];
540
+ const tcMatches = tr.match(/<a:tc\b[\s\S]*?<\/a:tc>/g) || [];
541
+ for (const tc of tcMatches) {
542
+ const texts = [];
543
+ const tMatches = tc.match(/<a:t(?=[\s>])[^>]*>([\s\S]*?)<\/a:t>/g) || [];
544
+ for (const t of tMatches) {
545
+ const value = t.match(/<a:t(?=[\s>])[^>]*>([\s\S]*?)<\/a:t>/)?.[1] || '';
546
+ if (value)
547
+ texts.push(this.decodeXmlEntities(value));
548
+ }
549
+ row.push(texts.join(' ').trim());
550
+ }
551
+ if (row.length > 0)
552
+ rows.push(row);
553
+ }
554
+ return rows;
555
+ }
556
+ static extractTextParagraphs(shapeXml, colors) {
557
+ const paragraphs = [];
558
+ const txBody = this.extractTagBlock(shapeXml, 'p:txBody') || shapeXml;
559
+ const lstStyle = this.extractTagBlock(txBody, 'a:lstStyle') || '';
560
+ const bodyPr = this.extractTagBlock(txBody, 'a:bodyPr') || '';
561
+ const pMatches = txBody.match(/<a:p\b[\s\S]*?<\/a:p>/g) || [];
562
+ for (const pXml of pMatches) {
563
+ const textParts = [];
564
+ const runs = [];
565
+ const runMatches = pXml.match(/<a:r\b[\s\S]*?<\/a:r>|<a:fld\b[\s\S]*?<\/a:fld>|<a:t(?=[\s>])[^>]*>[\s\S]*?<\/a:t>/g) || [];
566
+ let lastRun = '';
567
+ for (const run of runMatches) {
568
+ if (run.startsWith('<a:r') || run.startsWith('<a:fld')) {
569
+ lastRun = run;
570
+ const t = run.match(/<a:t(?=[\s>])[^>]*>([\s\S]*?)<\/a:t>/)?.[1];
571
+ if (t) {
572
+ const text = this.decodeXmlEntities(t);
573
+ textParts.push(text);
574
+ const runRPr = run.match(/<a:rPr[^>]*\/?>/)?.[0] || '';
575
+ const runSz = Number(this.getAttr(runRPr, 'sz') || 0);
576
+ runs.push({
577
+ text,
578
+ fontSizePx: runSz > 0 ? Math.round((runSz / 100) * 1.333) : undefined,
579
+ bold: this.parseOptionalBoolAttr(runRPr, 'b'),
580
+ italic: this.parseOptionalBoolAttr(runRPr, 'i'),
581
+ color: this.extractColorFromXml(runRPr + run, colors)
582
+ });
583
+ }
584
+ }
585
+ else {
586
+ const t = run.match(/<a:t(?=[\s>])[^>]*>([\s\S]*?)<\/a:t>/)?.[1];
587
+ if (t) {
588
+ const text = this.decodeXmlEntities(t);
589
+ textParts.push(text);
590
+ runs.push({ text });
591
+ }
592
+ }
593
+ }
594
+ const text = textParts.join('').trim();
595
+ if (!text)
596
+ continue;
597
+ const inlinePPr = pXml.match(/<a:pPr[^>]*\/?>/)?.[0] || '';
598
+ const inlineLevel = Number(this.getAttr(inlinePPr, 'lvl') || 0);
599
+ const level = Number.isFinite(inlineLevel) ? inlineLevel : 0;
600
+ const levelStyle = this.extractParagraphLevelStyle(lstStyle, level);
601
+ const levelPPr = levelStyle.match(/<a:lvl\d+pPr[^>]*>/)?.[0] || '';
602
+ const pPr = inlinePPr || levelPPr;
603
+ const levelRPr = levelStyle.match(/<a:defRPr[^>]*\/?>/)?.[0] || '';
604
+ const bodyDefaultRPr = txBody.match(/<a:defRPr[^>]*\/?>/)?.[0]
605
+ || bodyPr.match(/<a:defRPr[^>]*\/?>/)?.[0]
606
+ || '';
607
+ const rPr = lastRun.match(/<a:rPr[^>]*\/?>/)?.[0]
608
+ || pXml.match(/<a:defRPr[^>]*\/?>/)?.[0]
609
+ || levelRPr
610
+ || bodyDefaultRPr
611
+ || '';
612
+ const align = this.getAttr(pPr, 'algn') || undefined;
613
+ const size = Number(this.getAttr(rPr, 'sz') || 0);
614
+ const color = this.extractColorFromXml(rPr, colors);
615
+ const hasBullet = /<a:buChar\b|<a:buAutoNum\b|<a:buBlip\b/.test(pXml) || /<a:buChar\b|<a:buAutoNum\b|<a:buBlip\b/.test(levelStyle);
616
+ const hasBuNone = /<a:buNone\b/.test(pXml) || /<a:buNone\b/.test(levelStyle);
617
+ paragraphs.push({
618
+ text,
619
+ level: Number.isFinite(level) ? level : 0,
620
+ bullet: hasBullet && !hasBuNone,
621
+ align,
622
+ fontSizePx: size > 0 ? Math.round((size / 100) * 1.333) : undefined,
623
+ bold: this.parseOptionalBoolAttr(rPr, 'b'),
624
+ italic: this.parseOptionalBoolAttr(rPr, 'i'),
625
+ color,
626
+ runs: runs.length > 0 ? runs : undefined
627
+ });
628
+ }
629
+ return paragraphs;
630
+ }
631
+ static parseGeometry(xml) {
632
+ const xfrm = this.extractTagBlock(xml, 'a:xfrm') || this.extractTagBlock(xml, 'p:xfrm');
633
+ if (!xfrm)
634
+ return null;
635
+ const off = xfrm.match(/<a:off[^>]*\/>/)?.[0] || '';
636
+ const ext = xfrm.match(/<a:ext[^>]*\/>/)?.[0] || '';
637
+ const x = Number(this.getAttr(off, 'x') || 0);
638
+ const y = Number(this.getAttr(off, 'y') || 0);
639
+ const cx = Number(this.getAttr(ext, 'cx') || 0);
640
+ const cy = Number(this.getAttr(ext, 'cy') || 0);
641
+ // Allow cx=0 or cy=0 so horizontal/vertical line connectors keep their position.
642
+ if (!cx && !cy)
643
+ return null;
644
+ const rotRaw = Number(this.getAttr(xfrm, 'rot') || 0);
645
+ const flipH = this.getAttr(xfrm, 'flipH') === '1';
646
+ const flipV = this.getAttr(xfrm, 'flipV') === '1';
647
+ return {
648
+ x: this.emuToPx(x),
649
+ y: this.emuToPx(y),
650
+ width: this.emuToPx(cx),
651
+ height: this.emuToPx(cy),
652
+ rotateDeg: rotRaw ? rotRaw / 60000 : undefined,
653
+ flipH: flipH || undefined,
654
+ flipV: flipV || undefined
655
+ };
656
+ }
657
+ static parseGroupTransform(xml) {
658
+ const grpPr = this.extractTagBlock(xml, 'p:grpSpPr');
659
+ const xfrm = grpPr ? this.extractTagBlock(grpPr, 'a:xfrm') : '';
660
+ if (!xfrm) {
661
+ return ZERO_TX;
662
+ }
663
+ const off = xfrm.match(/<a:off[^>]*\/>/)?.[0] || '';
664
+ const ext = xfrm.match(/<a:ext[^>]*\/>/)?.[0] || '';
665
+ const chOff = xfrm.match(/<a:chOff[^>]*\/>/)?.[0] || '';
666
+ const chExt = xfrm.match(/<a:chExt[^>]*\/>/)?.[0] || '';
667
+ const offX = Number(this.getAttr(off, 'x') || 0);
668
+ const offY = Number(this.getAttr(off, 'y') || 0);
669
+ const extX = Number(this.getAttr(ext, 'cx') || 1);
670
+ const extY = Number(this.getAttr(ext, 'cy') || 1);
671
+ const chOffX = Number(this.getAttr(chOff, 'x') || 0);
672
+ const chOffY = Number(this.getAttr(chOff, 'y') || 0);
673
+ const chExtX = Number(this.getAttr(chExt, 'cx') || extX || 1);
674
+ const chExtY = Number(this.getAttr(chExt, 'cy') || extY || 1);
675
+ const sx = extX / (chExtX || 1);
676
+ const sy = extY / (chExtY || 1);
677
+ const rotRaw = Number(this.getAttr(xfrm, 'rot') || 0);
678
+ return {
679
+ offX: this.emuToPx(offX - chOffX * sx),
680
+ offY: this.emuToPx(offY - chOffY * sy),
681
+ scaleX: sx,
682
+ scaleY: sy,
683
+ rotDeg: rotRaw ? rotRaw / 60000 : 0
684
+ };
685
+ }
686
+ static combineTransforms(parent, child) {
687
+ return {
688
+ offX: parent.offX + child.offX * parent.scaleX,
689
+ offY: parent.offY + child.offY * parent.scaleY,
690
+ scaleX: parent.scaleX * child.scaleX,
691
+ scaleY: parent.scaleY * child.scaleY,
692
+ rotDeg: (parent.rotDeg || 0) + (child.rotDeg || 0)
693
+ };
694
+ }
695
+ static applyTransform(geom, tx) {
696
+ return {
697
+ x: Math.round(tx.offX + geom.x * tx.scaleX),
698
+ y: Math.round(tx.offY + geom.y * tx.scaleY),
699
+ width: Math.round(geom.width * tx.scaleX),
700
+ height: Math.round(geom.height * tx.scaleY),
701
+ rotateDeg: (geom.rotateDeg || 0) + (tx.rotDeg || 0),
702
+ flipH: geom.flipH,
703
+ flipV: geom.flipV
704
+ };
705
+ }
706
+ static parseTheme(themeXml) {
707
+ const colors = {
708
+ lt1: '#ffffff',
709
+ dk1: '#000000',
710
+ lt2: '#eeeeee',
711
+ dk2: '#222222',
712
+ accent1: '#4472c4',
713
+ accent2: '#ed7d31',
714
+ accent3: '#a5a5a5',
715
+ accent4: '#ffc000',
716
+ accent5: '#5b9bd5',
717
+ accent6: '#70ad47'
718
+ };
719
+ if (!themeXml)
720
+ return { colors };
721
+ const clrScheme = this.extractTagBlock(themeXml, 'a:clrScheme') || '';
722
+ const keys = ['lt1', 'dk1', 'lt2', 'dk2', 'accent1', 'accent2', 'accent3', 'accent4', 'accent5', 'accent6'];
723
+ for (const key of keys) {
724
+ const block = this.extractTagBlock(clrScheme, `a:${key}`) || '';
725
+ const srgb = block.match(/<a:srgbClr[^>]*val="([^"]+)"/)?.[1];
726
+ const sys = block.match(/<a:sysClr[^>]*lastClr="([^"]+)"/)?.[1];
727
+ if (srgb)
728
+ colors[key] = `#${srgb}`;
729
+ else if (sys)
730
+ colors[key] = `#${sys}`;
731
+ }
732
+ return { colors };
733
+ }
734
+ static extractBackgroundColor(xml, colors) {
735
+ if (!xml)
736
+ return undefined;
737
+ const bgPr = this.extractTagBlock(xml, 'p:bgPr') || '';
738
+ if (!bgPr)
739
+ return undefined;
740
+ const solid = this.extractColorFromXml(bgPr, colors);
741
+ if (solid)
742
+ return solid;
743
+ const gradFill = this.extractTagBlock(bgPr, 'a:gradFill') || '';
744
+ if (gradFill) {
745
+ const stops = gradFill.match(/<a:gs\b[\s\S]*?<\/a:gs>/g) || [];
746
+ const lastStop = stops[stops.length - 1] || gradFill;
747
+ return this.extractColorFromXml(lastStop, colors);
748
+ }
749
+ return undefined;
750
+ }
751
+ static extractFillColor(xml, colors) {
752
+ const spPr = this.extractTagBlock(xml, 'p:spPr') || xml;
753
+ // Strip nested blocks (line, effects, 3d) that contain their own solidFill
754
+ // so we don't mistake line color for fill color.
755
+ const fillScope = this.stripNestedBlocks(spPr, ['a:ln', 'a:effectLst', 'a:scene3d', 'a:sp3d']);
756
+ // Explicit <a:noFill/> means no fill regardless of style refs.
757
+ if (/<a:noFill\b[^>]*\/?>/.test(fillScope)) {
758
+ return undefined;
759
+ }
760
+ const solid = this.extractTagBlock(fillScope, 'a:solidFill') || '';
761
+ const solidColor = this.extractColorFromXml(solid, colors);
762
+ if (solidColor)
763
+ return solidColor;
764
+ const gradFill = this.extractTagBlock(fillScope, 'a:gradFill') || '';
765
+ if (gradFill) {
766
+ const stops = gradFill.match(/<a:gs\b[\s\S]*?<\/a:gs>/g) || [];
767
+ const firstStop = stops[0] || gradFill;
768
+ const lastStop = stops[stops.length - 1] || gradFill;
769
+ return this.extractColorFromXml(firstStop, colors) || this.extractColorFromXml(lastStop, colors);
770
+ }
771
+ const style = this.extractTagBlock(xml, 'p:style') || '';
772
+ const fillRef = this.extractTagBlock(style, 'a:fillRef') || '';
773
+ return this.extractColorFromXml(fillRef, colors);
774
+ }
775
+ static stripNestedBlocks(xml, tags) {
776
+ let result = xml;
777
+ for (const tag of tags) {
778
+ const re = new RegExp(`<${tag}\\b[^>]*/>|<${tag}\\b[\\s\\S]*?<\\/${tag}>`, 'g');
779
+ result = result.replace(re, '');
780
+ }
781
+ return result;
782
+ }
783
+ static extractLineColor(xml, colors) {
784
+ const spPr = this.extractTagBlock(xml, 'p:spPr') || xml;
785
+ const ln = this.extractTagBlock(spPr, 'a:ln') || '';
786
+ return this.extractColorFromXml(ln, colors);
787
+ }
788
+ static extractColorFromXml(xml, colors) {
789
+ if (!xml)
790
+ return undefined;
791
+ const srgbNode = xml.match(/<a:srgbClr[^>]*val="([^"]+)"[^>]*\/>|<a:srgbClr[^>]*val="([^"]+)"[^>]*>[\s\S]*?<\/a:srgbClr>/);
792
+ if (srgbNode) {
793
+ const raw = srgbNode[1] || srgbNode[2];
794
+ const base = raw ? `#${raw}` : undefined;
795
+ if (base)
796
+ return this.applyColorTransforms(base, srgbNode[0]);
797
+ }
798
+ const sysNode = xml.match(/<a:sysClr[^>]*lastClr="([^"]+)"[^>]*\/>|<a:sysClr[^>]*lastClr="([^"]+)"[^>]*>[\s\S]*?<\/a:sysClr>/);
799
+ if (sysNode) {
800
+ const raw = sysNode[1] || sysNode[2];
801
+ const base = raw ? `#${raw}` : undefined;
802
+ if (base)
803
+ return this.applyColorTransforms(base, sysNode[0]);
804
+ }
805
+ const presetNode = xml.match(/<a:prstClr[^>]*val="([^"]+)"[^>]*\/>|<a:prstClr[^>]*val="([^"]+)"[^>]*>[\s\S]*?<\/a:prstClr>/);
806
+ if (presetNode) {
807
+ const preset = presetNode[1] || presetNode[2];
808
+ const presetColor = preset ? this.mapPresetColorName(preset) : undefined;
809
+ if (presetColor)
810
+ return this.applyColorTransforms(presetColor, presetNode[0]);
811
+ }
812
+ const schemeNode = xml.match(/<a:schemeClr[^>]*val="([^"]+)"[^>]*\/>|<a:schemeClr[^>]*val="([^"]+)"[^>]*>[\s\S]*?<\/a:schemeClr>/);
813
+ if (schemeNode) {
814
+ const scheme = ((schemeNode[1] || schemeNode[2]) || '').trim();
815
+ if (scheme) {
816
+ let base = colors.themeColors[scheme];
817
+ if (!base) {
818
+ const mapped = colors.clrMap[scheme];
819
+ if (mapped)
820
+ base = colors.themeColors[mapped];
821
+ }
822
+ if (base)
823
+ return this.applyColorTransforms(base, schemeNode[0]);
824
+ }
825
+ }
826
+ return undefined;
827
+ }
828
+ static isTitleShape(xml) {
829
+ const phType = xml.match(/<p:ph[^>]*type="([^"]+)"/)?.[1] || '';
830
+ if (phType === 'title' || phType === 'ctrTitle')
831
+ return true;
832
+ const name = xml.match(/<p:cNvPr[^>]*name="([^"]+)"/)?.[1] || '';
833
+ // Avoid treating subtitle placeholders as title.
834
+ if (/subtitle/i.test(name))
835
+ return false;
836
+ return /^title\b/i.test(name) || /title placeholder/i.test(name);
837
+ }
838
+ static getPlaceholderKey(xml) {
839
+ const ph = xml.match(/<p:ph[^>]*\/>/)?.[0] || xml.match(/<p:ph[^>]*>/)?.[0] || '';
840
+ if (!ph)
841
+ return undefined;
842
+ const rawIdx = this.getAttr(ph, 'idx') || '0';
843
+ const idx = (rawIdx && rawIdx !== '0' && rawIdx !== '4294967295') ? rawIdx : undefined;
844
+ const type = (this.getAttr(ph, 'type') || 'body').toLowerCase();
845
+ const normalizedType = this.normalizePlaceholderType(type);
846
+ if (normalizedType === 'title' || normalizedType === 'body' || normalizedType === 'sldnum' || normalizedType === 'ftr' || normalizedType === 'dt') {
847
+ return `type:${normalizedType}`;
848
+ }
849
+ if (idx) {
850
+ return `idx:${idx}`;
851
+ }
852
+ return `type:${normalizedType}`;
853
+ }
854
+ static normalizePlaceholderType(type) {
855
+ if (type === 'title' || type === 'ctrtitle')
856
+ return 'title';
857
+ if (type === 'subtitle' || type === 'subTitle'.toLowerCase())
858
+ return 'body';
859
+ if (type === 'sldnum')
860
+ return 'sldnum';
861
+ if (type === 'body' || type === 'obj' || type === 'content')
862
+ return 'body';
863
+ return type;
864
+ }
865
+ static getPlaceholderType(xml) {
866
+ const ph = xml.match(/<p:ph[^>]*\/>/)?.[0] || xml.match(/<p:ph[^>]*>/)?.[0] || '';
867
+ if (!ph)
868
+ return undefined;
869
+ const type = (this.getAttr(ph, 'type') || '').toLowerCase();
870
+ return type || undefined;
871
+ }
872
+ static async getSlideSize(zip) {
873
+ const presentation = await this.readZipText(zip, 'ppt/presentation.xml');
874
+ const szTag = presentation.match(/<p:sldSz[^>]*\/>/)?.[0] || '';
875
+ const cx = Number(this.getAttr(szTag, 'cx') || 0);
876
+ const cy = Number(this.getAttr(szTag, 'cy') || 0);
877
+ if (!cx || !cy)
878
+ return { widthPx: 1280, heightPx: 720 };
879
+ const widthPx = this.emuToPx(cx);
880
+ const heightPx = this.emuToPx(cy);
881
+ if (widthPx < 300 || heightPx < 200)
882
+ return { widthPx: 1280, heightPx: 720 };
883
+ return { widthPx, heightPx };
884
+ }
885
+ static async getOrderedSlidePaths(zip) {
886
+ const presentationXml = await this.readZipText(zip, 'ppt/presentation.xml');
887
+ const rels = await this.getRelationships(zip, 'ppt/presentation.xml');
888
+ const relMap = new Map();
889
+ rels.forEach((r) => relMap.set(r.id, r.target));
890
+ const ordered = [];
891
+ const idMatches = presentationXml.match(/<p:sldId[^>]*r:id="([^"]+)"[^>]*\/?/g) || [];
892
+ for (const match of idMatches) {
893
+ const id = match.match(/r:id="([^"]+)"/)?.[1];
894
+ if (!id)
895
+ continue;
896
+ const target = relMap.get(id);
897
+ if (target && zip.file(target))
898
+ ordered.push(target);
899
+ }
900
+ if (ordered.length > 0)
901
+ return ordered;
902
+ return Object.keys(zip.files)
903
+ .filter((name) => /^ppt\/slides\/slide\d+\.xml$/i.test(name))
904
+ .sort((a, b) => {
905
+ const na = Number(a.match(/slide(\d+)\.xml/i)?.[1] || 0);
906
+ const nb = Number(b.match(/slide(\d+)\.xml/i)?.[1] || 0);
907
+ return na - nb;
908
+ });
909
+ }
910
+ static async getRelationships(zip, partPath) {
911
+ const relPath = this.toRelsPath(partPath);
912
+ const relXml = await this.readZipText(zip, relPath);
913
+ if (!relXml)
914
+ return [];
915
+ const list = [];
916
+ const re = /<Relationship[^>]*Id="([^"]+)"[^>]*Type="([^"]+)"[^>]*Target="([^"]+)"[^>]*\/?/g;
917
+ let m;
918
+ while ((m = re.exec(relXml)) !== null) {
919
+ list.push({
920
+ id: m[1],
921
+ type: m[2],
922
+ target: this.resolvePath(partPath, m[3])
923
+ });
924
+ }
925
+ return list;
926
+ }
927
+ static toRelsPath(partPath) {
928
+ const dir = path.posix.dirname(partPath);
929
+ const base = path.posix.basename(partPath);
930
+ return path.posix.join(dir, '_rels', `${base}.rels`);
931
+ }
932
+ static resolvePath(basePath, target) {
933
+ if (target.startsWith('/')) {
934
+ return target.replace(/^\/+/, '');
935
+ }
936
+ return path.posix.normalize(path.posix.join(path.posix.dirname(basePath), target));
937
+ }
938
+ static async readZipText(zip, zipPath) {
939
+ const file = zip.file(zipPath);
940
+ if (!file)
941
+ return '';
942
+ return await file.async('text');
943
+ }
944
+ static extractBalancedTag(xml, tag, startAt) {
945
+ const closeToken = `</${tag}>`;
946
+ let pos = startAt;
947
+ const firstOpen = this.findNextTagIndex(xml, tag, pos);
948
+ if (firstOpen !== startAt)
949
+ return null;
950
+ const firstClose = xml.indexOf('>', firstOpen);
951
+ if (firstClose === -1)
952
+ return null;
953
+ // Self-closing tag
954
+ const beforeClose = xml.slice(firstOpen, firstClose + 1);
955
+ if (/\/\s*>$/.test(beforeClose)) {
956
+ return {
957
+ content: xml.slice(firstOpen, firstClose + 1),
958
+ innerContent: '',
959
+ end: firstClose + 1
960
+ };
961
+ }
962
+ let depth = 1;
963
+ pos = firstClose + 1;
964
+ while (depth > 0) {
965
+ const nextOpen = this.findNextTagIndex(xml, tag, pos);
966
+ const nextClose = xml.indexOf(closeToken, pos);
967
+ if (nextClose === -1)
968
+ return null;
969
+ if (nextOpen !== -1 && nextOpen < nextClose) {
970
+ const openEnd = xml.indexOf('>', nextOpen);
971
+ if (openEnd === -1)
972
+ return null;
973
+ if (xml[openEnd - 1] !== '/') {
974
+ depth += 1;
975
+ }
976
+ pos = openEnd + 1;
977
+ }
978
+ else {
979
+ depth -= 1;
980
+ pos = nextClose + closeToken.length;
981
+ }
982
+ }
983
+ return {
984
+ content: xml.slice(firstOpen, pos),
985
+ innerContent: xml.slice(firstClose + 1, pos - closeToken.length),
986
+ end: pos
987
+ };
988
+ }
989
+ static extractTagBlock(xml, tag) {
990
+ const idx = xml.indexOf(`<${tag}`);
991
+ if (idx === -1)
992
+ return '';
993
+ return this.extractBalancedTag(xml, tag, idx)?.content || '';
994
+ }
995
+ static getAttr(tag, attr) {
996
+ if (!tag)
997
+ return undefined;
998
+ const escaped = attr.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
999
+ return tag.match(new RegExp(`${escaped}=(?:"([^"]+)"|'([^']+)')`))?.[1]
1000
+ || tag.match(new RegExp(`${escaped}=(?:"([^"]+)"|'([^']+)')`))?.[2];
1001
+ }
1002
+ static parseOptionalBoolAttr(tag, attr) {
1003
+ const raw = this.getAttr(tag, attr);
1004
+ if (raw === undefined)
1005
+ return undefined;
1006
+ return raw === '1' || raw.toLowerCase() === 'true';
1007
+ }
1008
+ static emuToPx(emu) {
1009
+ return Math.round(emu / 9525);
1010
+ }
1011
+ static decodeXmlEntities(input) {
1012
+ return input
1013
+ .replace(/&amp;/g, '&')
1014
+ .replace(/&lt;/g, '<')
1015
+ .replace(/&gt;/g, '>')
1016
+ .replace(/&quot;/g, '"')
1017
+ .replace(/&#39;/g, "'")
1018
+ .replace(/&#xD;/gi, '')
1019
+ .replace(/&#xA;/gi, ' ')
1020
+ .replace(/&#10;/g, ' ');
1021
+ }
1022
+ static extractParagraphLevelStyle(lstStyle, level) {
1023
+ if (!lstStyle)
1024
+ return '';
1025
+ const normalizedLevel = Math.max(0, Math.min(8, Number.isFinite(level) ? level : 0)) + 1;
1026
+ return this.extractTagBlock(lstStyle, `a:lvl${normalizedLevel}pPr`) || '';
1027
+ }
1028
+ static buildColorContext(theme, masterXml, layoutXml, slideXml) {
1029
+ const masterMap = this.parseMasterClrMap(masterXml);
1030
+ const layoutOverride = this.parseClrMapOverride(layoutXml);
1031
+ const slideOverride = this.parseClrMapOverride(slideXml);
1032
+ let clrMap = { ...masterMap };
1033
+ if (layoutOverride) {
1034
+ clrMap = { ...clrMap, ...layoutOverride };
1035
+ }
1036
+ if (slideOverride) {
1037
+ clrMap = { ...clrMap, ...slideOverride };
1038
+ }
1039
+ return {
1040
+ themeColors: theme.colors,
1041
+ clrMap
1042
+ };
1043
+ }
1044
+ static parseMasterClrMap(masterXml) {
1045
+ const defaults = {
1046
+ bg1: 'lt1',
1047
+ tx1: 'dk1',
1048
+ bg2: 'lt2',
1049
+ tx2: 'dk2',
1050
+ accent1: 'accent1',
1051
+ accent2: 'accent2',
1052
+ accent3: 'accent3',
1053
+ accent4: 'accent4',
1054
+ accent5: 'accent5',
1055
+ accent6: 'accent6',
1056
+ hlink: 'hlink',
1057
+ folHlink: 'folHlink'
1058
+ };
1059
+ const clrMapTag = masterXml.match(/<p:clrMap\b[^>]*\/>/)?.[0] || '';
1060
+ if (!clrMapTag) {
1061
+ return defaults;
1062
+ }
1063
+ const keys = Object.keys(defaults);
1064
+ const parsed = { ...defaults };
1065
+ keys.forEach((key) => {
1066
+ const value = this.getAttr(clrMapTag, key);
1067
+ if (value)
1068
+ parsed[key] = value;
1069
+ });
1070
+ return parsed;
1071
+ }
1072
+ static parseClrMapOverride(xml) {
1073
+ if (!xml)
1074
+ return null;
1075
+ const clrMapOvr = this.extractTagBlock(xml, 'p:clrMapOvr') || '';
1076
+ if (!clrMapOvr)
1077
+ return null;
1078
+ if (clrMapOvr.includes('<a:masterClrMapping')) {
1079
+ return null;
1080
+ }
1081
+ const overrideTag = clrMapOvr.match(/<a:overrideClrMapping\b[^>]*\/>/)?.[0] || '';
1082
+ if (!overrideTag)
1083
+ return null;
1084
+ const keys = ['bg1', 'tx1', 'bg2', 'tx2', 'accent1', 'accent2', 'accent3', 'accent4', 'accent5', 'accent6', 'hlink', 'folHlink'];
1085
+ const parsed = {};
1086
+ keys.forEach((key) => {
1087
+ const value = this.getAttr(overrideTag, key);
1088
+ if (value)
1089
+ parsed[key] = value;
1090
+ });
1091
+ return Object.keys(parsed).length > 0 ? parsed : null;
1092
+ }
1093
+ static parseChartData(xml, colors) {
1094
+ if (!xml)
1095
+ return undefined;
1096
+ const lineChart = this.extractTagBlock(xml, 'c:lineChart');
1097
+ if (lineChart) {
1098
+ const parsedLineChart = this.parseLineChartData(xml, lineChart, colors);
1099
+ if (parsedLineChart)
1100
+ return parsedLineChart;
1101
+ }
1102
+ const barChart = this.extractTagBlock(xml, 'c:barChart');
1103
+ if (!barChart)
1104
+ return undefined;
1105
+ const grouping = barChart.match(/<c:grouping[^>]*val="([^"]+)"/)?.[1] || '';
1106
+ const barDir = barChart.match(/<c:barDir[^>]*val="([^"]+)"/)?.[1] || '';
1107
+ if (grouping !== 'stacked' || barDir !== 'col')
1108
+ return undefined;
1109
+ const serBlocks = barChart.match(/<c:ser\b[\s\S]*?<\/c:ser>/g) || [];
1110
+ if (serBlocks.length === 0)
1111
+ return undefined;
1112
+ let categories = [];
1113
+ const series = serBlocks.map((serXml, idx) => {
1114
+ const name = this.decodeXmlEntities(serXml.match(/<c:tx[\s\S]*?<c:v>([\s\S]*?)<\/c:v>/)?.[1]
1115
+ || `Series ${idx + 1}`);
1116
+ const spPr = this.extractTagBlock(serXml, 'c:spPr') || '';
1117
+ const palette = ['#8a8a8a', '#d10000', '#4f81bd', '#9bbb59', '#8064a2', '#f79646'];
1118
+ const seriesColor = this.extractColorFromXml(spPr, colors) || palette[idx % palette.length];
1119
+ if (categories.length === 0) {
1120
+ const categoryPts = serXml.match(/<c:cat[\s\S]*?<\/c:cat>/)?.[0] || '';
1121
+ categories = this.extractChartPoints(categoryPts);
1122
+ }
1123
+ const valuePts = serXml.match(/<c:val[\s\S]*?<\/c:val>/)?.[0] || '';
1124
+ const values = this.extractChartNumericPoints(valuePts, categories.length || undefined);
1125
+ const valueFormatRaw = valuePts.match(/<c:formatCode>([\s\S]*?)<\/c:formatCode>/)?.[1];
1126
+ const dataLabel = this.parseSeriesDataLabel(serXml, colors);
1127
+ return {
1128
+ name,
1129
+ color: seriesColor,
1130
+ values,
1131
+ valueFormat: valueFormatRaw ? this.decodeXmlEntities(valueFormatRaw) : undefined,
1132
+ dataLabel
1133
+ };
1134
+ });
1135
+ if (categories.length === 0) {
1136
+ const maxLen = Math.max(...series.map((s) => s.values.length));
1137
+ categories = Array.from({ length: maxLen }, (_, i) => `${i + 1}`);
1138
+ }
1139
+ const normalizedSeries = series.map((s) => ({
1140
+ ...s,
1141
+ values: this.padValues(s.values, categories.length)
1142
+ }));
1143
+ const gapWidth = this.parseNumber(barChart.match(/<c:gapWidth[^>]*val="([^"]+)"/)?.[1]);
1144
+ const overlap = this.parseNumber(barChart.match(/<c:overlap[^>]*val="([^"]+)"/)?.[1]);
1145
+ const categoryAxis = this.parseCategoryAxis(xml, colors);
1146
+ const valueAxis = this.parseValueAxis(xml, colors);
1147
+ const legend = this.parseLegend(xml, colors);
1148
+ return {
1149
+ kind: 'stackedColumn',
1150
+ categories,
1151
+ series: normalizedSeries,
1152
+ gapWidth,
1153
+ overlap,
1154
+ categoryAxis,
1155
+ valueAxis,
1156
+ legend
1157
+ };
1158
+ }
1159
+ static parseLineChartData(chartXml, lineChart, colors) {
1160
+ const serBlocks = lineChart.match(/<c:ser\b[\s\S]*?<\/c:ser>/g) || [];
1161
+ if (serBlocks.length === 0)
1162
+ return undefined;
1163
+ let categories = [];
1164
+ const series = serBlocks.map((serXml, idx) => {
1165
+ const name = this.decodeXmlEntities(serXml.match(/<c:tx[\s\S]*?<c:v>([\s\S]*?)<\/c:v>/)?.[1]
1166
+ || `Series ${idx + 1}`);
1167
+ const spPr = this.extractTagBlock(serXml, 'c:spPr') || '';
1168
+ const palette = ['#4472c4', '#ed7d31', '#a5a5a5', '#ffc000', '#5b9bd5', '#70ad47'];
1169
+ const seriesColor = this.extractColorFromXml(spPr, colors) || palette[idx % palette.length];
1170
+ if (categories.length === 0) {
1171
+ const categoryPts = serXml.match(/<c:cat[\s\S]*?<\/c:cat>/)?.[0] || '';
1172
+ categories = this.extractChartPoints(categoryPts);
1173
+ }
1174
+ const valuePts = serXml.match(/<c:val[\s\S]*?<\/c:val>/)?.[0] || '';
1175
+ const values = this.extractChartNumericPoints(valuePts, categories.length || undefined);
1176
+ const valueFormatRaw = valuePts.match(/<c:formatCode>([\s\S]*?)<\/c:formatCode>/)?.[1];
1177
+ const dataLabel = this.parseSeriesDataLabel(serXml, colors);
1178
+ return {
1179
+ name,
1180
+ color: seriesColor,
1181
+ values,
1182
+ valueFormat: valueFormatRaw ? this.decodeXmlEntities(valueFormatRaw) : undefined,
1183
+ dataLabel
1184
+ };
1185
+ });
1186
+ if (categories.length === 0) {
1187
+ const maxLen = Math.max(...series.map((s) => s.values.length));
1188
+ categories = Array.from({ length: maxLen }, (_, i) => `${i + 1}`);
1189
+ }
1190
+ const normalizedSeries = series.map((s) => ({
1191
+ ...s,
1192
+ values: this.padValues(s.values, categories.length)
1193
+ }));
1194
+ const categoryAxis = this.parseCategoryAxis(chartXml, colors);
1195
+ const valueAxis = this.parseValueAxis(chartXml, colors);
1196
+ const legend = this.parseLegend(chartXml, colors);
1197
+ return {
1198
+ kind: 'line',
1199
+ categories,
1200
+ series: normalizedSeries,
1201
+ categoryAxis,
1202
+ valueAxis,
1203
+ legend
1204
+ };
1205
+ }
1206
+ static parseSeriesDataLabel(serXml, colors) {
1207
+ const dLbls = this.extractTagBlock(serXml, 'c:dLbls') || '';
1208
+ if (!dLbls)
1209
+ return undefined;
1210
+ const txPr = this.extractTagBlock(dLbls, 'c:txPr') || '';
1211
+ const style = this.parseTxPrStyle(txPr, colors);
1212
+ const numFmtRaw = dLbls.match(/<c:numFmt[^>]*formatCode="([^"]+)"/)?.[1];
1213
+ const showValRaw = dLbls.match(/<c:showVal[^>]*val="([^"]+)"/)?.[1];
1214
+ const numFmt = numFmtRaw ? this.decodeXmlEntities(numFmtRaw) : undefined;
1215
+ const showValue = showValRaw === '1' ? true : (showValRaw === '0' ? false : undefined);
1216
+ if (showValue === undefined && !numFmt && !style.fontSizePx && !style.color) {
1217
+ return undefined;
1218
+ }
1219
+ return {
1220
+ showValue,
1221
+ numFmt,
1222
+ fontSizePx: style.fontSizePx,
1223
+ color: style.color
1224
+ };
1225
+ }
1226
+ static parseCategoryAxis(chartXml, colors) {
1227
+ const catAx = this.extractTagBlock(chartXml, 'c:catAx') || '';
1228
+ if (!catAx)
1229
+ return undefined;
1230
+ const txPr = this.extractTagBlock(catAx, 'c:txPr') || '';
1231
+ const style = this.parseTxPrStyle(txPr, colors);
1232
+ const lineColor = this.extractColorFromXml(this.extractTagBlock(catAx, 'c:spPr') || '', colors);
1233
+ const numFmtRaw = catAx.match(/<c:numFmt[^>]*formatCode="([^"]+)"/)?.[1];
1234
+ const numFmt = numFmtRaw ? this.decodeXmlEntities(numFmtRaw) : undefined;
1235
+ return {
1236
+ numFmt,
1237
+ fontSizePx: style.fontSizePx,
1238
+ color: style.color,
1239
+ lineColor
1240
+ };
1241
+ }
1242
+ static parseValueAxis(chartXml, colors) {
1243
+ const valAx = this.extractTagBlock(chartXml, 'c:valAx') || '';
1244
+ if (!valAx)
1245
+ return undefined;
1246
+ const txPr = this.extractTagBlock(valAx, 'c:txPr') || '';
1247
+ const style = this.parseTxPrStyle(txPr, colors);
1248
+ const lineColor = this.extractColorFromXml(this.extractTagBlock(valAx, 'c:spPr') || '', colors);
1249
+ const majorGridColor = this.extractColorFromXml(this.extractTagBlock(valAx, 'c:majorGridlines') || '', colors);
1250
+ const numFmtRaw = valAx.match(/<c:numFmt[^>]*formatCode="([^"]+)"/)?.[1];
1251
+ const numFmt = numFmtRaw ? this.decodeXmlEntities(numFmtRaw) : undefined;
1252
+ const scaling = this.extractTagBlock(valAx, 'c:scaling') || '';
1253
+ const min = this.parseNumber(scaling.match(/<c:min[^>]*val="([^"]+)"/)?.[1]);
1254
+ const max = this.parseNumber(scaling.match(/<c:max[^>]*val="([^"]+)"/)?.[1]);
1255
+ const majorUnit = this.parseNumber(valAx.match(/<c:majorUnit[^>]*val="([^"]+)"/)?.[1]);
1256
+ const crossesAt = this.parseNumber(valAx.match(/<c:crossesAt[^>]*val="([^"]+)"/)?.[1]);
1257
+ return {
1258
+ numFmt,
1259
+ fontSizePx: style.fontSizePx,
1260
+ color: style.color,
1261
+ lineColor,
1262
+ gridColor: majorGridColor,
1263
+ majorUnit,
1264
+ min,
1265
+ max,
1266
+ crossesAt
1267
+ };
1268
+ }
1269
+ static parseLegend(chartXml, colors) {
1270
+ const legend = this.extractTagBlock(chartXml, 'c:legend') || '';
1271
+ if (!legend)
1272
+ return undefined;
1273
+ const position = legend.match(/<c:legendPos[^>]*val="([^"]+)"/)?.[1];
1274
+ const txPr = this.extractTagBlock(legend, 'c:txPr') || '';
1275
+ const style = this.parseTxPrStyle(txPr, colors);
1276
+ return {
1277
+ position: position || undefined,
1278
+ fontSizePx: style.fontSizePx,
1279
+ color: style.color,
1280
+ align: style.align
1281
+ };
1282
+ }
1283
+ static parseTxPrStyle(txPr, colors) {
1284
+ if (!txPr)
1285
+ return {};
1286
+ const pPr = txPr.match(/<a:pPr[^>]*\/?>/)?.[0] || '';
1287
+ const defRPr = txPr.match(/<a:defRPr[^>]*\/?>/)?.[0] || '';
1288
+ const endRPr = txPr.match(/<a:endParaRPr[^>]*\/?>/)?.[0] || '';
1289
+ const fontRaw = Number(this.getAttr(defRPr, 'sz') || this.getAttr(endRPr, 'sz') || 0);
1290
+ const color = this.extractColorFromXml(defRPr || txPr, colors);
1291
+ return {
1292
+ fontSizePx: fontRaw > 0 ? Math.round((fontRaw / 100) * 1.333) : undefined,
1293
+ color,
1294
+ align: this.getAttr(pPr, 'algn') || undefined
1295
+ };
1296
+ }
1297
+ static extractChartPoints(xml) {
1298
+ if (!xml)
1299
+ return [];
1300
+ const out = [];
1301
+ const pts = xml.match(/<c:pt\b[\s\S]*?<\/c:pt>/g) || [];
1302
+ pts.forEach((pt) => {
1303
+ const idx = Number(pt.match(/idx="(\d+)"/)?.[1] || 0);
1304
+ const raw = pt.match(/<c:v>([\s\S]*?)<\/c:v>/)?.[1] || '';
1305
+ out.push({ idx, value: this.decodeXmlEntities(raw) });
1306
+ });
1307
+ out.sort((a, b) => a.idx - b.idx);
1308
+ return out.map((p) => p.value);
1309
+ }
1310
+ static extractChartNumericPoints(xml, fallbackLength) {
1311
+ const values = [];
1312
+ if (!xml)
1313
+ return fallbackLength ? Array.from({ length: fallbackLength }, () => 0) : values;
1314
+ const pts = xml.match(/<c:pt\b[\s\S]*?<\/c:pt>/g) || [];
1315
+ pts.forEach((pt) => {
1316
+ const idx = Number(pt.match(/idx="(\d+)"/)?.[1] || 0);
1317
+ const raw = pt.match(/<c:v>([\s\S]*?)<\/c:v>/)?.[1] || '';
1318
+ const n = Number(raw);
1319
+ if (!Number.isNaN(n)) {
1320
+ values[idx] = n;
1321
+ }
1322
+ });
1323
+ if (fallbackLength && values.length < fallbackLength) {
1324
+ for (let i = 0; i < fallbackLength; i++) {
1325
+ if (!Number.isFinite(values[i]))
1326
+ values[i] = 0;
1327
+ }
1328
+ }
1329
+ return values.map((v) => (Number.isFinite(v) ? v : 0));
1330
+ }
1331
+ static padValues(values, length) {
1332
+ const out = Array.from({ length }, (_, i) => values[i] || 0);
1333
+ return out;
1334
+ }
1335
+ static parseCustomGeometryPath(shapeXml, width, height) {
1336
+ const custGeom = this.extractTagBlock(shapeXml, 'a:custGeom');
1337
+ if (!custGeom || width <= 0 || height <= 0)
1338
+ return undefined;
1339
+ const pathBlocks = custGeom.match(/<a:path\b[\s\S]*?<\/a:path>/g) || [];
1340
+ if (pathBlocks.length === 0)
1341
+ return undefined;
1342
+ const pathData = [];
1343
+ for (const pathBlock of pathBlocks) {
1344
+ const rawW = Number(this.getAttr(pathBlock, 'w') || 0);
1345
+ const rawH = Number(this.getAttr(pathBlock, 'h') || 0);
1346
+ const scaleX = rawW > 0 ? width / rawW : 1;
1347
+ const scaleY = rawH > 0 ? height / rawH : 1;
1348
+ const commandBlocks = pathBlock.match(/<a:(?:moveTo|lnTo|cubicBezTo)\b[\s\S]*?<\/a:(?:moveTo|lnTo|cubicBezTo)>|<a:close\s*\/>/g) || [];
1349
+ for (const commandBlock of commandBlocks) {
1350
+ if (commandBlock.startsWith('<a:moveTo')) {
1351
+ const pt = this.extractPathPoint(commandBlock, 0, scaleX, scaleY);
1352
+ if (pt)
1353
+ pathData.push(`M ${pt.x} ${pt.y}`);
1354
+ }
1355
+ else if (commandBlock.startsWith('<a:lnTo')) {
1356
+ const pt = this.extractPathPoint(commandBlock, 0, scaleX, scaleY);
1357
+ if (pt)
1358
+ pathData.push(`L ${pt.x} ${pt.y}`);
1359
+ }
1360
+ else if (commandBlock.startsWith('<a:cubicBezTo')) {
1361
+ const p1 = this.extractPathPoint(commandBlock, 0, scaleX, scaleY);
1362
+ const p2 = this.extractPathPoint(commandBlock, 1, scaleX, scaleY);
1363
+ const p3 = this.extractPathPoint(commandBlock, 2, scaleX, scaleY);
1364
+ if (p1 && p2 && p3) {
1365
+ pathData.push(`C ${p1.x} ${p1.y} ${p2.x} ${p2.y} ${p3.x} ${p3.y}`);
1366
+ }
1367
+ }
1368
+ else if (commandBlock.startsWith('<a:close')) {
1369
+ pathData.push('Z');
1370
+ }
1371
+ }
1372
+ }
1373
+ return pathData.length > 0 ? pathData.join(' ') : undefined;
1374
+ }
1375
+ static extractPathPoint(xml, index, scaleX, scaleY) {
1376
+ const pointTags = xml.match(/<a:pt\b[^>]*x="[^"]+"[^>]*y="[^"]+"[^>]*\/>/g) || [];
1377
+ const pointTag = pointTags[index];
1378
+ if (!pointTag)
1379
+ return null;
1380
+ const x = Number(this.getAttr(pointTag, 'x') || 0);
1381
+ const y = Number(this.getAttr(pointTag, 'y') || 0);
1382
+ return {
1383
+ x: Math.round(x * scaleX * 1000) / 1000,
1384
+ y: Math.round(y * scaleY * 1000) / 1000
1385
+ };
1386
+ }
1387
+ static mapPresetColorName(name) {
1388
+ const n = name.toLowerCase();
1389
+ if (n === 'black')
1390
+ return '#000000';
1391
+ if (n === 'white')
1392
+ return '#ffffff';
1393
+ if (n === 'red')
1394
+ return '#ff0000';
1395
+ if (n === 'blue')
1396
+ return '#0000ff';
1397
+ if (n === 'green')
1398
+ return '#008000';
1399
+ if (n === 'gray' || n === 'grey')
1400
+ return '#808080';
1401
+ return undefined;
1402
+ }
1403
+ static applyColorTransforms(hex, xml) {
1404
+ const rgb = this.hexToRgb(hex);
1405
+ if (!rgb)
1406
+ return hex;
1407
+ const shade = Number(xml.match(/<a:shade[^>]*val="(\d+)"/)?.[1] || 100000) / 100000;
1408
+ const tint = Number(xml.match(/<a:tint[^>]*val="(\d+)"/)?.[1] || 0) / 100000;
1409
+ const lumMod = Number(xml.match(/<a:lumMod[^>]*val="(\d+)"/)?.[1] || 100000) / 100000;
1410
+ const lumOff = Number(xml.match(/<a:lumOff[^>]*val="(\d+)"/)?.[1] || 0) / 100000;
1411
+ const apply = (value) => {
1412
+ let c = value * shade;
1413
+ c = c + (255 - c) * tint;
1414
+ c = c * lumMod + 255 * lumOff;
1415
+ return Math.max(0, Math.min(255, Math.round(c)));
1416
+ };
1417
+ return this.rgbToHex(apply(rgb.r), apply(rgb.g), apply(rgb.b));
1418
+ }
1419
+ static hexToRgb(hex) {
1420
+ const raw = hex.replace('#', '').trim();
1421
+ if (raw.length === 3) {
1422
+ const r = parseInt(raw[0] + raw[0], 16);
1423
+ const g = parseInt(raw[1] + raw[1], 16);
1424
+ const b = parseInt(raw[2] + raw[2], 16);
1425
+ if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b))
1426
+ return null;
1427
+ return { r, g, b };
1428
+ }
1429
+ if (raw.length === 6) {
1430
+ const r = parseInt(raw.slice(0, 2), 16);
1431
+ const g = parseInt(raw.slice(2, 4), 16);
1432
+ const b = parseInt(raw.slice(4, 6), 16);
1433
+ if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b))
1434
+ return null;
1435
+ return { r, g, b };
1436
+ }
1437
+ return null;
1438
+ }
1439
+ static rgbToHex(r, g, b) {
1440
+ return `#${[r, g, b].map((c) => c.toString(16).padStart(2, '0')).join('')}`;
1441
+ }
1442
+ static parseNumber(raw) {
1443
+ if (raw === undefined)
1444
+ return undefined;
1445
+ const value = Number(raw);
1446
+ return Number.isFinite(value) ? value : undefined;
1447
+ }
1448
+ static isPlaceholderPromptText(text) {
1449
+ const normalized = (text || '').replace(/\s+/g, ' ').trim().toLowerCase();
1450
+ if (!normalized)
1451
+ return true;
1452
+ const promptPatterns = [
1453
+ /^click to edit master/i,
1454
+ /^click to edit/i,
1455
+ /^edit master text styles?$/i,
1456
+ /^insert text here$/i,
1457
+ /^(first|second|third|fourth|fifth|sixth|seventh|eighth|ninth) level$/i,
1458
+ /^list (first|second|third|fourth|fifth|sixth|seventh|eighth|ninth) level$/i,
1459
+ /^click icon to add picture$/i,
1460
+ /^presentation title$/i,
1461
+ /^author$/i,
1462
+ /^department$/i,
1463
+ /^date$/i,
1464
+ /^location$/i,
1465
+ /^마스터 .* 스타일 편집$/i,
1466
+ /^마스터 텍스트 스타일을 편집합니다$/i,
1467
+ /^(첫째|둘째|셋째|넷째|다섯째|여섯째|일곱째|여덟째|아홉째) 수준$/i
1468
+ ];
1469
+ return promptPatterns.some((re) => re.test(normalized));
1470
+ }
1471
+ static resolveImageTarget(zip, target) {
1472
+ const ext = path.extname(target).toLowerCase();
1473
+ if (ext !== '.emf' && ext !== '.wmf') {
1474
+ return target;
1475
+ }
1476
+ const dir = path.posix.dirname(target);
1477
+ const base = path.posix.basename(target, ext);
1478
+ const exactCandidates = ['.png', '.jpg', '.jpeg', '.webp', '.gif'].map((e) => path.posix.join(dir, `${base}${e}`));
1479
+ for (const candidate of exactCandidates) {
1480
+ if (zip.file(candidate))
1481
+ return candidate;
1482
+ }
1483
+ const anyRaster = Object.keys(zip.files)
1484
+ .filter((name) => name.startsWith(`${dir}/`) && /\.(png|jpe?g|webp|gif)$/i.test(name))
1485
+ .sort();
1486
+ return anyRaster[0] || target;
1487
+ }
1488
+ static getMimeTypeByExtension(filePath) {
1489
+ const ext = path.extname(filePath).toLowerCase();
1490
+ const map = {
1491
+ '.png': 'image/png',
1492
+ '.jpg': 'image/jpeg',
1493
+ '.jpeg': 'image/jpeg',
1494
+ '.gif': 'image/gif',
1495
+ '.bmp': 'image/bmp',
1496
+ '.webp': 'image/webp',
1497
+ '.svg': 'image/svg+xml',
1498
+ '.wmf': 'image/wmf',
1499
+ '.emf': 'image/emf'
1500
+ };
1501
+ return map[ext] || 'application/octet-stream';
1502
+ }
1503
+ }