kordoc 4.18.0 → 4.18.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 (144) hide show
  1. package/README.md +7 -6
  2. package/dist/{-DHSHP3AB.js → -I3CP33G4.js} +13 -11
  3. package/dist/{chunk-JUU2M5WI.js → chunk-4B2EXELH.js} +2 -2
  4. package/dist/{chunk-NYF7AGOX.js → chunk-6OKBTY72.js} +2 -2
  5. package/dist/{chunk-JU4DNVBP.js → chunk-B2E6C2LH.js} +27 -11
  6. package/dist/chunk-B2E6C2LH.js.map +1 -0
  7. package/dist/{chunk-UA7TQ37A.js → chunk-BL7KRE6X.js} +3 -3
  8. package/dist/{chunk-PGZX5JWZ.js → chunk-C2CVOC5K.js} +4 -4
  9. package/dist/{chunk-WSYTO525.js → chunk-CKUIFTXJ.js} +2 -2
  10. package/dist/{chunk-FPU7WPQM.js → chunk-F5HING4Y.js} +25 -9
  11. package/dist/chunk-F5HING4Y.js.map +1 -0
  12. package/dist/{chunk-VVJCQKXV.js → chunk-GM7XIZF2.js} +2 -2
  13. package/dist/chunk-GS7T56RP.cjs.map +1 -1
  14. package/dist/{chunk-DSF64VZN.js → chunk-IA3UKDHA.js} +4 -4
  15. package/dist/chunk-IA3UKDHA.js.map +1 -0
  16. package/dist/chunk-J55UQC5H.cjs.map +1 -1
  17. package/dist/{chunk-ELTPM4HM.cjs → chunk-KDMAYOWH.cjs} +158 -43
  18. package/dist/chunk-KDMAYOWH.cjs.map +1 -0
  19. package/dist/{chunk-Q6SKAFWN.js → chunk-L5RLJPI3.js} +3 -3
  20. package/dist/{chunk-QBTYETIU.js → chunk-L64QRUMK.js} +202 -7
  21. package/dist/chunk-L64QRUMK.js.map +1 -0
  22. package/dist/{chunk-LLD4EY2F.js → chunk-LACO375L.js} +2 -2
  23. package/dist/{chunk-DT57TAEL.js → chunk-LSOD2A6N.js} +2 -2
  24. package/dist/{chunk-OLOMR452.js → chunk-M4P7YS2N.js} +395 -58
  25. package/dist/chunk-M4P7YS2N.js.map +1 -0
  26. package/dist/{chunk-5JFKSCTE.js → chunk-N5DQFA7L.js} +2 -2
  27. package/dist/{chunk-DTPALXTW.js → chunk-NZM2SSI4.js} +2 -2
  28. package/dist/{chunk-GNBQXQDK.js → chunk-O2DI4GHD.js} +2 -2
  29. package/dist/{chunk-32FQLCUJ.js → chunk-OBILAVWC.js} +2 -2
  30. package/dist/{chunk-PQZ4BDXT.js → chunk-OGAYGUJF.js} +122 -87
  31. package/dist/chunk-OGAYGUJF.js.map +1 -0
  32. package/dist/{chunk-2QBZMCRT.cjs → chunk-OI5ZSSIC.cjs} +12 -12
  33. package/dist/chunk-OI5ZSSIC.cjs.map +1 -0
  34. package/dist/{chunk-F4ESJBGU.cjs → chunk-QGFZU3JE.cjs} +31 -15
  35. package/dist/chunk-QGFZU3JE.cjs.map +1 -0
  36. package/dist/{chunk-WDJJ77HP.js → chunk-QP7IXF7R.js} +2 -2
  37. package/dist/{chunk-NKCKN4WW.js → chunk-T4MXCSBP.js} +137 -27
  38. package/dist/chunk-T4MXCSBP.js.map +1 -0
  39. package/dist/{chunk-CKMGVW6S.cjs → chunk-UZGZRGYM.cjs} +2 -2
  40. package/dist/chunk-UZGZRGYM.cjs.map +1 -0
  41. package/dist/{chunk-NIPT5FJO.js → chunk-VSY5PAJ5.js} +2 -2
  42. package/dist/{chunk-GP7BPJTU.cjs → chunk-W2YYBPW5.cjs} +3 -3
  43. package/dist/chunk-W2YYBPW5.cjs.map +1 -0
  44. package/dist/{chunk-RLVALQZI.cjs → chunk-YNLW6EGU.cjs} +3 -3
  45. package/dist/chunk-YNLW6EGU.cjs.map +1 -0
  46. package/dist/chunks-IFFRC5IQ.js +10 -0
  47. package/dist/cli.js +26 -26
  48. package/dist/{formula-6Q2SBBQ5.js → formula-AQZODKZF.js} +3 -3
  49. package/dist/{formula-M6JJBJ3O.js → formula-E3N7KDDG.js} +4 -4
  50. package/dist/{formula-43KHT3B3.cjs → formula-VY4ITOMN.cjs} +9 -9
  51. package/dist/formula-VY4ITOMN.cjs.map +1 -0
  52. package/dist/{image-ocr-22XQ4TMN.cjs → image-ocr-6LYEBMVR.cjs} +15 -15
  53. package/dist/image-ocr-6LYEBMVR.cjs.map +1 -0
  54. package/dist/{image-ocr-2FIFDA7I.js → image-ocr-IMYXOHHV.js} +6 -6
  55. package/dist/{image-ocr-TH2E6E3E.js → image-ocr-LN3GSQPW.js} +9 -9
  56. package/dist/index.cjs +1042 -603
  57. package/dist/index.cjs.map +1 -1
  58. package/dist/index.d.cts +8 -6
  59. package/dist/index.d.ts +8 -6
  60. package/dist/index.js +529 -90
  61. package/dist/index.js.map +1 -1
  62. package/dist/mcp.js +26 -27
  63. package/dist/mcp.js.map +1 -1
  64. package/dist/{model-bundle-MLEDO25V.js → model-bundle-IHKNDLOT.js} +5 -5
  65. package/dist/{models-MYB5M4XC.js → models-YXITRAMR.js} +5 -5
  66. package/dist/page-range-QHHFTL7X.cjs.map +1 -1
  67. package/dist/{parser-L77S4RZC.cjs → parser-7KECZTTP.cjs} +176 -228
  68. package/dist/parser-7KECZTTP.cjs.map +1 -0
  69. package/dist/{parser-BKTX4GVM.js → parser-KB4BYQBZ.js} +14 -66
  70. package/dist/parser-KB4BYQBZ.js.map +1 -0
  71. package/dist/{parser-WW7NWTUM.js → parser-M3T2EWUN.js} +11 -63
  72. package/dist/parser-M3T2EWUN.js.map +1 -0
  73. package/dist/pdf-ocr-4S2OAHUX.js +15 -0
  74. package/dist/pdf-ocr-IGYCZ7G2.cjs +16 -0
  75. package/dist/pdf-ocr-IGYCZ7G2.cjs.map +1 -0
  76. package/dist/pdf-ocr-YEBFWU2K.js +19 -0
  77. package/dist/{profile-io-XZPNRT4U.js → profile-io-GQWQR7YQ.js} +3 -3
  78. package/dist/{rasterize-4EWPBCOJ.js → rasterize-Y6TVFQ3M.js} +3 -3
  79. package/dist/{redact-doc-YYCWBWGL.js → redact-doc-TRPZ3HNW.js} +11 -11
  80. package/dist/{render-JNMBK4WF.js → render-GHT5HKBO.js} +7 -7
  81. package/dist/seal-3YTXSABW.js +10 -0
  82. package/dist/{setup-NM5ICZ4U.js → setup-AGSCEHDB.js} +3 -3
  83. package/dist/visual-R3E35Q2K.js +20 -0
  84. package/dist/{watch-PTTCHO32.js → watch-ALSEW4GB.js} +13 -13
  85. package/dist/watch-ALSEW4GB.js.map +1 -0
  86. package/package.json +1 -1
  87. package/dist/chunk-2QBZMCRT.cjs.map +0 -1
  88. package/dist/chunk-CKMGVW6S.cjs.map +0 -1
  89. package/dist/chunk-DSF64VZN.js.map +0 -1
  90. package/dist/chunk-ELTPM4HM.cjs.map +0 -1
  91. package/dist/chunk-F4ESJBGU.cjs.map +0 -1
  92. package/dist/chunk-FPU7WPQM.js.map +0 -1
  93. package/dist/chunk-GP7BPJTU.cjs.map +0 -1
  94. package/dist/chunk-JU4DNVBP.js.map +0 -1
  95. package/dist/chunk-NKCKN4WW.js.map +0 -1
  96. package/dist/chunk-OLOMR452.js.map +0 -1
  97. package/dist/chunk-PQZ4BDXT.js.map +0 -1
  98. package/dist/chunk-QBTYETIU.js.map +0 -1
  99. package/dist/chunk-RLVALQZI.cjs.map +0 -1
  100. package/dist/chunks-WTZTE3OL.js +0 -10
  101. package/dist/formula-43KHT3B3.cjs.map +0 -1
  102. package/dist/image-ocr-22XQ4TMN.cjs.map +0 -1
  103. package/dist/parser-BKTX4GVM.js.map +0 -1
  104. package/dist/parser-L77S4RZC.cjs.map +0 -1
  105. package/dist/parser-WW7NWTUM.js.map +0 -1
  106. package/dist/pdf-ocr-IUTBPKOM.js +0 -15
  107. package/dist/pdf-ocr-RQUU3JBR.js +0 -19
  108. package/dist/pdf-ocr-ZWF6ZXLP.cjs +0 -16
  109. package/dist/pdf-ocr-ZWF6ZXLP.cjs.map +0 -1
  110. package/dist/seal-ZIVUVBLM.js +0 -10
  111. package/dist/visual-4W4RBIOA.js +0 -20
  112. package/dist/watch-PTTCHO32.js.map +0 -1
  113. /package/dist/{-DHSHP3AB.js.map → -I3CP33G4.js.map} +0 -0
  114. /package/dist/{chunk-JUU2M5WI.js.map → chunk-4B2EXELH.js.map} +0 -0
  115. /package/dist/{chunk-NYF7AGOX.js.map → chunk-6OKBTY72.js.map} +0 -0
  116. /package/dist/{chunk-UA7TQ37A.js.map → chunk-BL7KRE6X.js.map} +0 -0
  117. /package/dist/{chunk-PGZX5JWZ.js.map → chunk-C2CVOC5K.js.map} +0 -0
  118. /package/dist/{chunk-WSYTO525.js.map → chunk-CKUIFTXJ.js.map} +0 -0
  119. /package/dist/{chunk-VVJCQKXV.js.map → chunk-GM7XIZF2.js.map} +0 -0
  120. /package/dist/{chunk-Q6SKAFWN.js.map → chunk-L5RLJPI3.js.map} +0 -0
  121. /package/dist/{chunk-LLD4EY2F.js.map → chunk-LACO375L.js.map} +0 -0
  122. /package/dist/{chunk-DT57TAEL.js.map → chunk-LSOD2A6N.js.map} +0 -0
  123. /package/dist/{chunk-5JFKSCTE.js.map → chunk-N5DQFA7L.js.map} +0 -0
  124. /package/dist/{chunk-DTPALXTW.js.map → chunk-NZM2SSI4.js.map} +0 -0
  125. /package/dist/{chunk-GNBQXQDK.js.map → chunk-O2DI4GHD.js.map} +0 -0
  126. /package/dist/{chunk-32FQLCUJ.js.map → chunk-OBILAVWC.js.map} +0 -0
  127. /package/dist/{chunk-WDJJ77HP.js.map → chunk-QP7IXF7R.js.map} +0 -0
  128. /package/dist/{chunk-NIPT5FJO.js.map → chunk-VSY5PAJ5.js.map} +0 -0
  129. /package/dist/{chunks-WTZTE3OL.js.map → chunks-IFFRC5IQ.js.map} +0 -0
  130. /package/dist/{formula-6Q2SBBQ5.js.map → formula-AQZODKZF.js.map} +0 -0
  131. /package/dist/{formula-M6JJBJ3O.js.map → formula-E3N7KDDG.js.map} +0 -0
  132. /package/dist/{image-ocr-2FIFDA7I.js.map → image-ocr-IMYXOHHV.js.map} +0 -0
  133. /package/dist/{image-ocr-TH2E6E3E.js.map → image-ocr-LN3GSQPW.js.map} +0 -0
  134. /package/dist/{model-bundle-MLEDO25V.js.map → model-bundle-IHKNDLOT.js.map} +0 -0
  135. /package/dist/{models-MYB5M4XC.js.map → models-YXITRAMR.js.map} +0 -0
  136. /package/dist/{pdf-ocr-IUTBPKOM.js.map → pdf-ocr-4S2OAHUX.js.map} +0 -0
  137. /package/dist/{pdf-ocr-RQUU3JBR.js.map → pdf-ocr-YEBFWU2K.js.map} +0 -0
  138. /package/dist/{profile-io-XZPNRT4U.js.map → profile-io-GQWQR7YQ.js.map} +0 -0
  139. /package/dist/{rasterize-4EWPBCOJ.js.map → rasterize-Y6TVFQ3M.js.map} +0 -0
  140. /package/dist/{redact-doc-YYCWBWGL.js.map → redact-doc-TRPZ3HNW.js.map} +0 -0
  141. /package/dist/{render-JNMBK4WF.js.map → render-GHT5HKBO.js.map} +0 -0
  142. /package/dist/{seal-ZIVUVBLM.js.map → seal-3YTXSABW.js.map} +0 -0
  143. /package/dist/{setup-NM5ICZ4U.js.map → setup-AGSCEHDB.js.map} +0 -0
  144. /package/dist/{visual-4W4RBIOA.js.map → visual-R3E35Q2K.js.map} +0 -0
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  blocksToPages,
3
3
  inlineImagesIntoMarkdown
4
- } from "./chunk-GNBQXQDK.js";
4
+ } from "./chunk-O2DI4GHD.js";
5
5
  import {
6
6
  parsePageRange
7
7
  } from "./chunk-V4BH5SRQ.js";
@@ -20,6 +20,7 @@ import {
20
20
  escapeGfm,
21
21
  escapeHtmlCellText,
22
22
  escapeLiteralDollar,
23
+ escapeLiteralTags,
23
24
  flattenLayoutTables,
24
25
  hasStructuredCellContent,
25
26
  mapPuaText,
@@ -30,7 +31,7 @@ import {
30
31
  tidyScriptTags,
31
32
  unframeLayoutTables,
32
33
  wrapScript
33
- } from "./chunk-JU4DNVBP.js";
34
+ } from "./chunk-B2E6C2LH.js";
34
35
  import {
35
36
  KordocError,
36
37
  OPTIONAL_DEP_INSTALL_HINT,
@@ -47,17 +48,17 @@ import {
47
48
  toArrayBuffer,
48
49
  unescapeHtml,
49
50
  unzipLimitBytes
50
- } from "./chunk-5JFKSCTE.js";
51
+ } from "./chunk-N5DQFA7L.js";
51
52
 
52
53
  // src/plain-markdown.ts
53
54
  function toPlainMarkdown(md2) {
54
- return plainScripts(md2).replace(/!\[[^\]\n]*\]\([^)\n]*\)/g, "").replace(/<img\b[^>]*>/gi, "").replace(/(?<!!)\[([^\]\n]*)\]\((?:https?|mailto|ftp):[^)\s]*\)/g, "$1").replace(/<\/?u>/g, "").replace(/(?<!\\)\*\*(?=\S)([^\n]*?\S)(?<!\\)\*\*/g, "$1").replace(/(<br>)+(?=<\/t[dh]>)/g, "").replace(/[ \t]+$/gm, "").replace(/\n{3,}/g, "\n\n").trim();
55
+ return plainScripts(md2).replace(/!\[[^\]\n]*\]\([^)\n]*\)/g, "").replace(/<img\b[^>]*>/gi, "").replace(/(?<!!)\[([^\]\n]*)\]\((?:https?|mailto|ftp):[^)\s]*\)/g, "$1").replace(/(?<!\\)<\/?u>/g, "").replace(/(?<!\\)\*\*(?=\S)([^\n]*?\S)(?<!\\)\*\*/g, "$1").replace(/(<br>)+(?=<\/t[dh]>)/g, "").replace(/[ \t]+$/gm, "").replace(/\n{3,}/g, "\n\n").trim();
55
56
  }
56
57
 
57
58
  // src/html-tables.ts
58
59
  var TABLE_TAG = /(<\/?(?:table|tr|th|td)\b[^>]*>)/i;
59
60
  function pipeCellToHtml(cell2) {
60
- return cell2.split(/(<br>|<\/?(?:u|sup|sub)>|<img\b[^>]*>)/).map((part, i) => i % 2 ? part : part.replace(/\\([\\`*_{}[\]()#+\-.!|~<>$])/g, "$1").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")).join("");
61
+ return cell2.split(/(<br>|(?<!\\)<\/?(?:u|sup|sub)>|<img\b[^>]*>)/).map((part, i) => i % 2 ? part : part.replace(/\\([\\`*_{}[\]()#+\-.!|~<>$])/g, "$1").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")).join("");
61
62
  }
62
63
  function splitPipeRow(line) {
63
64
  const t = line.trim().replace(/^\|/, "").replace(/(?<!\\)\|$/, "");
@@ -688,6 +689,21 @@ async function decryptHwpxInPlace(zip, manifestXml, password) {
688
689
  return decrypted.length;
689
690
  }
690
691
 
692
+ // src/toc-entry.ts
693
+ var TOC_LEADER = "";
694
+ var LEADERS = /\x1F/g;
695
+ var PAGE_LABEL = /^(?:\d{1,4}|[ivxlc]{1,7}|\d{1,3}\s*[-–]\s*\d{1,4}|[-–]\s*\d{1,4}\s*[-–])$/i;
696
+ var TOC_ENTRY_BLOCKS = /* @__PURE__ */ new WeakSet();
697
+ function takeTocLeaders(text) {
698
+ if (!text.includes(TOC_LEADER)) return { text, tocEntry: false };
699
+ const lines = text.replace(/\x1E/g, "").split("\n").filter((l) => l.trim());
700
+ const tocEntry = lines.length > 0 && lines.every((l) => {
701
+ const at = l.lastIndexOf(TOC_LEADER);
702
+ return at > 0 && /\S/.test(l.slice(0, at).replace(LEADERS, "")) && PAGE_LABEL.test(l.slice(at + 1).trim());
703
+ });
704
+ return { text: text.replace(LEADERS, " "), tocEntry };
705
+ }
706
+
691
707
  // src/hwpx/styles.ts
692
708
  function isRealStrikeShape(shape) {
693
709
  switch (shape) {
@@ -938,7 +954,7 @@ function detectHwpxHeadings(blocks, styleMap) {
938
954
  }
939
955
  }
940
956
  for (const block of blocks) {
941
- if (block.type !== "paragraph" || !block.text) continue;
957
+ if (block.type !== "paragraph" || !block.text || TOC_ENTRY_BLOCKS.has(block)) continue;
942
958
  const text = block.text.trim();
943
959
  if (text.length === 0 || text.length > 200 || /^\d+$/.test(text)) continue;
944
960
  let level = 0;
@@ -1991,7 +2007,7 @@ function extractRunSpans(para2, ctx, mode, requireMixed) {
1991
2007
  const tkids = rc.childNodes;
1992
2008
  for (let k = 0; k < (tkids?.length ?? 0); k++) {
1993
2009
  const tk = tkids[k];
1994
- if (tk.nodeType === 3 || tk.nodeType === 4) text += escapeLiteralDollar(tk.textContent || "");
2010
+ if (tk.nodeType === 3 || tk.nodeType === 4) text += escapeLiteralTags(escapeLiteralDollar(tk.textContent || ""));
1995
2011
  else if (tk.nodeType === 1) {
1996
2012
  const ttag = (tk.tagName || tk.localName || "").replace(/^[^:]+:/, "");
1997
2013
  if (ttag === "tab" || ttag === "fwSpace" || ttag === "hwSpace" || ttag === "nbSpace") text += " ";
@@ -2164,13 +2180,13 @@ function walkSection(node, blocks, tableCtx, tableStack, ctx, depth = 0) {
2164
2180
  case "p": {
2165
2181
  const paraPg = ctx.paraPage?.get(el);
2166
2182
  if (paraPg !== void 0 && ctx.pageBase !== void 0) ctx.page = ctx.pageBase + paraPg + 1;
2167
- const { text: rawText, href, footnote, style, segments, placeholderSpans } = extractParagraphInfo(el, ctx.styleMap, ctx);
2183
+ const { text: rawText, href, footnote, style, segments, placeholderSpans, tocEntry } = extractParagraphInfo(el, ctx.styleMap, ctx);
2168
2184
  let text = rawText;
2169
2185
  let headingLevel;
2170
2186
  const ph = resolveParaHeading(el, ctx);
2171
2187
  if (text) {
2172
2188
  if (ph?.prefix) text = ph.prefix + " " + text;
2173
- headingLevel = ph?.headingLevel;
2189
+ headingLevel = tocEntry ? void 0 : ph?.headingLevel;
2174
2190
  }
2175
2191
  if (segments) {
2176
2192
  const cell2 = tableCtx?.cell ?? null;
@@ -2232,6 +2248,7 @@ function walkSection(node, blocks, tableCtx, tableStack, ctx, depth = 0) {
2232
2248
  }
2233
2249
  const block = { type: headingLevel ? "heading" : "paragraph", text, pageNumber: ctx.page };
2234
2250
  if (headingLevel) block.level = headingLevel;
2251
+ if (tocEntry) TOC_ENTRY_BLOCKS.add(block);
2235
2252
  if (style) block.style = style;
2236
2253
  if (href) block.href = href;
2237
2254
  if (footnote) block.footnoteText = footnote;
@@ -2664,7 +2681,7 @@ function extractParagraphInfo(para2, styleMap, ctx) {
2664
2681
  const open = openFields.pop();
2665
2682
  if (open?.rangeIdx !== void 0) linkRanges[open.rangeIdx].end = text.length;
2666
2683
  if (open?.guide !== void 0 && open.start !== void 0) {
2667
- const value = text.slice(open.start).replace(/\\\$/g, "$");
2684
+ const value = text.slice(open.start).replace(/\\([$<])/g, "$1");
2668
2685
  if (value && (value === open.guide || value.trimEnd() === open.guide)) {
2669
2686
  text = text.slice(0, open.start) + PH_OPEN + text.slice(open.start) + PH_CLOSE;
2670
2687
  }
@@ -2773,7 +2790,7 @@ function extractParagraphInfo(para2, styleMap, ctx) {
2773
2790
  ctx.warnings?.push({ page: ctx.page, message: "\uBCC0\uACBD\uCD94\uC801 \uC0AD\uC81C \uD14D\uC2A4\uD2B8 \uCD9C\uB825 \uC81C\uC678", code: "HIDDEN_TEXT_FILTERED" });
2774
2791
  }
2775
2792
  } else {
2776
- text += escapeLiteralDollar(t.replace(/\x1E/g, ""));
2793
+ text += escapeLiteralTags(escapeLiteralDollar(t.replace(/\x1E/g, "")));
2777
2794
  }
2778
2795
  continue;
2779
2796
  }
@@ -2793,8 +2810,9 @@ function extractParagraphInfo(para2, styleMap, ctx) {
2793
2810
  // 점 채움을 탭 하나로 바꾸고 쪽 번호를 남기는 것과 같다). 종전엔 채움 탭 뒤를 쪽 번호로 보고 잘라, 목차 쪽 번호만이 아니라
2794
2811
  // 일정표 "사용자 의견조사 ····· '26년 8~9월"(gate-fill 36646162)의 일정, 줄바꿈 뒤 다음 목차 항목
2795
2812
  // "<참고2> 직종별사업체노동력조사 개요"(rhwp issue6044)까지 사라졌다
2813
+ // (채움 탭은 목차 항목 판정까지 TOC_LEADER 표지로 들고 가다 보통 탭으로 바꾼다 — toc-entry.ts)
2796
2814
  case "tab":
2797
- text += " ";
2815
+ text += /^(?:|0|NONE)$/i.test(child.getAttribute("leader") ?? "") ? " " : TOC_LEADER;
2798
2816
  break;
2799
2817
  case "br":
2800
2818
  if ((child.getAttribute("type") || "line") === "line") text += "\n";
@@ -2941,6 +2959,9 @@ function extractParagraphInfo(para2, styleMap, ctx) {
2941
2959
  if (applied.length) href = void 0;
2942
2960
  }
2943
2961
  text = tidyScriptTags(text);
2962
+ const toc = takeTocLeaders(text);
2963
+ text = toc.text;
2964
+ const tocEntry = toc.tocEntry;
2944
2965
  const cleanParaText = (raw) => {
2945
2966
  let t = raw.replace(/[ \t]+/g, " ").trim();
2946
2967
  if (/^그림입니다\.?\s*원본\s*그림의\s*(이름|크기)/.test(t)) t = "";
@@ -2976,7 +2997,7 @@ function extractParagraphInfo(para2, styleMap, ctx) {
2976
2997
  if (!style.fontSize && !style.bold && !style.italic) style = void 0;
2977
2998
  }
2978
2999
  }
2979
- return { text: cleanText, href, footnote, style, segments, placeholderSpans };
3000
+ return { text: cleanText, href, footnote, style, segments, placeholderSpans, ...tocEntry ? { tocEntry } : {} };
2980
3001
  }
2981
3002
  function withPrefixSpan(spans, prefix) {
2982
3003
  return prefix ? [{ text: prefix + " " }, ...spans] : spans;
@@ -3180,6 +3201,11 @@ function parseDocInfo(records) {
3180
3201
  return { charShapes, paraShapes, styles, binData, numberings, bullets, borderEdges };
3181
3202
  }
3182
3203
  var LITERAL_DOLLAR_MARK = "\uFDD0";
3204
+ var LITERAL_LT_MARK = "\uFDD1";
3205
+ var LITERAL_LT_TAG = /\uFDD1(?=\/?(?:u|sup|sub)>)/g;
3206
+ function resolveLiteralLt(text) {
3207
+ return text.includes(LITERAL_LT_MARK) ? text.replace(LITERAL_LT_TAG, "\\<").replaceAll(LITERAL_LT_MARK, "<") : text;
3208
+ }
3183
3209
  function createParaTextState() {
3184
3210
  return { text: "", ctrlIdx: 0, fieldStack: [], fieldRanges: [] };
3185
3211
  }
@@ -3244,8 +3270,9 @@ function appendParaText(state, data, resolveControl) {
3244
3270
  // ── inline 타입 (2바이트 + 14바이트 확장) ──
3245
3271
  // 확장 u16[7] 중 [2] 의 하위 바이트 = 채움 모양(0 없음·3 점선 …), 상위 = 탭 종류+1 (rhwp tab_extended 실측).
3246
3272
  // 채움 탭도 보통 탭 — 채움선은 글이 아니고 뒤 글(목차 쪽 번호 등)은 남긴다 (HWPX section-walker "tab" 과 같은 정책)
3273
+ // 본문 파서(tocLeaders)는 목차 항목 판정까지 채움 탭을 TOC_LEADER 표지로 받는다 (#121)
3247
3274
  case CHAR_TAB:
3248
- result += " ";
3275
+ result += state.tocLeaders && i + 6 <= data.length && (data.readUInt16LE(i + 4) & 255) !== 0 ? TOC_LEADER : " ";
3249
3276
  if (i + 14 <= data.length) i += 14;
3250
3277
  break;
3251
3278
  default:
@@ -3278,7 +3305,7 @@ function appendParaText(state, data, resolveControl) {
3278
3305
  break;
3279
3306
  }
3280
3307
  }
3281
- result += ch === 36 && state.dollarMark ? LITERAL_DOLLAR_MARK : String.fromCharCode(ch);
3308
+ result += !state.dollarMark ? String.fromCharCode(ch) : ch === 36 ? LITERAL_DOLLAR_MARK : ch === 60 ? LITERAL_LT_MARK : String.fromCharCode(ch);
3282
3309
  }
3283
3310
  break;
3284
3311
  }
@@ -5135,6 +5162,7 @@ function emitParagraphBlocks(p) {
5135
5162
  pageNumber
5136
5163
  };
5137
5164
  if (p.headingLevel > 0) block.level = p.headingLevel;
5165
+ if (p.tocEntry) TOC_ENTRY_BLOCKS.add(block);
5138
5166
  if (style) {
5139
5167
  block.style = style;
5140
5168
  if (style.strike || style.underline) block.text = headMarker ? `${headMarker} ${decorate(trimmed)}` : decorate(trimmed);
@@ -5410,6 +5438,7 @@ function parseParagraph(records, start, end, ctx) {
5410
5438
  }
5411
5439
  const state = createParaTextState();
5412
5440
  state.dollarMark = true;
5441
+ state.tocLeaders = true;
5413
5442
  state.scriptAt = scriptLookup(charShapeRuns, ctx.docInfo);
5414
5443
  const resolver = (idx, id) => {
5415
5444
  let ctrl = idx >= 0 && idx < ctrls.length ? ctrls[idx] : void 0;
@@ -5434,7 +5463,7 @@ function parseParagraph(records, start, end, ctx) {
5434
5463
  const anchor = text.slice(r.start, r.end);
5435
5464
  if (ctrl.guide !== void 0) {
5436
5465
  if (ctx.doc.includeFieldPlaceholders) continue;
5437
- const plain4 = anchor.replaceAll(LITERAL_DOLLAR_MARK, "$");
5466
+ const plain4 = anchor.replaceAll(LITERAL_DOLLAR_MARK, "$").replaceAll(LITERAL_LT_MARK, "<");
5438
5467
  if (plain4 === ctrl.guide || plain4.trimEnd() === ctrl.guide) {
5439
5468
  text = text.slice(0, r.start) + text.slice(r.end);
5440
5469
  applied.push([r.start, r.end]);
@@ -5450,13 +5479,16 @@ function parseParagraph(records, start, end, ctx) {
5450
5479
  }
5451
5480
  }
5452
5481
  if (text.includes(LITERAL_DOLLAR_MARK)) text = text.replaceAll(LITERAL_DOLLAR_MARK, "\\$");
5482
+ text = resolveLiteralLt(text);
5453
5483
  text = tidyScriptTags(text);
5484
+ const toc = takeTocLeaders(text);
5485
+ text = toc.text;
5454
5486
  let headingLevel = 0;
5455
5487
  let headMarker = null;
5456
5488
  const ps = ctx.docInfo && paraShapeId >= 0 && paraShapeId < ctx.docInfo.paraShapes.length ? ctx.docInfo.paraShapes[paraShapeId] : null;
5457
5489
  if (ps && ps.headType > 0) {
5458
5490
  if (ps.headType === 1) {
5459
- headingLevel = Math.min(ps.paraLevel + 1, 6);
5491
+ if (!toc.tocEntry) headingLevel = Math.min(ps.paraLevel + 1, 6);
5460
5492
  }
5461
5493
  if (ps.headType === 1 || ps.headType === 2) {
5462
5494
  const nid = ps.numberingId || (ps.headType === 1 ? ctx.doc.outlineNumberingId : 0);
@@ -5478,6 +5510,7 @@ function parseParagraph(records, start, end, ctx) {
5478
5510
  text,
5479
5511
  headMarker,
5480
5512
  headingLevel,
5513
+ tocEntry: toc.tocEntry,
5481
5514
  style: ctx.docInfo && charShapeIds.length > 0 ? resolveCharStyle(charShapeIds, ctx.docInfo) : void 0,
5482
5515
  footnotes: ctrls.filter((c) => c.footnote).map((c) => c.footnote),
5483
5516
  // 컨트롤 파생 블록 (표/이미지/글상자) — 컨트롤 순서대로
@@ -6095,7 +6128,7 @@ function detectHwp5Headings(blocks, docInfo) {
6095
6128
  if (baseFontSize <= 0) return;
6096
6129
  for (const block of blocks) {
6097
6130
  if (block.type === "heading") continue;
6098
- if (block.type !== "paragraph" || !block.text) continue;
6131
+ if (block.type !== "paragraph" || !block.text || TOC_ENTRY_BLOCKS.has(block)) continue;
6099
6132
  const text = block.text.trim();
6100
6133
  if (text.length === 0 || text.length > 200) continue;
6101
6134
  if (/^\d+$/.test(text)) continue;
@@ -19006,7 +19039,7 @@ function parseCharStream(reader, charCount, ctx, para2) {
19006
19039
  continue;
19007
19040
  }
19008
19041
  if (ch >= 32) {
19009
- para2.text += escapeLiteralDollar(decodeJohabText(ch) ?? "");
19042
+ para2.text += escapeLiteralTags(escapeLiteralDollar(decodeJohabText(ch) ?? ""));
19010
19043
  continue;
19011
19044
  }
19012
19045
  if (ch === 9) {
@@ -19354,7 +19387,7 @@ function collectRichText(root) {
19354
19387
  const el = children[i];
19355
19388
  const local = el.localName || el.tagName?.replace(/^[^:]+:/, "") || "";
19356
19389
  if (local === "rPh") continue;
19357
- if (local === "t") out += el.textContent ?? "";
19390
+ if (local === "t") out += escapeLiteralTags(el.textContent ?? "");
19358
19391
  else walk(el);
19359
19392
  }
19360
19393
  };
@@ -19978,14 +20011,14 @@ function extractSheetCells(records, bofIndex, sst, convertNum) {
19978
20011
  const h = readCellHeader(rec.data);
19979
20012
  if (h && rec.data.length >= 10) {
19980
20013
  const isst = rec.data.readUInt32LE(6);
19981
- cells.push({ row: h.row, col: h.col, value: sst[isst] ?? "" });
20014
+ cells.push({ row: h.row, col: h.col, value: escapeLiteralTags(sst[isst] ?? "") });
19982
20015
  }
19983
20016
  break;
19984
20017
  }
19985
20018
  case OP_LABEL: {
19986
20019
  const h = readCellHeader(rec.data);
19987
20020
  if (h) {
19988
- cells.push({ row: h.row, col: h.col, value: decodeLabelString(rec.data) });
20021
+ cells.push({ row: h.row, col: h.col, value: escapeLiteralTags(decodeLabelString(rec.data)) });
19989
20022
  }
19990
20023
  break;
19991
20024
  }
@@ -20001,7 +20034,7 @@ function extractSheetCells(records, bofIndex, sst, convertNum) {
20001
20034
  cells.push({
20002
20035
  row: h.row,
20003
20036
  col: h.col,
20004
- value: decodeFormulaStringRecord(next.data)
20037
+ value: escapeLiteralTags(decodeFormulaStringRecord(next.data))
20005
20038
  });
20006
20039
  i = j;
20007
20040
  } else {
@@ -20904,7 +20937,7 @@ function parseFootnotes(xml) {
20904
20937
  const runs = findElements(p, "r");
20905
20938
  for (const r of runs) {
20906
20939
  const tElements = getChildElements(r, "t");
20907
- for (const t of tElements) texts.push(t.textContent ?? "");
20940
+ for (const t of tElements) texts.push(escapeLiteralTags(t.textContent ?? ""));
20908
20941
  }
20909
20942
  }
20910
20943
  notes.set(id, texts.join("").trim());
@@ -20935,7 +20968,7 @@ function collectOmmlRoots(p) {
20935
20968
  function extractRun(r) {
20936
20969
  let text = "";
20937
20970
  for (const el of effectiveChildElements(r)) {
20938
- if (matchesLocal(el, "t")) text += el.textContent ?? "";
20971
+ if (matchesLocal(el, "t")) text += escapeLiteralTags(el.textContent ?? "");
20939
20972
  else if (matchesLocal(el, "br") || matchesLocal(el, "cr")) text += "\n";
20940
20973
  else if (matchesLocal(el, "tab")) text += " ";
20941
20974
  else if (matchesLocal(el, "sym")) text += symbolChar(getAttr(el, "font"), getAttr(el, "char"));
@@ -21446,8 +21479,323 @@ async function parseDocxDocument(buffer, options) {
21446
21479
  };
21447
21480
  }
21448
21481
 
21449
- // src/hwpml/parser.ts
21482
+ // src/pptx/parser.ts
21483
+ import JSZip6 from "jszip";
21450
21484
  import { DOMParser as DOMParser4 } from "@xmldom/xmldom";
21485
+ var MAX_DECOMPRESS_SIZE6 = unzipLimitBytes(100 * 1024 * 1024);
21486
+ var MEDIA_PART_RE2 = /^ppt\/(?:media|embeddings)\//;
21487
+ var MAX_ZIP_ENTRIES2 = 1e4;
21488
+ var MAX_SLIDES = 2e3;
21489
+ var SKIP_PLACEHOLDERS = /* @__PURE__ */ new Set(["sldNum", "dt", "ftr", "hdr", "sldImg"]);
21490
+ var BULLET_PLACEHOLDERS = /* @__PURE__ */ new Set(["body", "obj"]);
21491
+ var REL_NOTES = "/notesSlide";
21492
+ var REL_IMAGE = "/image";
21493
+ var SLIDE_PART_RE = /^ppt\/slides\/slide(\d+)\.xml$/;
21494
+ var IMAGE_MIME = {
21495
+ png: "image/png",
21496
+ jpg: "image/jpeg",
21497
+ jpeg: "image/jpeg",
21498
+ gif: "image/gif",
21499
+ bmp: "image/bmp",
21500
+ wmf: "image/wmf",
21501
+ emf: "image/emf",
21502
+ tif: "image/tiff",
21503
+ tiff: "image/tiff",
21504
+ svg: "image/svg+xml"
21505
+ };
21506
+ function parseXml3(text) {
21507
+ return new DOMParser4().parseFromString(stripDtd(text), "text/xml");
21508
+ }
21509
+ function getAttr2(el, name) {
21510
+ const attrs = el.attributes;
21511
+ for (let i = 0; i < attrs.length; i++) {
21512
+ const attr = attrs[i];
21513
+ if (attr.localName === name || attr.name === name) return attr.value;
21514
+ }
21515
+ return null;
21516
+ }
21517
+ var NS_REL = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
21518
+ function getRelId(el) {
21519
+ const attrs = el.attributes;
21520
+ for (let i = 0; i < attrs.length; i++) {
21521
+ const attr = attrs[i];
21522
+ if (attr.localName === "id" && (attr.namespaceURI === NS_REL || attr.name === "r:id")) return attr.value;
21523
+ }
21524
+ return null;
21525
+ }
21526
+ function findPath(el, ...names) {
21527
+ for (const name of names) {
21528
+ if (!el) return null;
21529
+ el = findChildByLocalName(el, name);
21530
+ }
21531
+ return el;
21532
+ }
21533
+ function resolvePart(baseDir, target) {
21534
+ if (target.startsWith("/")) return target.slice(1);
21535
+ const parts = baseDir ? baseDir.split("/") : [];
21536
+ for (const seg of target.split("/")) {
21537
+ if (seg === "..") parts.pop();
21538
+ else if (seg && seg !== ".") parts.push(seg);
21539
+ }
21540
+ return parts.join("/");
21541
+ }
21542
+ async function readRels(zip, part) {
21543
+ const map = /* @__PURE__ */ new Map();
21544
+ const slash = part.lastIndexOf("/");
21545
+ const dir = slash >= 0 ? part.slice(0, slash) : "";
21546
+ const file = zip.file(`${dir ? dir + "/" : ""}_rels/${part.slice(slash + 1)}.rels`);
21547
+ if (!file) return map;
21548
+ const doc = parseXml3(await file.async("text"));
21549
+ for (const rel of childrenByLocalName(doc.documentElement, "Relationship")) {
21550
+ const id = getAttr2(rel, "Id");
21551
+ const target = getAttr2(rel, "Target");
21552
+ if (!id || !target || getAttr2(rel, "TargetMode") === "External") continue;
21553
+ map.set(id, { type: getAttr2(rel, "Type") ?? "", target: resolvePart(dir, target) });
21554
+ }
21555
+ return map;
21556
+ }
21557
+ async function slideParts(zip) {
21558
+ const ordered = [];
21559
+ const presFile = zip.file("ppt/presentation.xml");
21560
+ if (presFile) {
21561
+ const pres = parseXml3(await presFile.async("text"));
21562
+ const rels = await readRels(zip, "ppt/presentation.xml");
21563
+ const list = findChildByLocalName(pres.documentElement, "sldIdLst");
21564
+ for (const sld of list ? childrenByLocalName(list, "sldId") : []) {
21565
+ const target = rels.get(getRelId(sld) ?? "")?.target;
21566
+ if (target && zip.file(target)) ordered.push(target);
21567
+ }
21568
+ }
21569
+ if (ordered.length === 0) {
21570
+ const names = Object.keys(zip.files).filter((n) => SLIDE_PART_RE.test(n));
21571
+ names.sort((a, b) => Number(SLIDE_PART_RE.exec(a)[1]) - Number(SLIDE_PART_RE.exec(b)[1]));
21572
+ ordered.push(...names);
21573
+ }
21574
+ if (ordered.length > MAX_SLIDES) throw new KordocError(`PPTX \uC2AC\uB77C\uC774\uB4DC \uC218 \uCD08\uACFC: ${ordered.length} (\uCD5C\uB300 ${MAX_SLIDES})`);
21575
+ return ordered;
21576
+ }
21577
+ function paragraphText(p) {
21578
+ let text = "";
21579
+ for (const child of elementChildren(p)) {
21580
+ const name = localName(child);
21581
+ if (name === "r" || name === "fld") {
21582
+ const t = findChildByLocalName(child, "t");
21583
+ if (t) text += escapeLiteralTags(escapeLiteralDollar(rawTextContent(t)));
21584
+ } else if (name === "br") {
21585
+ text += " ";
21586
+ }
21587
+ }
21588
+ return text.replace(/\s+/g, " ").trim();
21589
+ }
21590
+ function bodyParagraphs(txBody) {
21591
+ const out = [];
21592
+ for (const p of childrenByLocalName(txBody, "p")) {
21593
+ const text = paragraphText(p);
21594
+ if (!text) continue;
21595
+ const pPr = findChildByLocalName(p, "pPr");
21596
+ let bullet;
21597
+ if (pPr) {
21598
+ if (findChildByLocalName(pPr, "buNone")) bullet = "none";
21599
+ else if (findChildByLocalName(pPr, "buAutoNum")) bullet = "ordered";
21600
+ else if (findChildByLocalName(pPr, "buChar") || findChildByLocalName(pPr, "buBlip")) bullet = "unordered";
21601
+ }
21602
+ const lvl = Math.max(0, parseInt(pPr ? getAttr2(pPr, "lvl") ?? "0" : "0", 10) || 0);
21603
+ out.push({ text, lvl, bullet });
21604
+ }
21605
+ return out;
21606
+ }
21607
+ function placeholderType(sp) {
21608
+ const ph = findPath(sp, "nvSpPr", "nvPr", "ph");
21609
+ return ph ? getAttr2(ph, "type") ?? "obj" : null;
21610
+ }
21611
+ function pushParagraphs(paras, bulletDefault, page, out) {
21612
+ let lastList = null;
21613
+ for (const para2 of paras) {
21614
+ const bullet = para2.bullet ?? (bulletDefault ? "unordered" : "none");
21615
+ if (bullet === "none") {
21616
+ out.push({ type: "paragraph", text: para2.text, pageNumber: page });
21617
+ lastList = null;
21618
+ continue;
21619
+ }
21620
+ const item = { type: "list", text: para2.text, listType: bullet, pageNumber: page };
21621
+ if (para2.lvl > 0 && lastList) {
21622
+ ;
21623
+ (lastList.children ??= []).push(item);
21624
+ } else {
21625
+ out.push(item);
21626
+ lastList = item;
21627
+ }
21628
+ }
21629
+ }
21630
+ function parseTable2(tbl2, page, keepEmptyCols) {
21631
+ const rows = childrenByLocalName(tbl2, "tr");
21632
+ if (rows.length === 0) return null;
21633
+ const cellRows = rows.map((tr, r) => {
21634
+ const cells = [];
21635
+ childrenByLocalName(tr, "tc").forEach((tc2, c) => {
21636
+ if (getAttr2(tc2, "hMerge") === "1" || getAttr2(tc2, "vMerge") === "1") return;
21637
+ const txBody = findChildByLocalName(tc2, "txBody");
21638
+ const text = txBody ? bodyParagraphs(txBody).map((p) => p.text).join("\n") : "";
21639
+ cells.push({
21640
+ text,
21641
+ colSpan: Math.max(1, parseInt(getAttr2(tc2, "gridSpan") ?? "1", 10) || 1),
21642
+ rowSpan: Math.max(1, parseInt(getAttr2(tc2, "rowSpan") ?? "1", 10) || 1),
21643
+ colAddr: c,
21644
+ rowAddr: r
21645
+ });
21646
+ });
21647
+ return cells;
21648
+ });
21649
+ const table2 = buildTable(cellRows, { keepAnchoredEmptyCols: keepEmptyCols });
21650
+ if (table2.rows === 0 || table2.cols === 0) return null;
21651
+ return { type: "table", table: table2, pageNumber: page };
21652
+ }
21653
+ async function pictureBlock(pic, ctx) {
21654
+ const blip = findPath(pic, "blipFill", "blip");
21655
+ const embed = blip ? getAttr2(blip, "embed") : null;
21656
+ const rel = embed ? ctx.rels.get(embed) : void 0;
21657
+ if (!rel || !rel.type.endsWith(REL_IMAGE)) return null;
21658
+ let filename = ctx.imageNames.get(rel.target);
21659
+ if (!filename) {
21660
+ const file = ctx.zip.file(rel.target);
21661
+ if (!file) return null;
21662
+ const ext = rel.target.split(".").pop()?.toLowerCase() ?? "png";
21663
+ filename = `image_${String(ctx.imageNames.size + 1).padStart(3, "0")}.${ext}`;
21664
+ try {
21665
+ if (ctx.options?.images !== false) {
21666
+ const data = await file.async("uint8array");
21667
+ ctx.images.push({ filename, data, mimeType: IMAGE_MIME[ext] ?? detectImageMime(data) ?? "application/octet-stream", source: rel.target });
21668
+ }
21669
+ } catch (err) {
21670
+ ctx.warnings.push({
21671
+ page: ctx.page,
21672
+ code: "SKIPPED_IMAGE",
21673
+ message: `PPTX \uC774\uBBF8\uC9C0 \uCD94\uCD9C \uC2E4\uD328 (${rel.target}): ${err instanceof Error ? err.message : String(err)}`
21674
+ });
21675
+ return null;
21676
+ }
21677
+ ctx.imageNames.set(rel.target, filename);
21678
+ }
21679
+ return { type: "image", text: filename, pageNumber: ctx.page };
21680
+ }
21681
+ async function walkShapes(tree, ctx, out, depth = 0) {
21682
+ if (depth > MAX_XML_DEPTH) return;
21683
+ for (const el of elementChildren(tree)) {
21684
+ switch (localName(el)) {
21685
+ case "grpSp":
21686
+ await walkShapes(el, ctx, out, depth + 1);
21687
+ break;
21688
+ case "AlternateContent": {
21689
+ const choice = findChildByLocalName(el, "Choice");
21690
+ if (choice) await walkShapes(choice, ctx, out, depth + 1);
21691
+ break;
21692
+ }
21693
+ case "sp": {
21694
+ const ph = placeholderType(el);
21695
+ const txBody = findChildByLocalName(el, "txBody");
21696
+ if (ph && SKIP_PLACEHOLDERS.has(ph) || !txBody) break;
21697
+ const paras = bodyParagraphs(txBody);
21698
+ if (paras.length === 0) break;
21699
+ if (ph === "title" || ph === "ctrTitle") {
21700
+ out.push({ type: "heading", level: ph === "ctrTitle" ? 1 : 2, text: paras.map((p) => p.text).join(" "), pageNumber: ctx.page });
21701
+ } else {
21702
+ pushParagraphs(paras, ph !== null && BULLET_PLACEHOLDERS.has(ph), ctx.page, out);
21703
+ }
21704
+ break;
21705
+ }
21706
+ case "graphicFrame": {
21707
+ const data = findPath(el, "graphic", "graphicData");
21708
+ const tbl2 = data ? findChildByLocalName(data, "tbl") : null;
21709
+ if (tbl2) {
21710
+ const block = parseTable2(tbl2, ctx.page, ctx.options?.keepTrailingEmptyCols);
21711
+ if (block) out.push(block);
21712
+ } else if (data) {
21713
+ const uri = getAttr2(data, "uri") ?? "";
21714
+ const kind = /chart/i.test(uri) ? "\uCC28\uD2B8" : /diagram/i.test(uri) ? "SmartArt" : "\uAC1C\uCCB4";
21715
+ ctx.warnings.push({ page: ctx.page, code: "UNSUPPORTED_ELEMENT", message: `PPTX ${kind}\uC758 \uAE00\uC740 \uC77D\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 (${ctx.page}\uCABD)` });
21716
+ }
21717
+ break;
21718
+ }
21719
+ case "pic": {
21720
+ const block = await pictureBlock(el, ctx);
21721
+ if (block) out.push(block);
21722
+ break;
21723
+ }
21724
+ }
21725
+ }
21726
+ }
21727
+ async function notesText(zip, rels) {
21728
+ const rel = [...rels.values()].find((r) => r.type.endsWith(REL_NOTES));
21729
+ const file = rel ? zip.file(rel.target) : null;
21730
+ if (!file) return "";
21731
+ const tree = findPath(parseXml3(await file.async("text")).documentElement, "cSld", "spTree");
21732
+ const texts = [];
21733
+ for (const sp of tree ? childrenByLocalName(tree, "sp") : []) {
21734
+ const txBody = findChildByLocalName(sp, "txBody");
21735
+ if (placeholderType(sp) === "body" && txBody) texts.push(...bodyParagraphs(txBody).map((p) => p.text));
21736
+ }
21737
+ return texts.join(" ");
21738
+ }
21739
+ async function parsePptxDocument(buffer, options) {
21740
+ precheckZipSize(buffer, MAX_DECOMPRESS_SIZE6, MAX_ZIP_ENTRIES2, { re: MEDIA_PART_RE2, skip: options?.images === false });
21741
+ const zip = await JSZip6.loadAsync(buffer);
21742
+ if (!zip.file("ppt/presentation.xml")) {
21743
+ throw new KordocError("\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 PPTX \uD30C\uC77C: ppt/presentation.xml\uC774 \uC5C6\uC2B5\uB2C8\uB2E4");
21744
+ }
21745
+ const warnings = [];
21746
+ const blocks = [];
21747
+ const imageNames = /* @__PURE__ */ new Map();
21748
+ const images = [];
21749
+ const slides = await slideParts(zip);
21750
+ for (let i = 0; i < slides.length; i++) {
21751
+ const page = i + 1;
21752
+ const root = parseXml3(await zip.file(slides[i]).async("text")).documentElement;
21753
+ if (getAttr2(root, "show") === "0") {
21754
+ warnings.push({ page, code: "HIDDEN_TEXT_FILTERED", message: `PPTX \uC228\uAE34 \uC2AC\uB77C\uC774\uB4DC ${page}\uCABD\uC744 \uAC74\uB108\uB701\uB2C8\uB2E4` });
21755
+ continue;
21756
+ }
21757
+ const rels = await readRels(zip, slides[i]);
21758
+ const tree = findPath(root, "cSld", "spTree");
21759
+ if (tree) await walkShapes(tree, { zip, rels, page, warnings, options, imageNames, images }, blocks);
21760
+ const notes = await notesText(zip, rels);
21761
+ if (notes) blocks.push({ type: "paragraph", text: `\uBC1C\uD45C\uC790 \uB178\uD2B8: ${notes}`, quote: true, pageNumber: page });
21762
+ }
21763
+ const metadata = { pageCount: slides.length, pageMode: "layout" };
21764
+ const coreFile = zip.file("docProps/core.xml");
21765
+ if (coreFile) {
21766
+ try {
21767
+ const coreDoc = parseXml3(await coreFile.async("text"));
21768
+ const getFirst = (tag) => {
21769
+ const els = coreDoc.getElementsByTagName(tag);
21770
+ return els.length > 0 ? (els[0].textContent ?? "").trim() || void 0 : void 0;
21771
+ };
21772
+ metadata.title = getFirst("dc:title") || getFirst("dcterms:title");
21773
+ metadata.author = getFirst("dc:creator");
21774
+ metadata.description = getFirst("dc:description");
21775
+ const created = getFirst("dcterms:created");
21776
+ if (created) metadata.createdAt = created;
21777
+ const modified = getFirst("dcterms:modified");
21778
+ if (modified) metadata.modifiedAt = modified;
21779
+ } catch (err) {
21780
+ warnings.push({
21781
+ code: "PARTIAL_PARSE",
21782
+ message: `PPTX \uBA54\uD0C0\uB370\uC774\uD130(core.xml) \uD30C\uC2F1 \uC2E4\uD328: ${err instanceof Error ? err.message : String(err)}`
21783
+ });
21784
+ }
21785
+ }
21786
+ const outline = blocks.filter((b) => b.type === "heading").map((b) => ({ level: b.level ?? 2, text: b.text ?? "", pageNumber: b.pageNumber }));
21787
+ return {
21788
+ markdown: blocksToMarkdown(blocks),
21789
+ blocks,
21790
+ metadata,
21791
+ outline: outline.length > 0 ? outline : void 0,
21792
+ warnings: warnings.length > 0 ? warnings : void 0,
21793
+ images: images.length > 0 ? images : void 0
21794
+ };
21795
+ }
21796
+
21797
+ // src/hwpml/parser.ts
21798
+ import { DOMParser as DOMParser5 } from "@xmldom/xmldom";
21451
21799
  var MAX_TABLE_ROWS = 5e3;
21452
21800
  var MAX_TABLE_COLS = 500;
21453
21801
  var MAX_HWPML_BYTES = 50 * 1024 * 1024;
@@ -21459,7 +21807,7 @@ function parseHwpmlDocument(buffer, options) {
21459
21807
  const normalized = text.replace(/&nbsp;/g, "&#160;");
21460
21808
  const xml = stripDtd(normalized);
21461
21809
  const warnings = [];
21462
- const parser = new DOMParser4({
21810
+ const parser = new DOMParser5({
21463
21811
  onError: (_level, msg2) => {
21464
21812
  warnings.push({ message: `HWPML XML \uD30C\uC2F1 \uACBD\uACE0: ${msg2}`, code: "MALFORMED_XML" });
21465
21813
  }
@@ -21552,7 +21900,7 @@ function walkContent(node, blocks, paraShapeMap, sectionNum, warnings, inHeaderF
21552
21900
  }
21553
21901
  if (tag === "TABLE") {
21554
21902
  if (!inHeaderFooter) {
21555
- parseTable2(el, blocks, paraShapeMap, sectionNum, warnings, keep);
21903
+ parseTable3(el, blocks, paraShapeMap, sectionNum, warnings, keep);
21556
21904
  }
21557
21905
  continue;
21558
21906
  }
@@ -21571,7 +21919,7 @@ function walkTablesInP(node, blocks, paraShapeMap, sectionNum, warnings, keep, d
21571
21919
  if (el.nodeType !== 1) continue;
21572
21920
  const tag = localName(el);
21573
21921
  if (tag === "TABLE") {
21574
- parseTable2(el, blocks, paraShapeMap, sectionNum, warnings, keep);
21922
+ parseTable3(el, blocks, paraShapeMap, sectionNum, warnings, keep);
21575
21923
  continue;
21576
21924
  }
21577
21925
  if (tag === "FOOTNOTE" || tag === "ENDNOTE" || tag === "HEADER" || tag === "FOOTER") continue;
@@ -21604,7 +21952,7 @@ function collectCharText(node, parts, depth = 0) {
21604
21952
  if (tag === "CHAR") {
21605
21953
  for (const c of Array.from(el.childNodes)) {
21606
21954
  const t = c.nodeType === 1 ? localName(c) === "TAB" ? " " : rawTextContent(c) : c.nodeType === 3 || c.nodeType === 4 ? c.nodeValue ?? "" : "";
21607
- if (t) parts.push(t);
21955
+ if (t) parts.push(escapeLiteralTags(t));
21608
21956
  }
21609
21957
  } else if (tag === "TABLE" || tag === "PICTURE" || tag === "SHAPEOBJECT") {
21610
21958
  } else if (tag === "AUTONUM") {
@@ -21613,7 +21961,7 @@ function collectCharText(node, parts, depth = 0) {
21613
21961
  }
21614
21962
  }
21615
21963
  }
21616
- function parseTable2(el, blocks, _paraShapeMap, sectionNum, warnings, keep) {
21964
+ function parseTable3(el, blocks, _paraShapeMap, sectionNum, warnings, keep) {
21617
21965
  const table2 = buildHmlTable(el, warnings, keep, 0);
21618
21966
  if (!table2) return;
21619
21967
  const caption = extractShapeCaption(el);
@@ -22555,10 +22903,10 @@ function fillInlineFields(text, values, filled, matchedLabels, blockedLabels) {
22555
22903
  }
22556
22904
 
22557
22905
  // src/form/filler-hwpx.ts
22558
- import JSZip7 from "jszip";
22906
+ import JSZip8 from "jszip";
22559
22907
 
22560
22908
  // src/form/click-here.ts
22561
- import JSZip6 from "jszip";
22909
+ import JSZip7 from "jszip";
22562
22910
 
22563
22911
  // src/roundtrip/source-map.ts
22564
22912
  function escapeXmlText(text) {
@@ -22644,7 +22992,7 @@ function prefixOf(qname) {
22644
22992
  }
22645
22993
  function scanSectionXml(xml, sectionIndex) {
22646
22994
  const stack = [];
22647
- const bodyParagraphs = [];
22995
+ const bodyParagraphs2 = [];
22648
22996
  const tables = [];
22649
22997
  const headerTexts = [];
22650
22998
  const footerTexts = [];
@@ -22767,8 +23115,8 @@ function scanSectionXml(xml, sectionIndex) {
22767
23115
  } else if (local === "cellAddr") {
22768
23116
  const cell2 = cellStack[cellStack.length - 1];
22769
23117
  if (cell2 && insideCurrentTable(stack, tableStack)) {
22770
- const ca = parseInt(getAttr2(attrsRaw, "colAddr") || "", 10);
22771
- const ra = parseInt(getAttr2(attrsRaw, "rowAddr") || "", 10);
23118
+ const ca = parseInt(getAttr3(attrsRaw, "colAddr") || "", 10);
23119
+ const ra = parseInt(getAttr3(attrsRaw, "rowAddr") || "", 10);
22772
23120
  if (!isNaN(ca)) cell2.colAddr = ca;
22773
23121
  if (!isNaN(ra)) cell2.rowAddr = ra;
22774
23122
  cell2.addrTagRange = { start: m.index, end: m.index + full.length };
@@ -22776,8 +23124,8 @@ function scanSectionXml(xml, sectionIndex) {
22776
23124
  } else if (local === "cellSpan") {
22777
23125
  const cell2 = cellStack[cellStack.length - 1];
22778
23126
  if (cell2 && insideCurrentTable(stack, tableStack)) {
22779
- const cs = parseInt(getAttr2(attrsRaw, "colSpan") || "1", 10);
22780
- const rs = parseInt(getAttr2(attrsRaw, "rowSpan") || "1", 10);
23127
+ const cs = parseInt(getAttr3(attrsRaw, "colSpan") || "1", 10);
23128
+ const rs = parseInt(getAttr3(attrsRaw, "rowSpan") || "1", 10);
22781
23129
  cell2.colSpan = isNaN(cs) || cs < 1 ? 1 : cs;
22782
23130
  cell2.rowSpan = isNaN(rs) || rs < 1 ? 1 : rs;
22783
23131
  }
@@ -22806,7 +23154,7 @@ function scanSectionXml(xml, sectionIndex) {
22806
23154
  if (cls.inTextbox) para2.inTextbox = true;
22807
23155
  stack.push({ local, qname, contentStart });
22808
23156
  paraStack.push(para2);
22809
- if (para2.kind === "body" || para2.kind === "draw") bodyParagraphs.push(para2);
23157
+ if (para2.kind === "body" || para2.kind === "draw") bodyParagraphs2.push(para2);
22810
23158
  else if (para2.kind === "cell") {
22811
23159
  const cell2 = cellStack[cellStack.length - 1];
22812
23160
  if (cell2) cell2.paragraphs.push(para2);
@@ -22841,14 +23189,14 @@ function scanSectionXml(xml, sectionIndex) {
22841
23189
  const cell2 = cellStack[cellStack.length - 1];
22842
23190
  if (cell2 && insideCurrentTable(stack, tableStack)) {
22843
23191
  if (local === "cellAddr") {
22844
- const ca = parseInt(getAttr2(attrsRaw, "colAddr") || "", 10);
22845
- const ra = parseInt(getAttr2(attrsRaw, "rowAddr") || "", 10);
23192
+ const ca = parseInt(getAttr3(attrsRaw, "colAddr") || "", 10);
23193
+ const ra = parseInt(getAttr3(attrsRaw, "rowAddr") || "", 10);
22846
23194
  if (!isNaN(ca)) cell2.colAddr = ca;
22847
23195
  if (!isNaN(ra)) cell2.rowAddr = ra;
22848
23196
  cell2.addrTagRange = { start: m.index, end: contentStart };
22849
23197
  } else {
22850
- const cs = parseInt(getAttr2(attrsRaw, "colSpan") || "1", 10);
22851
- const rs = parseInt(getAttr2(attrsRaw, "rowSpan") || "1", 10);
23198
+ const cs = parseInt(getAttr3(attrsRaw, "colSpan") || "1", 10);
23199
+ const rs = parseInt(getAttr3(attrsRaw, "rowSpan") || "1", 10);
22852
23200
  cell2.colSpan = isNaN(cs) || cs < 1 ? 1 : cs;
22853
23201
  cell2.rowSpan = isNaN(rs) || rs < 1 ? 1 : rs;
22854
23202
  }
@@ -22862,7 +23210,7 @@ function scanSectionXml(xml, sectionIndex) {
22862
23210
  if (para2) para2.text += " ";
22863
23211
  }
22864
23212
  }
22865
- for (const para2 of bodyParagraphs) fillRunInsertPos(para2, xml);
23213
+ for (const para2 of bodyParagraphs2) fillRunInsertPos(para2, xml);
22866
23214
  for (const para2 of excludedParagraphs) fillRunInsertPos(para2, xml);
22867
23215
  const fillTableInsertPos = (table2, depth = 0) => {
22868
23216
  if (depth > 16) return;
@@ -22875,9 +23223,9 @@ function scanSectionXml(xml, sectionIndex) {
22875
23223
  };
22876
23224
  for (const table2 of tables) fillTableInsertPos(table2);
22877
23225
  for (const table2 of orphanTables) fillTableInsertPos(table2);
22878
- return { sectionIndex, xml, bodyParagraphs, tables, headerTexts, footerTexts, excludedParagraphs, orphanTables };
23226
+ return { sectionIndex, xml, bodyParagraphs: bodyParagraphs2, tables, headerTexts, footerTexts, excludedParagraphs, orphanTables };
22879
23227
  }
22880
- function getAttr2(attrsRaw, name) {
23228
+ function getAttr3(attrsRaw, name) {
22881
23229
  const re = new RegExp(`(?:^|\\s)${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)')`);
22882
23230
  const m = attrsRaw.match(re);
22883
23231
  return m ? m[1] ?? m[2] : void 0;
@@ -23201,7 +23549,7 @@ function fillClickHereInXml(xml, cursor, blockedLabels) {
23201
23549
  }
23202
23550
  async function extractClickHereFields(hwpxBuffer) {
23203
23551
  precheckZipSize(hwpxBuffer);
23204
- const zip = await JSZip6.loadAsync(hwpxBuffer);
23552
+ const zip = await JSZip7.loadAsync(hwpxBuffer);
23205
23553
  const sectionPaths = Object.keys(zip.files).filter((name) => /[Ss]ection\d+\.xml$/i.test(name)).sort();
23206
23554
  const fields = [];
23207
23555
  for (let si = 0; si < sectionPaths.length; si++) {
@@ -23430,7 +23778,7 @@ function patchZipEntries(original, replacements, additions) {
23430
23778
  async function fillHwpx(hwpxBuffer, values, blockedLabels) {
23431
23779
  const u8 = new Uint8Array(hwpxBuffer);
23432
23780
  precheckZipSize(hwpxBuffer);
23433
- const zip = await JSZip7.loadAsync(hwpxBuffer);
23781
+ const zip = await JSZip8.loadAsync(hwpxBuffer);
23434
23782
  const sectionPaths = Object.keys(zip.files).filter((name) => /[Ss]ection\d+\.xml$/i.test(name)).sort();
23435
23783
  if (sectionPaths.length === 0) {
23436
23784
  throw new KordocError("HWPX\uC5D0\uC11C \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
@@ -23889,7 +24237,7 @@ function chooseTableRepresentation(table2, opts = {}) {
23889
24237
  }
23890
24238
 
23891
24239
  // src/hwpx/generator.ts
23892
- import JSZip8 from "jszip";
24240
+ import JSZip9 from "jszip";
23893
24241
 
23894
24242
  // src/hwpx/font-metrics.ts
23895
24243
  var FONTS = {
@@ -30140,7 +30488,8 @@ function renderListItem(block, blockIdx, ctx) {
30140
30488
  ctx.prevWasOrdered = false;
30141
30489
  }
30142
30490
  const indentPrefix = " ".repeat(indent);
30143
- return generateParagraph(indentPrefix + marker + (block.text || ""), PARA_LIST);
30491
+ const markerRun = `<hp:run charPrIDRef="${CHAR_NORMAL}"><hp:t>${escapeTextXml(indentPrefix + marker)}</hp:t></hp:run>`;
30492
+ return `<hp:p paraPrIDRef="${PARA_LIST}" styleIDRef="0">${markerRun}${generateRuns(block.text || "")}</hp:p>`;
30144
30493
  }
30145
30494
  function renderHr(ctx) {
30146
30495
  return ctx.gongmun ? `<hp:p paraPrIDRef="${PARA_NORMAL}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_NORMAL}"><hp:t></hp:t></hp:run></hp:p>` : `<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="0"><hp:t>\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500</hp:t></hp:run></hp:p>`;
@@ -31648,7 +31997,7 @@ async function markdownToHwpx(markdown, options) {
31648
31997
  if (remap2 && remap2.tables.length > 0 && remap2.tables.every((t) => !t.used)) {
31649
31998
  console.warn(`[kordoc] format profile: \uD504\uB85C\uD544 \uD45C ${remap2.tables.length}\uAC1C\uAC00 \uBB38\uC11C \uD45C\uC640 \uB9E4\uCE6D\uB418\uC9C0 \uC54A\uC544 \uBBF8\uC801\uC6A9 (\uD589\xB7\uC5F4/\uCCAB \uC140 \uD14D\uC2A4\uD2B8 \uBD88\uC77C\uCE58)`);
31650
31999
  }
31651
- const zip2 = new JSZip8();
32000
+ const zip2 = new JSZip9();
31652
32001
  zip2.file("mimetype", "application/hwp+zip", { compression: "STORE" });
31653
32002
  zip2.file("META-INF/container.xml", generateContainerXml());
31654
32003
  zip2.file("Contents/content.hpf", generateManifest(chartParts2, images2.manifestItems(), "gongmun"));
@@ -31695,7 +32044,7 @@ async function markdownToHwpx(markdown, options) {
31695
32044
  }
31696
32045
  }
31697
32046
  const extraBorderFills = [...remap?.borderFillXmls ?? [], ...bfReg?.emit() ?? []];
31698
- const zip = new JSZip8();
32047
+ const zip = new JSZip9();
31699
32048
  zip.file("mimetype", "application/hwp+zip", { compression: "STORE" });
31700
32049
  zip.file("META-INF/container.xml", generateContainerXml());
31701
32050
  zip.file("Contents/content.hpf", generateManifest(chartParts, images.manifestItems(), gongmun ? "gongmun" : "default"));
@@ -31915,7 +32264,7 @@ function readBuiltinTemplateSample(t) {
31915
32264
  }
31916
32265
 
31917
32266
  // src/form/seal.ts
31918
- import JSZip9 from "jszip";
32267
+ import JSZip10 from "jszip";
31919
32268
 
31920
32269
  // src/render/head-styles.ts
31921
32270
  var DEFAULT_PARA_GEOM = {
@@ -32261,7 +32610,7 @@ async function placeSealHwpx(hwpxBuffer, ops) {
32261
32610
  if (ops.length === 0) throw new KordocError("place_seal: \uBC30\uCE58\uD560 \uB3C4\uC7A5\uC774 \uC5C6\uC2B5\uB2C8\uB2E4");
32262
32611
  const u8 = new Uint8Array(hwpxBuffer);
32263
32612
  precheckZipSize(hwpxBuffer);
32264
- const zip = await JSZip9.loadAsync(hwpxBuffer);
32613
+ const zip = await JSZip10.loadAsync(hwpxBuffer);
32265
32614
  const sectionPaths = Object.keys(zip.files).filter((name) => /[Ss]ection\d+\.xml$/i.test(name)).sort((a, b) => Number(a.match(/(\d+)\.xml$/i)?.[1] ?? 0) - Number(b.match(/(\d+)\.xml$/i)?.[1] ?? 0));
32266
32615
  if (sectionPaths.length === 0) throw new KordocError("HWPX\uC5D0\uC11C \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
32267
32616
  const manifestPath = Object.keys(zip.files).find((name) => /\.hpf$/i.test(name));
@@ -32425,7 +32774,7 @@ async function placeSealHwpx(hwpxBuffer, ops) {
32425
32774
  }
32426
32775
 
32427
32776
  // src/hwpx/extract-profile.ts
32428
- import JSZip10 from "jszip";
32777
+ import JSZip11 from "jszip";
32429
32778
  function elemsByLocal(root, name) {
32430
32779
  const all = root.getElementsByTagName("*");
32431
32780
  const out = [];
@@ -32512,7 +32861,7 @@ function num(s) {
32512
32861
  const n = parseInt(s, 10);
32513
32862
  return Number.isFinite(n) ? n : void 0;
32514
32863
  }
32515
- function parseTable3(tbl2, tableIndex, borderFills, charPrs) {
32864
+ function parseTable4(tbl2, tableIndex, borderFills, charPrs) {
32516
32865
  const rows = num(tbl2.getAttribute("rowCnt")) ?? 0;
32517
32866
  const cols = num(tbl2.getAttribute("colCnt")) ?? 0;
32518
32867
  const sz = findChildByLocalName(tbl2, "sz");
@@ -32628,7 +32977,7 @@ function pick(map, keys) {
32628
32977
  }
32629
32978
  async function hwpxToProfile(input) {
32630
32979
  const buf = input instanceof ArrayBuffer ? input : toArrayBuffer(input);
32631
- const zip = await JSZip10.loadAsync(buf);
32980
+ const zip = await JSZip11.loadAsync(buf);
32632
32981
  const parser = createXmlParser();
32633
32982
  const headerFile = zip.file("Contents/header.xml") ?? zip.file(/[Hh]eader\.xml$/)?.[0];
32634
32983
  let headerXml = "<root/>";
@@ -32643,7 +32992,7 @@ async function hwpxToProfile(input) {
32643
32992
  const doc = parser.parseFromString(await f.async("text"), "text/xml");
32644
32993
  for (const tbl2 of elemsByLocal(doc, "tbl")) {
32645
32994
  if (!isTopLevelTable(tbl2)) continue;
32646
- tables.push(parseTable3(tbl2, tableIndex++, borderFills, charPrs));
32995
+ tables.push(parseTable4(tbl2, tableIndex++, borderFills, charPrs));
32647
32996
  }
32648
32997
  }
32649
32998
  return { schema_version: "0.3.0", tables };
@@ -33345,7 +33694,7 @@ function muncheLintWarnings(text, limit = 10) {
33345
33694
  }
33346
33695
 
33347
33696
  // src/roundtrip/patcher.ts
33348
- import JSZip11 from "jszip";
33697
+ import JSZip12 from "jszip";
33349
33698
 
33350
33699
  // src/roundtrip/table-rows.ts
33351
33700
  var ROW_OBJECT_RE = /<(?:[A-Za-z0-9_]+:)?(?:tbl|pic|equation|ole|container|shape|drawingObject|drawText|video|chart|fieldBegin|fieldEnd|ctrl)\b/;
@@ -34537,7 +34886,12 @@ async function patchHwp(original, editedMarkdown, options) {
34537
34886
  try {
34538
34887
  const kept = parseHwp5Document(originalBuf, { layoutTables: "keep" });
34539
34888
  origBlocks = kept.blocks;
34540
- if (editedFromVisual(kept.markdown, parseHwp5Document(originalBuf).markdown, editedMarkdown)) return fail(LAYOUT_MODE_MISMATCH);
34889
+ const visual = parseHwp5Document(originalBuf).markdown;
34890
+ if (kept.markdown !== visual && editedMarkdown === visual) {
34891
+ const verification2 = options?.verify === false ? void 0 : diffUnitLists(splitMarkdownUnits(visual), splitMarkdownUnits(editedMarkdown));
34892
+ return { success: true, data: new Uint8Array(original), applied: 0, skipped, verification: verification2 };
34893
+ }
34894
+ if (editedFromVisual(kept.markdown, visual, editedMarkdown)) return fail(LAYOUT_MODE_MISMATCH);
34541
34895
  } catch (err) {
34542
34896
  return fail(`\uC6D0\uBCF8 HWP \uD30C\uC2F1 \uC2E4\uD328: ${msg(err)}`);
34543
34897
  }
@@ -35207,7 +35561,7 @@ async function patchHwpx(original, editedMarkdown, options) {
35207
35561
  }
35208
35562
  let zip;
35209
35563
  try {
35210
- zip = await JSZip11.loadAsync(original);
35564
+ zip = await JSZip12.loadAsync(original);
35211
35565
  } catch {
35212
35566
  return { success: false, applied: 0, skipped, error: "ZIP \uB85C\uB4DC \uC2E4\uD328" };
35213
35567
  }
@@ -35652,8 +36006,8 @@ async function resolveHeaderEntryName(zip) {
35652
36006
  }
35653
36007
 
35654
36008
  // src/validate.ts
35655
- import JSZip12 from "jszip";
35656
- import { DOMParser as DOMParser5 } from "@xmldom/xmldom";
36009
+ import JSZip13 from "jszip";
36010
+ import { DOMParser as DOMParser6 } from "@xmldom/xmldom";
35657
36011
  var REQUIRED_FILES = [
35658
36012
  "mimetype",
35659
36013
  "META-INF/container.xml",
@@ -35670,7 +36024,7 @@ async function validateHwpx(buffer) {
35670
36024
  const issues = [];
35671
36025
  let zip;
35672
36026
  try {
35673
- zip = await JSZip12.loadAsync(buffer);
36027
+ zip = await JSZip13.loadAsync(buffer);
35674
36028
  } catch (err) {
35675
36029
  return {
35676
36030
  ok: false,
@@ -35699,7 +36053,7 @@ async function validateHwpx(buffer) {
35699
36053
  const text = await zip.files[name].async("string");
35700
36054
  let firstError = null;
35701
36055
  try {
35702
- new DOMParser5({
36056
+ new DOMParser6({
35703
36057
  onError(level, msg2) {
35704
36058
  if (level !== "warning" && firstError === null) firstError = String(msg2);
35705
36059
  }
@@ -38206,12 +38560,12 @@ function blocksToChunks(blocks, options) {
38206
38560
  }
38207
38561
 
38208
38562
  // src/roundtrip/session.ts
38209
- import JSZip13 from "jszip";
38563
+ import JSZip14 from "jszip";
38210
38564
  async function buildState(bytes) {
38211
38565
  const format2 = detectFormat(new Uint8Array(bytes.subarray(0, 512)).buffer);
38212
38566
  if (format2 !== "hwpx") throw new Error(`HWPX \uC138\uC158\uC740 HWPX \uBB38\uC11C\uB9CC \uC5FD\uB2C8\uB2E4 (\uAC10\uC9C0\uB41C \uD3EC\uB9F7: ${format2}) \u2014 HWP 5.x \uB294 patchHwp \uB85C \uD328\uCE58\uD558\uC138\uC694`);
38213
38567
  const parsed = await parseHwpxDocument(u8ToArrayBuffer2(bytes), { layoutTables: "keep" });
38214
- const zip = await JSZip13.loadAsync(bytes);
38568
+ const zip = await JSZip14.loadAsync(bytes);
38215
38569
  const sectionPaths = await resolveSectionEntryNames(zip);
38216
38570
  if (sectionPaths.length === 0) {
38217
38571
  throw new Error("HWPX \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
@@ -38732,7 +39086,7 @@ async function blocksToPdf(blocks, options) {
38732
39086
  }
38733
39087
 
38734
39088
  // src/render/svg-render.ts
38735
- import JSZip14 from "jszip";
39089
+ import JSZip15 from "jszip";
38736
39090
 
38737
39091
  // src/render/layout.ts
38738
39092
  function toInt32(v, fallback = 0) {
@@ -39338,22 +39692,48 @@ function rotatedBounds(box, rotations) {
39338
39692
  if (!rotations.length) return box;
39339
39693
  let points = [[box.x, box.y], [box.x + box.w, box.y], [box.x + box.w, box.y + box.h], [box.x, box.y + box.h]];
39340
39694
  for (let i = rotations.length - 1; i >= 0; i--) {
39341
- const { angle, cx, cy } = rotations[i];
39695
+ const r = rotations[i];
39696
+ if (Array.isArray(r)) {
39697
+ points = points.map(([x2, y2]) => [r[0] * x2 + r[2] * y2 + r[4], r[1] * x2 + r[3] * y2 + r[5]]);
39698
+ continue;
39699
+ }
39700
+ const { angle, cx, cy } = r;
39342
39701
  const a = angle * Math.PI / 180, c = Math.cos(a), s = Math.sin(a);
39343
39702
  points = points.map(([x2, y2]) => [cx + c * (x2 - cx) - s * (y2 - cy), cy + s * (x2 - cx) + c * (y2 - cy)]);
39344
39703
  }
39345
39704
  const x = Math.min(...points.map((p) => p[0])), y = Math.min(...points.map((p) => p[1]));
39346
39705
  return { x, y, w: Math.max(...points.map((p) => p[0])) - x, h: Math.max(...points.map((p) => p[1])) - y };
39347
39706
  }
39348
- function objectGeometry(el, x, y, w, h) {
39707
+ function mulAffine(m, n) {
39708
+ return [
39709
+ m[0] * n[0] + m[2] * n[1],
39710
+ m[1] * n[0] + m[3] * n[1],
39711
+ m[0] * n[2] + m[2] * n[3],
39712
+ m[1] * n[2] + m[3] * n[3],
39713
+ m[0] * n[4] + m[2] * n[5] + m[4],
39714
+ m[1] * n[4] + m[3] * n[5] + m[5]
39715
+ ];
39716
+ }
39717
+ function renderingMatrix(el) {
39718
+ const info = findChildByLocalName(el, "renderingInfo");
39719
+ if (!info) return null;
39720
+ let m = null;
39721
+ for (const c of elements(info)) {
39722
+ const tag = ln(c);
39723
+ if (tag !== "transMatrix" && tag !== "scaMatrix" && tag !== "rotMatrix") continue;
39724
+ const n = [num2(c, "e1", 1), num2(c, "e4"), num2(c, "e2"), num2(c, "e5", 1), num2(c, "e3"), num2(c, "e6")];
39725
+ m = m ? mulAffine(m, n) : n;
39726
+ }
39727
+ return m;
39728
+ }
39729
+ function objectGeometry(el, x, y, w, h, boxW = w, boxH = h) {
39349
39730
  const rot = findChildByLocalName(el, "rotationInfo");
39350
39731
  const angle = num2(rot, "angle") % 360;
39351
39732
  if (!angle) return { x, y, w, h, bbox: { x, y, w, h }, transform: "" };
39352
- const org = findChildByLocalName(el, "orgSz");
39353
- const cx = x + num2(rot, "centerX", num2(org, "width", w) / 2) * (w / (num2(org, "width") || w || 1));
39354
- const cy = y + num2(rot, "centerY", num2(org, "height", h) / 2) * (h / (num2(org, "height") || h || 1));
39733
+ const cx = x + boxW / 2, cy = y + boxH / 2;
39734
+ const fx = cx - w / 2, fy = cy - h / 2;
39355
39735
  const rotation = { angle, cx: Math.round(cx), cy: Math.round(cy) };
39356
- return { x, y, w, h, bbox: rotatedBounds({ x, y, w, h }, [rotation]), rotation, transform: `rotate(${angle} ${pt(cx)} ${pt(cy)})` };
39736
+ return { x: fx, y: fy, w, h, bbox: rotatedBounds({ x: fx, y: fy, w, h }, [rotation]), rotation, transform: `rotate(${angle} ${pt(cx)} ${pt(cy)})` };
39357
39737
  }
39358
39738
  function shapeFill(el, defs, images, warn) {
39359
39739
  const brush = findChildByLocalName(el, "fillBrush");
@@ -39401,10 +39781,13 @@ function shapeFill(el, defs, images, warn) {
39401
39781
  }
39402
39782
  return "none";
39403
39783
  }
39404
- function shapeGeometry(o, x, y, defs, images, warn) {
39784
+ function shapeGeometry(o, boxX, boxY, defs, images, warn, local = false) {
39405
39785
  const el = o.el, org = findChildByLocalName(el, "orgSz"), cur = findChildByLocalName(el, "curSz");
39406
39786
  const ow = num2(org, "width"), oh = num2(org, "height");
39407
- const w = num2(cur, "width") || ow || o.width, h = num2(cur, "height") || oh || o.height;
39787
+ const w = (local ? ow : num2(cur, "width")) || ow || num2(cur, "width") || o.width;
39788
+ const h = (local ? oh : num2(cur, "height")) || oh || num2(cur, "height") || o.height;
39789
+ const geometry = local ? { x: boxX, y: boxY, w, h, bbox: { x: boxX, y: boxY, w, h }, transform: "" } : objectGeometry(el, boxX, boxY, w, h, o.width || w, o.height || h);
39790
+ const { x, y } = geometry;
39408
39791
  const sx = ow > 0 ? w / ow : 1, sy = oh > 0 ? h / oh : 1;
39409
39792
  const line = findChildByLocalName(el, "lineShape"), style = line?.getAttribute("style") ?? "SOLID";
39410
39793
  const color = esc(line?.getAttribute("color") || "#000000");
@@ -39423,8 +39806,7 @@ function shapeGeometry(o, x, y, defs, images, warn) {
39423
39806
  if (pts.length >= 2) svg = `<polygon points="${pts.join(" ")}"${fill}${stroke}/>`;
39424
39807
  } else if (o.tag === "curve") {
39425
39808
  const segs = elements(el).filter((c) => ln(c) === "seg");
39426
- if (segs.some((s) => s.getAttribute("type") !== "LINE")) warn("curve:bezier", "\uBE44\uC120\uD615 \uACE1\uC120 \uB80C\uB354 \uBBF8\uC9C0\uC6D0 \u2014 \uC0DD\uB7B5");
39427
- else if (segs.length) {
39809
+ if (segs.length) {
39428
39810
  let path = "", lastX, lastY;
39429
39811
  for (const s of segs) {
39430
39812
  const x1 = num2(s, "x1"), y1 = num2(s, "y1"), x2 = num2(s, "x2"), y2 = num2(s, "y2");
@@ -39436,7 +39818,7 @@ function shapeGeometry(o, x, y, defs, images, warn) {
39436
39818
  svg = `<path d="${path}"${fill}${stroke}/>`;
39437
39819
  }
39438
39820
  } else if (o.tag === "arc") svg = `<ellipse cx="${pt(x + w / 2)}" cy="${pt(y + h / 2)}" rx="${pt(w / 2)}" ry="${pt(h / 2)}" fill="none"${stroke || ` stroke="${color}" stroke-width="0.3"`}/>`;
39439
- return { ...objectGeometry(el, x, y, w, h), svg };
39821
+ return { ...geometry, svg };
39440
39822
  }
39441
39823
 
39442
39824
  // src/render/svg-render.ts
@@ -39730,6 +40112,7 @@ function drawObject(o, x, y, baseV, areaW, ctx, depth) {
39730
40112
  if (o.tag === "tbl") drawTable(o.el, x, y, ctx, depth + 1);
39731
40113
  else if (o.tag === "pic") drawPic(o.el, x, y, ctx);
39732
40114
  else if (o.tag === "container") {
40115
+ if (drawGroupByMatrix(o, x, y, baseV, areaW, ctx, depth)) return;
39733
40116
  for (const ch of elements(o.el)) {
39734
40117
  const tag = ln(ch);
39735
40118
  if (!OBJ_TAGS.has(tag)) continue;
@@ -39746,8 +40129,42 @@ function drawObject(o, x, y, baseV, areaW, ctx, depth) {
39746
40129
  warnOnce(ctx, `shape:${o.tag}`, `\uAC1C\uCCB4(${o.tag}) \uB80C\uB354 \uBBF8\uC9C0\uC6D0 \u2014 \uC0DD\uB7B5`);
39747
40130
  }
39748
40131
  }
39749
- function drawShape(o, x, y, ctx, depth) {
39750
- const geometry = shapeGeometry(o, x, y, ctx.defs, ctx.images, (key, msg2) => warnOnce(ctx, key, msg2));
40132
+ function drawGroupByMatrix(o, x, y, baseV, areaW, ctx, depth) {
40133
+ const top = renderingMatrix(o.el);
40134
+ const leaves = [];
40135
+ const collect = (el) => {
40136
+ for (const ch of elements(el)) {
40137
+ const tag = ln(ch);
40138
+ if (!OBJ_TAGS.has(tag)) continue;
40139
+ if (tag === "container") {
40140
+ if (!collect(ch)) return false;
40141
+ } else if (renderingMatrix(ch)) leaves.push(ch);
40142
+ else return false;
40143
+ }
40144
+ return true;
40145
+ };
40146
+ if (!top || !collect(o.el)) return false;
40147
+ const org = findChildByLocalName(o.el, "orgSz");
40148
+ const box = rotatedBounds({ x: 0, y: 0, w: num2(org, "width"), h: num2(org, "height") }, [top]);
40149
+ const base = [1, 0, 0, 1, x - box.x, y - box.y];
40150
+ for (const leaf of leaves) {
40151
+ const m = mulAffine(base, renderingMatrix(leaf));
40152
+ const tag = ln(leaf), lorg = findChildByLocalName(leaf, "orgSz");
40153
+ const w = num2(lorg, "width") || num2(findChildByLocalName(leaf, "curSz"), "width");
40154
+ const h = num2(lorg, "height") || num2(findChildByLocalName(leaf, "curSz"), "height");
40155
+ ctx.regionRotations.push(m);
40156
+ emit(ctx, `<g transform="matrix(${m.slice(0, 4).map((v) => Math.round(v * 1e6) / 1e6).join(" ")} ${pt2(m[4])} ${pt2(m[5])})">`);
40157
+ const sub = { el: leaf, tag, index: 0, inline: true, width: w, height: h, omL: 0, omR: 0 };
40158
+ if (tag === "pic") drawPicFrame(leaf, 0, 0, w, h, "", ctx);
40159
+ else if (SHAPE_TAGS.has(tag)) drawShape(sub, 0, 0, ctx, depth + 1, true);
40160
+ else drawObject(sub, 0, 0, baseV, areaW, ctx, depth + 1);
40161
+ emit(ctx, "</g>");
40162
+ ctx.regionRotations.pop();
40163
+ }
40164
+ return true;
40165
+ }
40166
+ function drawShape(o, x, y, ctx, depth, local = false) {
40167
+ const geometry = shapeGeometry(o, x, y, ctx.defs, ctx.images, (key, msg2) => warnOnce(ctx, key, msg2), local);
39751
40168
  const b = geometry;
39752
40169
  if (geometry.rotation) ctx.regionRotations.push(geometry.rotation);
39753
40170
  ctx.stats.shapes++;
@@ -39841,16 +40258,31 @@ function imageSymbol(loaded, ctx) {
39841
40258
  }
39842
40259
  function drawPic(pic, x, y, ctx) {
39843
40260
  const sz = findChildByLocalName(pic, "sz");
39844
- const w = num2(sz, "width", 5669), h = num2(sz, "height", 5669);
40261
+ let w = num2(sz, "width", 5669), h = num2(sz, "height", 5669);
40262
+ const cur = findChildByLocalName(pic, "curSz");
40263
+ const rotated = objectGeometry(pic, x, y, num2(cur, "width") || w, num2(cur, "height") || h, w, h);
40264
+ if (rotated.rotation) {
40265
+ x = rotated.x;
40266
+ y = rotated.y;
40267
+ w = rotated.w;
40268
+ h = rotated.h;
40269
+ ctx.regionRotations.push(rotated.rotation);
40270
+ }
40271
+ drawPicFrame(pic, x, y, w, h, rotated.transform, ctx);
40272
+ if (rotated.rotation) ctx.regionRotations.pop();
40273
+ }
40274
+ function drawPicFrame(pic, x, y, w, h, transform, ctx) {
39845
40275
  const img = findFirst(pic, "img");
39846
40276
  const ref = img?.getAttribute("binaryItemIDRef");
39847
40277
  const loaded = ref != null ? ctx.images.get(ref) : void 0;
39848
40278
  const imgId = ctx.regions.add("image", bboxOf(ctx, x, y, w, h), { sourceId: ref ?? void 0, parentId: parentId(ctx) });
39849
40279
  emit(ctx, regionOpenTag(imgId, "image", pageNo(ctx)));
40280
+ const close = transform ? "</g></g>" : "</g>";
40281
+ if (transform) emit(ctx, `<g transform="${transform}">`);
39850
40282
  if (!loaded) {
39851
40283
  emit(ctx, `<rect x="${pt2(x)}" y="${pt2(y)}" width="${pt2(w)}" height="${pt2(h)}" fill="#eee" stroke="#c00" stroke-width="0.5"/>`);
39852
40284
  warnOnce(ctx, `img:${ref}`, `\uC774\uBBF8\uC9C0 \uBC14\uC774\uB108\uB9AC \uB204\uB77D: ${ref ?? "(ref \uC5C6\uC74C)"}`);
39853
- emit(ctx, "</g>");
40285
+ emit(ctx, close);
39854
40286
  return;
39855
40287
  }
39856
40288
  ctx.stats.images++;
@@ -39872,7 +40304,7 @@ function drawPic(pic, x, y, ctx) {
39872
40304
  } else {
39873
40305
  emit(ctx, `<use href="#${symId}" x="${pt2(x)}" y="${pt2(y)}" width="${pt2(w)}" height="${pt2(h)}"/>`);
39874
40306
  }
39875
- emit(ctx, "</g>");
40307
+ emit(ctx, close);
39876
40308
  }
39877
40309
  function sniffMime(name, bytes) {
39878
40310
  const lower = name.toLowerCase();
@@ -40000,7 +40432,7 @@ async function renderHwpxInternal(input, options) {
40000
40432
  precheckZipSize(ab, MAX_DECOMPRESS_SIZE, MAX_ZIP_ENTRIES);
40001
40433
  let zip;
40002
40434
  try {
40003
- zip = await JSZip14.loadAsync(input);
40435
+ zip = await JSZip15.loadAsync(input);
40004
40436
  } catch {
40005
40437
  throw new KordocError("HWPX(ZIP) \uD615\uC2DD\uC774 \uC544\uB2D9\uB2C8\uB2E4 \u2014 \uB80C\uB354\uB294 HWPX\uB9CC \uC9C0\uC6D0");
40006
40438
  }
@@ -41055,9 +41487,7 @@ async function dispatch(format2, buffer, opts) {
41055
41487
  const zipFormat = await detectZipFormat(buffer);
41056
41488
  if (zipFormat === "xlsx") return parseXlsx(buffer, opts);
41057
41489
  if (zipFormat === "docx") return parseDocx(buffer, opts);
41058
- if (zipFormat === "pptx") {
41059
- return { success: false, fileType: "pptx", error: "PPTX \uD30C\uC77C\uC740 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD30C\uC77C \uD615\uC2DD\uC785\uB2C8\uB2E4.", code: "UNSUPPORTED_FORMAT" };
41060
- }
41490
+ if (zipFormat === "pptx") return parsePptx(buffer, opts);
41061
41491
  return parseHwpx(buffer, opts);
41062
41492
  }
41063
41493
  case "hwp": {
@@ -41082,7 +41512,7 @@ function scriptsOff(r, off) {
41082
41512
  }
41083
41513
  async function parseImage(buffer, options) {
41084
41514
  try {
41085
- const { parseImageDocument } = await import("./image-ocr-2FIFDA7I.js");
41515
+ const { parseImageDocument } = await import("./image-ocr-IMYXOHHV.js");
41086
41516
  const { blocks, warnings } = await parseImageDocument(buffer, options);
41087
41517
  stripScriptTags({ blocks });
41088
41518
  return {
@@ -41142,7 +41572,7 @@ async function parseHwp(buffer, options) {
41142
41572
  async function parsePdf(buffer, options) {
41143
41573
  let parsePdfDocument;
41144
41574
  try {
41145
- const mod = await import("./parser-WW7NWTUM.js");
41575
+ const mod = await import("./parser-M3T2EWUN.js");
41146
41576
  parsePdfDocument = mod.parsePdfDocument;
41147
41577
  } catch {
41148
41578
  return {
@@ -41184,6 +41614,14 @@ async function parseDocx(buffer, options) {
41184
41614
  return { success: false, fileType: "docx", error: sanitizeError(err), code: classifyError(err) };
41185
41615
  }
41186
41616
  }
41617
+ async function parsePptx(buffer, options) {
41618
+ try {
41619
+ const { markdown, blocks, metadata, outline, warnings, images } = await parsePptxDocument(buffer, options);
41620
+ return { success: true, fileType: "pptx", markdown, blocks, metadata, outline, warnings, images: images?.length ? images : void 0, pageCount: metadata?.pageCount };
41621
+ } catch (err) {
41622
+ return { success: false, fileType: "pptx", error: sanitizeError(err), code: classifyError(err) };
41623
+ }
41624
+ }
41187
41625
  async function parseHwpml(buffer, options) {
41188
41626
  try {
41189
41627
  const { markdown, blocks, metadata, outline, warnings } = parseHwpmlDocument(buffer, options);
@@ -41296,6 +41734,7 @@ export {
41296
41734
  parseHwpx,
41297
41735
  parseImage,
41298
41736
  parsePdf,
41737
+ parsePptx,
41299
41738
  parseXls,
41300
41739
  parseXlsx,
41301
41740
  patchHwp,