kordoc 4.14.2 → 4.14.3

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 (151) hide show
  1. package/README.md +38 -5
  2. package/dist/{-AQKXLFIM.js → -7XBPXCXN.js} +13 -11
  3. package/dist/{chunk-FOJZNMMK.js → chunk-2LQGSDZ6.js} +1 -1
  4. package/dist/chunk-2LQGSDZ6.js.map +1 -0
  5. package/dist/chunk-4R67INKT.js +1002 -0
  6. package/dist/chunk-4R67INKT.js.map +1 -0
  7. package/dist/{chunk-ILU76UF3.js → chunk-4RJ5D7MT.js} +2 -2
  8. package/dist/chunk-4RJ5D7MT.js.map +1 -0
  9. package/dist/{chunk-EQZI77PP.cjs → chunk-5VVS2QIJ.cjs} +3 -3
  10. package/dist/{chunk-EQZI77PP.cjs.map → chunk-5VVS2QIJ.cjs.map} +1 -1
  11. package/dist/{chunk-3DUADAY2.js → chunk-A275ZQLX.js} +209 -87
  12. package/dist/chunk-A275ZQLX.js.map +1 -0
  13. package/dist/{chunk-EISO3POT.js → chunk-CVEERPMP.js} +788 -194
  14. package/dist/chunk-CVEERPMP.js.map +1 -0
  15. package/dist/{chunk-ZWD734EY.js → chunk-D6KQKMU3.js} +2 -2
  16. package/dist/{chunk-QOMNEWCQ.cjs → chunk-EBZUZC6F.cjs} +305 -31
  17. package/dist/chunk-EBZUZC6F.cjs.map +1 -0
  18. package/dist/{chunk-HR2XNERB.cjs → chunk-EMGF6EEV.cjs} +2 -2
  19. package/dist/{chunk-HR2XNERB.cjs.map → chunk-EMGF6EEV.cjs.map} +1 -1
  20. package/dist/{chunk-GDW4YP7K.js → chunk-FAZR34CA.js} +2 -2
  21. package/dist/{chunk-TNCKOL2O.js → chunk-GTINVM5K.js} +2 -2
  22. package/dist/{chunk-L7DMSMAG.cjs → chunk-HX4UUF7Y.cjs} +1 -1
  23. package/dist/chunk-HX4UUF7Y.cjs.map +1 -0
  24. package/dist/{chunk-UET462HB.js → chunk-I4NNYWIR.js} +2 -2
  25. package/dist/chunk-IH56ZISC.js +620 -0
  26. package/dist/chunk-IH56ZISC.js.map +1 -0
  27. package/dist/{chunk-EDNJLUSE.js → chunk-IHZEFW4L.js} +2 -2
  28. package/dist/chunk-IW2CB755.cjs +1002 -0
  29. package/dist/chunk-IW2CB755.cjs.map +1 -0
  30. package/dist/{chunk-ZWOFQRIB.js → chunk-JCVIOLBF.js} +3 -3
  31. package/dist/{chunk-JQEFP45L.js → chunk-L5EH2NVO.js} +2 -2
  32. package/dist/{chunk-247ANM72.cjs → chunk-NOAQRCV6.cjs} +219 -97
  33. package/dist/chunk-NOAQRCV6.cjs.map +1 -0
  34. package/dist/{chunk-7IAQ4HOK.js → chunk-NQCUAGLR.js} +1 -1
  35. package/dist/chunk-NQCUAGLR.js.map +1 -0
  36. package/dist/{chunk-NS55SM7A.js → chunk-O2PGQNYE.js} +2 -2
  37. package/dist/{chunk-QC5T6D4J.js → chunk-QGW7NMTX.js} +299 -25
  38. package/dist/chunk-QGW7NMTX.js.map +1 -0
  39. package/dist/{chunk-A7QC2ZVP.js → chunk-UVVEBI22.js} +209 -87
  40. package/dist/chunk-UVVEBI22.js.map +1 -0
  41. package/dist/chunk-VJHAUMCF.js +949 -0
  42. package/dist/chunk-VJHAUMCF.js.map +1 -0
  43. package/dist/{chunk-C7FQBSON.js → chunk-VNFJPTU4.js} +2 -2
  44. package/dist/{chunk-VQ5W4MYO.js → chunk-VYOGXNQ3.js} +298 -24
  45. package/dist/chunk-VYOGXNQ3.js.map +1 -0
  46. package/dist/{chunk-DOGPOY4B.js → chunk-WB5LW52Z.js} +2 -2
  47. package/dist/{chunk-TSFS5VKD.js → chunk-YGBZABI4.js} +332 -141
  48. package/dist/chunk-YGBZABI4.js.map +1 -0
  49. package/dist/chunks-I7TEMJXH.js +10 -0
  50. package/dist/cli.js +228 -97
  51. package/dist/cli.js.map +1 -1
  52. package/dist/{formula-PJJ6S3Z2.cjs → formula-CPO2TZGB.cjs} +9 -9
  53. package/dist/{formula-PJJ6S3Z2.cjs.map → formula-CPO2TZGB.cjs.map} +1 -1
  54. package/dist/{formula-5S7WWXGH.js → formula-NOIDZEBA.js} +4 -4
  55. package/dist/{formula-JGFTXWYT.js → formula-X44GV7TN.js} +3 -3
  56. package/dist/{image-ocr-WCAYXLOA.js → image-ocr-72RAR4WR.js} +34 -16
  57. package/dist/image-ocr-72RAR4WR.js.map +1 -0
  58. package/dist/{image-ocr-FLIL7MGN.js → image-ocr-R6W6A75P.js} +32 -14
  59. package/dist/image-ocr-R6W6A75P.js.map +1 -0
  60. package/dist/{image-ocr-ZTYAGV3W.cjs → image-ocr-SUGT64ZM.cjs} +36 -18
  61. package/dist/image-ocr-SUGT64ZM.cjs.map +1 -0
  62. package/dist/index.cjs +2416 -1184
  63. package/dist/index.cjs.map +1 -1
  64. package/dist/index.d.cts +62 -25
  65. package/dist/index.d.ts +62 -25
  66. package/dist/index.js +1853 -621
  67. package/dist/index.js.map +1 -1
  68. package/dist/mcp.js +74 -53
  69. package/dist/mcp.js.map +1 -1
  70. package/dist/{model-bundle-FSPZZ2EM.js → model-bundle-57CLEBCO.js} +5 -5
  71. package/dist/{models-CJPAQK5R.js → models-7ELIFTD7.js} +5 -5
  72. package/dist/{parser-SCUTZAIJ.js → parser-4NKAYWAX.js} +261 -241
  73. package/dist/parser-4NKAYWAX.js.map +1 -0
  74. package/dist/{parser-A4TMPJAM.js → parser-HWFQRSUQ.js} +261 -241
  75. package/dist/parser-HWFQRSUQ.js.map +1 -0
  76. package/dist/{parser-VCXE4I5A.cjs → parser-RANGXGBD.cjs} +289 -269
  77. package/dist/parser-RANGXGBD.cjs.map +1 -0
  78. package/dist/pdf-ocr-BNC34MNU.js +13 -0
  79. package/dist/pdf-ocr-QX2GB33Q.js +16 -0
  80. package/dist/pdf-ocr-VHSFACH7.cjs +14 -0
  81. package/dist/{pdf-ocr-MBZZ57LD.cjs.map → pdf-ocr-VHSFACH7.cjs.map} +1 -1
  82. package/dist/{profile-io-LBM263K7.js → profile-io-V52PL5ZO.js} +3 -3
  83. package/dist/{rasterize-46RG4ZF6.js → rasterize-ZIGLJDOG.js} +3 -3
  84. package/dist/redact-TIQEQI3M.js +18 -0
  85. package/dist/redact-doc-KXZ3TZAG.js +1216 -0
  86. package/dist/redact-doc-KXZ3TZAG.js.map +1 -0
  87. package/dist/{render-NGP5T24S.js → render-U5PE6V2E.js} +7 -7
  88. package/dist/seal-DNXJBILR.js +10 -0
  89. package/dist/{setup-AU4OCHNH.js → setup-YRDM4ODP.js} +3 -3
  90. package/dist/visual-5XWMA4EV.js +19 -0
  91. package/dist/{watch-MKPUCPXM.js → watch-5IOLBATT.js} +12 -12
  92. package/package.json +2 -2
  93. package/dist/chunk-247ANM72.cjs.map +0 -1
  94. package/dist/chunk-3DUADAY2.js.map +0 -1
  95. package/dist/chunk-422427XJ.cjs +0 -571
  96. package/dist/chunk-422427XJ.cjs.map +0 -1
  97. package/dist/chunk-7IAQ4HOK.js.map +0 -1
  98. package/dist/chunk-A7QC2ZVP.js.map +0 -1
  99. package/dist/chunk-DZIXKL7E.js +0 -145
  100. package/dist/chunk-DZIXKL7E.js.map +0 -1
  101. package/dist/chunk-EISO3POT.js.map +0 -1
  102. package/dist/chunk-EP7KOI2V.js +0 -571
  103. package/dist/chunk-EP7KOI2V.js.map +0 -1
  104. package/dist/chunk-FOJZNMMK.js.map +0 -1
  105. package/dist/chunk-ILU76UF3.js.map +0 -1
  106. package/dist/chunk-KVWV4ULP.js +0 -516
  107. package/dist/chunk-KVWV4ULP.js.map +0 -1
  108. package/dist/chunk-L7DMSMAG.cjs.map +0 -1
  109. package/dist/chunk-QC5T6D4J.js.map +0 -1
  110. package/dist/chunk-QOMNEWCQ.cjs.map +0 -1
  111. package/dist/chunk-TSFS5VKD.js.map +0 -1
  112. package/dist/chunk-VQ5W4MYO.js.map +0 -1
  113. package/dist/chunks-SX77P5CV.js +0 -10
  114. package/dist/image-ocr-FLIL7MGN.js.map +0 -1
  115. package/dist/image-ocr-WCAYXLOA.js.map +0 -1
  116. package/dist/image-ocr-ZTYAGV3W.cjs.map +0 -1
  117. package/dist/parser-A4TMPJAM.js.map +0 -1
  118. package/dist/parser-SCUTZAIJ.js.map +0 -1
  119. package/dist/parser-VCXE4I5A.cjs.map +0 -1
  120. package/dist/pdf-ocr-76SAUXMZ.js +0 -13
  121. package/dist/pdf-ocr-CPU55CVF.js +0 -16
  122. package/dist/pdf-ocr-MBZZ57LD.cjs +0 -14
  123. package/dist/redact-7ILEAUTS.js +0 -12
  124. package/dist/seal-RGUPOLXD.js +0 -10
  125. package/dist/visual-NZLYIYEM.js +0 -19
  126. /package/dist/{-AQKXLFIM.js.map → -7XBPXCXN.js.map} +0 -0
  127. /package/dist/{chunk-ZWD734EY.js.map → chunk-D6KQKMU3.js.map} +0 -0
  128. /package/dist/{chunk-GDW4YP7K.js.map → chunk-FAZR34CA.js.map} +0 -0
  129. /package/dist/{chunk-TNCKOL2O.js.map → chunk-GTINVM5K.js.map} +0 -0
  130. /package/dist/{chunk-UET462HB.js.map → chunk-I4NNYWIR.js.map} +0 -0
  131. /package/dist/{chunk-EDNJLUSE.js.map → chunk-IHZEFW4L.js.map} +0 -0
  132. /package/dist/{chunk-ZWOFQRIB.js.map → chunk-JCVIOLBF.js.map} +0 -0
  133. /package/dist/{chunk-JQEFP45L.js.map → chunk-L5EH2NVO.js.map} +0 -0
  134. /package/dist/{chunk-NS55SM7A.js.map → chunk-O2PGQNYE.js.map} +0 -0
  135. /package/dist/{chunk-C7FQBSON.js.map → chunk-VNFJPTU4.js.map} +0 -0
  136. /package/dist/{chunk-DOGPOY4B.js.map → chunk-WB5LW52Z.js.map} +0 -0
  137. /package/dist/{chunks-SX77P5CV.js.map → chunks-I7TEMJXH.js.map} +0 -0
  138. /package/dist/{formula-5S7WWXGH.js.map → formula-NOIDZEBA.js.map} +0 -0
  139. /package/dist/{formula-JGFTXWYT.js.map → formula-X44GV7TN.js.map} +0 -0
  140. /package/dist/{model-bundle-FSPZZ2EM.js.map → model-bundle-57CLEBCO.js.map} +0 -0
  141. /package/dist/{models-CJPAQK5R.js.map → models-7ELIFTD7.js.map} +0 -0
  142. /package/dist/{pdf-ocr-76SAUXMZ.js.map → pdf-ocr-BNC34MNU.js.map} +0 -0
  143. /package/dist/{pdf-ocr-CPU55CVF.js.map → pdf-ocr-QX2GB33Q.js.map} +0 -0
  144. /package/dist/{profile-io-LBM263K7.js.map → profile-io-V52PL5ZO.js.map} +0 -0
  145. /package/dist/{rasterize-46RG4ZF6.js.map → rasterize-ZIGLJDOG.js.map} +0 -0
  146. /package/dist/{redact-7ILEAUTS.js.map → redact-TIQEQI3M.js.map} +0 -0
  147. /package/dist/{render-NGP5T24S.js.map → render-U5PE6V2E.js.map} +0 -0
  148. /package/dist/{seal-RGUPOLXD.js.map → seal-DNXJBILR.js.map} +0 -0
  149. /package/dist/{setup-AU4OCHNH.js.map → setup-YRDM4ODP.js.map} +0 -0
  150. /package/dist/{visual-NZLYIYEM.js.map → visual-5XWMA4EV.js.map} +0 -0
  151. /package/dist/{watch-MKPUCPXM.js.map → watch-5IOLBATT.js.map} +0 -0
@@ -12,13 +12,14 @@ import {
12
12
  paraTextPureT,
13
13
  patchZipEntries,
14
14
  scanSectionXml
15
- } from "./chunk-ZWOFQRIB.js";
15
+ } from "./chunk-JCVIOLBF.js";
16
16
  import {
17
17
  CONVERT_MAP,
18
18
  FLAG_COMPRESSED,
19
19
  FLAG_DISTRIBUTION,
20
20
  FLAG_DRM,
21
21
  FLAG_ENCRYPTED,
22
+ INLINE_TABLE_MARK,
22
23
  MIDDLE_CONVERT_MAP,
23
24
  SPACE_EM_FIXED,
24
25
  TAG_CHAR_SHAPE,
@@ -28,6 +29,9 @@ import {
28
29
  TAG_PARA_TEXT,
29
30
  TAG_TABLE,
30
31
  appendParaText,
32
+ blocksPlainText,
33
+ buildAddressedTable,
34
+ cellTextFromBlocks,
31
35
  charWidthEm1000,
32
36
  circledHangul,
33
37
  circledNumber,
@@ -37,12 +41,15 @@ import {
37
41
  detectImageMime,
38
42
  detectOle2Format,
39
43
  detectZipFormat,
44
+ emitParagraphBlocks,
40
45
  extractRenderedRegions,
41
46
  faceClassForGen,
42
47
  fitRatioForFewerLines,
48
+ formatNumber,
43
49
  hangulAmount,
44
50
  hangulOrdinal,
45
51
  hmlToLatex,
52
+ hwpEquationToLatex,
46
53
  isExtendedOnlyCtrlChar,
47
54
  measureTextWidth,
48
55
  parseFileHeader,
@@ -51,12 +58,12 @@ import {
51
58
  renderDocumentToScene,
52
59
  romanNumeral,
53
60
  simulateWrap
54
- } from "./chunk-TSFS5VKD.js";
61
+ } from "./chunk-YGBZABI4.js";
55
62
  import {
56
63
  HEADING_RATIO_H1,
57
64
  HEADING_RATIO_H2,
58
65
  HEADING_RATIO_H3
59
- } from "./chunk-7IAQ4HOK.js";
66
+ } from "./chunk-NQCUAGLR.js";
60
67
  import {
61
68
  parsePageRange
62
69
  } from "./chunk-MOL7MDBG.js";
@@ -75,16 +82,20 @@ import {
75
82
  findChildByLocalName,
76
83
  localName,
77
84
  rawTextContent
78
- } from "./chunk-ILU76UF3.js";
85
+ } from "./chunk-4RJ5D7MT.js";
79
86
  import {
80
87
  MAX_COLS,
81
88
  MAX_ROWS,
82
89
  blocksToMarkdown,
83
90
  buildTable,
84
91
  convertTableToText,
92
+ escapeGfm,
93
+ escapeLiteralDollar,
94
+ flattenLayoutTables,
85
95
  hasStructuredCellContent,
86
- mapPuaText
87
- } from "./chunk-QC5T6D4J.js";
96
+ mapPuaText,
97
+ noteSuffix
98
+ } from "./chunk-QGW7NMTX.js";
88
99
  import {
89
100
  KordocError,
90
101
  classifyError,
@@ -96,7 +107,7 @@ import {
96
107
  sanitizeHref,
97
108
  stripDtd,
98
109
  toArrayBuffer
99
- } from "./chunk-UET462HB.js";
110
+ } from "./chunk-I4NNYWIR.js";
100
111
 
101
112
  // src/table/visual.ts
102
113
  import { readFile as readFile2 } from "fs/promises";
@@ -654,7 +665,7 @@ function buildTableWithCellMeta(state, keepAnchoredEmptyCols, keepEmptyParagraph
654
665
  for (const row of state.rows) {
655
666
  for (const src of row) {
656
667
  flatIdx++;
657
- const needsBlocks = !!src.blocks && src.blocks.length > 0 && (src.hasStructure || src.blocks.some((b) => b.spans));
668
+ const needsBlocks = !!src.blocks && src.blocks.length > 0 && (src.hasStructure || src.blocks.some((b) => b.spans || b.footnoteText));
658
669
  if (!needsBlocks && !src.isHeader) continue;
659
670
  let target;
660
671
  const trimmed = keepEmptyParagraphs ? src.text : src.text.trim();
@@ -686,7 +697,7 @@ function buildTableWithCellMeta(state, keepAnchoredEmptyCols, keepEmptyParagraph
686
697
  }
687
698
  function completeTable(newTable, tableStack, blocks, ctx) {
688
699
  const parentTable = tableStack.length > 0 ? tableStack.pop() : null;
689
- if (newTable.rows.length === 0) {
700
+ if (newTable.rows.every((r) => r.length === 0)) {
690
701
  if (newTable.caption) blocks.push({ type: "paragraph", text: newTable.caption, pageNumber: ctx.page });
691
702
  return parentTable;
692
703
  }
@@ -836,6 +847,162 @@ function detectHwpxSectionPages(root) {
836
847
  };
837
848
  }
838
849
 
850
+ // src/hwpx/notes.ts
851
+ function codePointAttr(v) {
852
+ if (v == null || v === "") return void 0;
853
+ const n = parseInt(v, 10);
854
+ if (!Number.isFinite(n) || n < 0 || n > 1114111) return void 0;
855
+ return n === 0 ? "" : String.fromCodePoint(n);
856
+ }
857
+ function noteFormatFrom(get) {
858
+ return {
859
+ type: get("type") || "DIGIT",
860
+ userChar: get("userChar") ?? "",
861
+ prefixChar: get("prefixChar") ?? "",
862
+ suffixChar: get("suffixChar") ?? ""
863
+ };
864
+ }
865
+ function noteCore(num2, fmt, userChar) {
866
+ if (fmt.type === "USER_CHAR") return userChar ?? fmt.userChar;
867
+ return formatHeadNumber(num2, fmt.type);
868
+ }
869
+ function noteRefMark(attrs, fmt) {
870
+ const f = fmt ?? { type: "DIGIT", userChar: "", prefixChar: "", suffixChar: ")" };
871
+ const n = parseInt(attrs.number ?? "", 10);
872
+ const prefix = codePointAttr(attrs.prefixChar) ?? f.prefixChar;
873
+ const suffix = codePointAttr(attrs.suffixChar) ?? f.suffixChar;
874
+ return prefix + noteCore(Number.isFinite(n) ? n : 1, f, codePointAttr(attrs.userChar)) + suffix;
875
+ }
876
+ function noteAutoNumText(num2, fmt) {
877
+ const n = parseInt(num2 ?? "", 10);
878
+ return fmt.prefixChar + noteCore(Number.isFinite(n) ? n : 1, fmt) + fmt.suffixChar;
879
+ }
880
+ function isRenderedAutoNum(numType) {
881
+ return numType === "FOOTNOTE" || numType === "ENDNOTE" || numType === "PICTURE" || numType === "TABLE" || numType === "EQUATION";
882
+ }
883
+ function noteAttrsOf(el) {
884
+ return {
885
+ number: el.getAttribute("number"),
886
+ prefixChar: el.getAttribute("prefixChar"),
887
+ suffixChar: el.getAttribute("suffixChar"),
888
+ userChar: el.getAttribute("userChar")
889
+ };
890
+ }
891
+ function readSectionNoteFormats(root) {
892
+ const out = {};
893
+ const pick2 = (names) => {
894
+ for (const name of names) {
895
+ const els = root.getElementsByTagName(name);
896
+ if (els.length === 0) continue;
897
+ const f = findChildByLocalName(els[0], "autoNumFormat");
898
+ return f ? noteFormatFrom((n) => f.getAttribute(n)) : void 0;
899
+ }
900
+ return void 0;
901
+ };
902
+ out.footnote = pick2(["hp:footNotePr", "footNotePr"]);
903
+ out.endnote = pick2(["hp:endNotePr", "endNotePr"]);
904
+ return out;
905
+ }
906
+ function noteAutoNumOf(el) {
907
+ if (!isRenderedAutoNum(el.getAttribute("numType"))) return "";
908
+ const f = findChildByLocalName(el, "autoNumFormat");
909
+ return noteAutoNumText(el.getAttribute("num"), noteFormatFrom((n) => f?.getAttribute(n)));
910
+ }
911
+
912
+ // src/hwpx/run-spans.ts
913
+ var KORDOC_CHAR_CODE = "4";
914
+ var KORDOC_PARA_QUOTE = "6";
915
+ function spanModeOf(layout) {
916
+ if (layout === "default") return "kordoc";
917
+ if (layout === "gongmun") return "gongmun";
918
+ if (!layout) return "foreign";
919
+ return null;
920
+ }
921
+ function extractRunSpans(para2, ctx, mode, requireMixed) {
922
+ const styleMap = ctx.styleMap;
923
+ if (!styleMap) return null;
924
+ const spans = [];
925
+ let styled = false;
926
+ const kids = para2.childNodes;
927
+ if (!kids) return null;
928
+ for (let i = 0; i < kids.length; i++) {
929
+ const child = kids[i];
930
+ if (child.nodeType !== 1) continue;
931
+ const tag = (child.tagName || child.localName || "").replace(/^[^:]+:/, "");
932
+ if (tag === "linesegarray") continue;
933
+ if (tag !== "run" && tag !== "r") return null;
934
+ let text = "";
935
+ const rkids = child.childNodes;
936
+ for (let j = 0; j < (rkids?.length ?? 0); j++) {
937
+ const rc = rkids[j];
938
+ if (rc.nodeType !== 1) continue;
939
+ const rtag = (rc.tagName || rc.localName || "").replace(/^[^:]+:/, "");
940
+ if (rtag === "t") {
941
+ const tkids = rc.childNodes;
942
+ for (let k = 0; k < (tkids?.length ?? 0); k++) {
943
+ const tk = tkids[k];
944
+ if (tk.nodeType === 3 || tk.nodeType === 4) text += escapeLiteralDollar(tk.textContent || "");
945
+ else if (tk.nodeType === 1) {
946
+ const ttag = (tk.tagName || tk.localName || "").replace(/^[^:]+:/, "");
947
+ if (ttag === "tab" || ttag === "fwSpace" || ttag === "hwSpace" || ttag === "nbSpace") text += " ";
948
+ else return null;
949
+ }
950
+ }
951
+ } else if (rtag === "secPr" || rtag === "colPr") {
952
+ } else if (rtag === "ctrl") {
953
+ if (extractTextFromNode(rc)) return null;
954
+ } else {
955
+ return null;
956
+ }
957
+ }
958
+ if (!text) continue;
959
+ const prId = child.getAttribute("charPrIDRef") ?? "";
960
+ const cp = styleMap.charProperties.get(prId);
961
+ const span = { text };
962
+ if (mode !== "foreign" && prId === KORDOC_CHAR_CODE) span.code = true;
963
+ else {
964
+ if (cp?.bold) span.bold = true;
965
+ if (cp?.italic) span.italic = true;
966
+ if (cp?.strike) span.strike = true;
967
+ if (cp?.underline) span.underline = true;
968
+ }
969
+ if (span.bold || span.italic || span.strike || span.underline || span.code) styled = true;
970
+ spans.push(span);
971
+ }
972
+ if (!styled || spans.length === 0) return null;
973
+ const merged = [];
974
+ for (const s of spans) {
975
+ const last = merged[merged.length - 1];
976
+ if (last && !!last.bold === !!s.bold && !!last.italic === !!s.italic && !!last.strike === !!s.strike && !!last.underline === !!s.underline && !!last.code === !!s.code) {
977
+ last.text += s.text;
978
+ } else {
979
+ merged.push(s);
980
+ }
981
+ }
982
+ if (requireMixed && !merged.some((s) => s.strike || s.underline) && !merged.some((s) => !(s.bold || s.italic || s.code))) return null;
983
+ return merged;
984
+ }
985
+ function gongmunDepthFromIndent(para2, left, ctx) {
986
+ const styleMap = ctx.styleMap;
987
+ if (!styleMap) return null;
988
+ let unit = 0;
989
+ const kids = para2.childNodes;
990
+ for (let i = 0; i < (kids?.length ?? 0); i++) {
991
+ const child = kids[i];
992
+ if (child.nodeType !== 1) continue;
993
+ const tag = (child.tagName || child.localName || "").replace(/^[^:]+:/, "");
994
+ if (tag !== "run" && tag !== "r") continue;
995
+ const size = styleMap.charProperties.get(child.getAttribute("charPrIDRef") ?? "")?.fontSize;
996
+ if (size) unit = size * 100;
997
+ break;
998
+ }
999
+ if (!unit) return null;
1000
+ const r = left / unit;
1001
+ const nearInt = Math.round(r);
1002
+ const depth = Math.abs(r - nearInt) < 0.2 ? nearInt : Math.round(2 * r - 1);
1003
+ return depth >= 1 && depth < 8 ? depth : null;
1004
+ }
1005
+
839
1006
  // src/hwpx/section-walker.ts
840
1007
  function parseSectionXml(xml, styleMap, warnings, sectionNum, shared) {
841
1008
  const parser = createXmlParser(warnings);
@@ -858,6 +1025,7 @@ function parseSectionXml(xml, styleMap, warnings, sectionNum, shared) {
858
1025
  break;
859
1026
  }
860
1027
  }
1028
+ ctx.noteFormats = readSectionNoteFormats(doc.documentElement);
861
1029
  const blocks = [];
862
1030
  walkSection(doc.documentElement, blocks, null, [], ctx);
863
1031
  return blocks;
@@ -924,7 +1092,7 @@ function walkSection(node, blocks, tableCtx, tableStack, ctx, depth = 0) {
924
1092
  if (tableCtx) {
925
1093
  tableCtx.currentRow = [];
926
1094
  walkSection(el, blocks, tableCtx, tableStack, ctx, depth + 1);
927
- if (tableCtx.currentRow.length > 0) tableCtx.rows.push(tableCtx.currentRow);
1095
+ tableCtx.rows.push(tableCtx.currentRow);
928
1096
  tableCtx.currentRow = [];
929
1097
  }
930
1098
  break;
@@ -960,7 +1128,7 @@ function walkSection(node, blocks, tableCtx, tableStack, ctx, depth = 0) {
960
1128
  case "p": {
961
1129
  const paraPg = ctx.paraPage?.get(el);
962
1130
  if (paraPg !== void 0 && ctx.pageBase !== void 0) ctx.page = ctx.pageBase + paraPg + 1;
963
- const { text: rawText, href, footnote, style, segments } = extractParagraphInfo(el, ctx.styleMap, ctx);
1131
+ const { text: rawText, href, footnote, style, segments, placeholderSpans } = extractParagraphInfo(el, ctx.styleMap, ctx);
964
1132
  let text = rawText;
965
1133
  let headingLevel;
966
1134
  const ph = resolveParaHeading(el, ctx);
@@ -978,10 +1146,12 @@ function walkSection(node, blocks, tableCtx, tableStack, ctx, depth = 0) {
978
1146
  }
979
1147
  let segIdx = 0;
980
1148
  let first = true;
1149
+ let lastCellBlock;
981
1150
  const flush = () => {
982
1151
  const s = segs[segIdx++];
983
1152
  if (!s) return;
984
1153
  const block = { type: "paragraph", text: s, pageNumber: ctx.page };
1154
+ if (cell2) lastCellBlock = block;
985
1155
  if (first && !cell2) {
986
1156
  first = false;
987
1157
  if (style) block.style = style;
@@ -996,16 +1166,19 @@ function walkSection(node, blocks, tableCtx, tableStack, ctx, depth = 0) {
996
1166
  };
997
1167
  tableCtx = walkParagraphChildren(el, blocks, tableCtx, tableStack, ctx, depth + 1, flush);
998
1168
  while (segIdx < segs.length) flush();
999
- if (footnote && cell2) cell2.text += (cell2.text ? "\n" : "") + `(\uC8FC: ${footnote})`;
1169
+ if (footnote && cell2) {
1170
+ cell2.text += (cell2.text ? "\n" : "") + `(\uC8FC: ${footnote})`;
1171
+ if (lastCellBlock) lastCellBlock.footnoteText = footnote;
1172
+ }
1000
1173
  break;
1001
1174
  }
1002
1175
  if (text) {
1003
1176
  if (tableCtx?.cell) {
1004
1177
  const cell2 = tableCtx.cell;
1005
- if (footnote) text += ` (\uC8FC: ${footnote})`;
1006
- cell2.text += (cell2.text || cell2.paraSeen ? "\n" : "") + text;
1178
+ cell2.text += (cell2.text || cell2.paraSeen ? "\n" : "") + (footnote ? `${text} (\uC8FC: ${footnote})` : text);
1007
1179
  cell2.paraSeen = true;
1008
1180
  const cellBlock = { type: "paragraph", text, pageNumber: ctx.page };
1181
+ if (footnote) cellBlock.footnoteText = footnote;
1009
1182
  const cellSpanMode = spanModeOf(ctx.shared.kordocLayout);
1010
1183
  if (cellSpanMode && !ph?.prefix) {
1011
1184
  const spans = extractRunSpans(el, ctx, cellSpanMode, cellSpanMode !== "kordoc");
@@ -1013,7 +1186,7 @@ function walkSection(node, blocks, tableCtx, tableStack, ctx, depth = 0) {
1013
1186
  cellBlock.spans = spans;
1014
1187
  }
1015
1188
  }
1016
- ;
1189
+ if (!cellBlock.spans && placeholderSpans) cellBlock.spans = withPrefixSpan(placeholderSpans, ph?.prefix);
1017
1190
  (cell2.blocks ??= []).push(cellBlock);
1018
1191
  } else if (!tableCtx) {
1019
1192
  if (ctx.shared.kordocLayout && /^─{10,}$/.test(text)) {
@@ -1045,6 +1218,7 @@ function walkSection(node, blocks, tableCtx, tableStack, ctx, depth = 0) {
1045
1218
  }
1046
1219
  if (spanMode !== "foreign" && el.getAttribute("paraPrIDRef") === KORDOC_PARA_QUOTE) block.quote = true;
1047
1220
  }
1221
+ if (!headingLevel && !block.spans && placeholderSpans) block.spans = withPrefixSpan(placeholderSpans, ph?.prefix);
1048
1222
  blocks.push(block);
1049
1223
  } else {
1050
1224
  blocks.push({ type: "paragraph", text, pageNumber: ctx.page });
@@ -1124,7 +1298,7 @@ function userShapeComment(el) {
1124
1298
  function mergeBlocksIntoCell(cell2, sink) {
1125
1299
  for (const b of sink) {
1126
1300
  if ((b.type === "paragraph" || b.type === "heading") && b.text) {
1127
- cell2.text += (cell2.text ? "\n" : "") + b.text;
1301
+ cell2.text += (cell2.text ? "\n" : "") + b.text + (b.footnoteText ? ` (\uC8FC: ${b.footnoteText})` : "");
1128
1302
  (cell2.blocks ??= []).push(b);
1129
1303
  } else if (b.type === "image" || b.type === "table") {
1130
1304
  if (b.type === "image" && b.text) {
@@ -1180,7 +1354,7 @@ function hasPageAutoNum(el, depth = 0) {
1180
1354
  function collectSubListText(el, ctx, depth = 0) {
1181
1355
  return collectSubListContent(el, ctx, depth).text;
1182
1356
  }
1183
- function collectSubListContent(el, ctx, depth = 0) {
1357
+ function collectSubListContent(el, ctx, depth = 0, sep = "\n") {
1184
1358
  const out = { text: "", blocks: [], hasStructure: false };
1185
1359
  if (depth > 10) return out;
1186
1360
  const parts = [];
@@ -1191,7 +1365,9 @@ function collectSubListContent(el, ctx, depth = 0) {
1191
1365
  if (ch.nodeType !== 1) continue;
1192
1366
  const tag = (ch.tagName || ch.localName || "").replace(/^[^:]+:/, "");
1193
1367
  if (tag === "p" || tag === "para") {
1194
- const t = extractParagraphInfo(ch, ctx.styleMap, ctx).text;
1368
+ let t = extractParagraphInfo(ch, ctx.styleMap, ctx).text;
1369
+ const ph = resolveParaHeading(ch, ctx);
1370
+ if (t && ph?.prefix) t = ph.prefix + " " + t;
1195
1371
  if (t) {
1196
1372
  parts.push(t);
1197
1373
  out.blocks.push({ type: "paragraph", text: t, pageNumber: ctx.page });
@@ -1206,6 +1382,10 @@ function collectSubListContent(el, ctx, depth = 0) {
1206
1382
  out.hasStructure = true;
1207
1383
  }
1208
1384
  }
1385
+ for (const t2 of drawTextParaTexts(ch, ctx)) {
1386
+ parts.push(t2);
1387
+ out.blocks.push({ type: "paragraph", text: t2, pageNumber: ctx.page });
1388
+ }
1209
1389
  } else if (tag === "tbl") {
1210
1390
  const built = buildSubListTable(ch, ctx, depth);
1211
1391
  if (built.text) parts.push(built.text);
@@ -1214,13 +1394,41 @@ function collectSubListContent(el, ctx, depth = 0) {
1214
1394
  out.hasStructure = true;
1215
1395
  }
1216
1396
  } else {
1217
- const sub = collectSubListContent(ch, ctx, depth + 1);
1397
+ const sub = collectSubListContent(ch, ctx, depth + 1, sep);
1218
1398
  if (sub.text) parts.push(sub.text);
1219
1399
  out.blocks.push(...sub.blocks);
1220
1400
  if (sub.hasStructure) out.hasStructure = true;
1221
1401
  }
1222
1402
  }
1223
- out.text = parts.join("\n").trim();
1403
+ out.text = parts.join(sep).trim();
1404
+ return out;
1405
+ }
1406
+ function drawTextParaTexts(para2, ctx) {
1407
+ const out = [];
1408
+ const visit = (node, depth, inDrawText) => {
1409
+ if (depth > MAX_XML_DEPTH) return;
1410
+ const kids = node.childNodes;
1411
+ if (!kids) return;
1412
+ for (let i = 0; i < kids.length; i++) {
1413
+ const ch = kids[i];
1414
+ if (ch.nodeType !== 1) continue;
1415
+ const tag = (ch.tagName || ch.localName || "").replace(/^[^:]+:/, "");
1416
+ if (tag === "tbl" || tag === "footNote" || tag === "endNote") continue;
1417
+ if (tag === "drawText") {
1418
+ if (!inDrawText) visit(ch, depth + 1, true);
1419
+ continue;
1420
+ }
1421
+ if (inDrawText && (tag === "p" || tag === "para")) {
1422
+ let t = extractParagraphInfo(ch, ctx.styleMap, ctx).text.trim();
1423
+ const ph = resolveParaHeading(ch, ctx);
1424
+ if (t && ph?.prefix) t = ph.prefix + " " + t;
1425
+ if (t) out.push(t);
1426
+ continue;
1427
+ }
1428
+ visit(ch, depth + 1, inDrawText);
1429
+ }
1430
+ };
1431
+ visit(para2, 0, false);
1224
1432
  return out;
1225
1433
  }
1226
1434
  function buildSubListTable(el, ctx, depth) {
@@ -1368,6 +1576,29 @@ function extractDrawTextBlocks(drawTextNode, blocks, ctx) {
1368
1576
  }
1369
1577
  }
1370
1578
  }
1579
+ var PH_OPEN = "";
1580
+ var PH_CLOSE = "";
1581
+ function clickHereGuide(fieldBegin) {
1582
+ if ((fieldBegin.getAttribute("type") || "").toUpperCase() !== "CLICK_HERE") return void 0;
1583
+ if (fieldBegin.getAttribute("dirty") === "1") return void 0;
1584
+ const children = findChildByLocalName(fieldBegin, "parameters")?.childNodes;
1585
+ if (!children) return void 0;
1586
+ let fromCommand;
1587
+ for (let i = 0; i < children.length; i++) {
1588
+ const ch = children[i];
1589
+ if (ch.nodeType !== 1) continue;
1590
+ const tag = (ch.tagName || ch.localName || "").replace(/^[^:]+:/, "");
1591
+ if (tag !== "stringParam") continue;
1592
+ const name = ch.getAttribute("name");
1593
+ if (name === "Direction") return ch.textContent || void 0;
1594
+ if (name === "Command") {
1595
+ const cmd = ch.textContent || "";
1596
+ const m = /Direction:wstring:(\d+):/.exec(cmd);
1597
+ if (m) fromCommand = cmd.slice(m.index + m[0].length, m.index + m[0].length + Number(m[1])) || void 0;
1598
+ }
1599
+ }
1600
+ return fromCommand;
1601
+ }
1371
1602
  function extractHyperlinkHref(fieldBegin) {
1372
1603
  if ((fieldBegin.getAttribute("type") || "").toUpperCase() !== "HYPERLINK") return void 0;
1373
1604
  const params = findChildByLocalName(fieldBegin, "parameters");
@@ -1404,12 +1635,26 @@ function extractParagraphInfo(para2, styleMap, ctx) {
1404
1635
  openFields.push({ rangeIdx: linkRanges.length - 1 });
1405
1636
  if (!href) href = url;
1406
1637
  } else {
1407
- openFields.push({});
1638
+ const guide = clickHereGuide(el);
1639
+ openFields.push(guide ? { guide, start: text.length } : {});
1408
1640
  }
1409
1641
  };
1410
1642
  const onFieldEnd = () => {
1411
1643
  const open = openFields.pop();
1412
1644
  if (open?.rangeIdx !== void 0) linkRanges[open.rangeIdx].end = text.length;
1645
+ if (open?.guide !== void 0 && open.start !== void 0) {
1646
+ const value = text.slice(open.start).replace(/\\\$/g, "$");
1647
+ if (value && (value === open.guide || value.trimEnd() === open.guide)) {
1648
+ text = text.slice(0, open.start) + PH_OPEN + text.slice(open.start) + PH_CLOSE;
1649
+ }
1650
+ }
1651
+ };
1652
+ const addNote = (noteEl, tag) => {
1653
+ if (isInDeletedRange(ctx)) return;
1654
+ const endnote = tag === "endNote" || tag === "en";
1655
+ text += noteRefMark(noteAttrsOf(noteEl), endnote ? ctx?.noteFormats?.endnote : ctx?.noteFormats?.footnote);
1656
+ const noteText = ctx ? collectSubListContent(noteEl, ctx, 0, " ").text : extractTextFromNode(noteEl);
1657
+ if (noteText) footnote = (footnote ? footnote + "; " : "") + noteText;
1413
1658
  };
1414
1659
  const handleCtrl = (ctrlEl) => {
1415
1660
  const kids = ctrlEl.childNodes;
@@ -1433,13 +1678,11 @@ function extractParagraphInfo(para2, styleMap, ctx) {
1433
1678
  }
1434
1679
  break;
1435
1680
  }
1436
- // 각주/미주 — 해당 문단의 footnote로 인라인 보존
1681
+ // 각주/미주 — 개체 자리에 참조 부호, 본문은 해당 문단의 footnote로 인라인 보존
1437
1682
  case "footNote":
1438
- case "endNote": {
1439
- const noteText = extractTextFromNode(k);
1440
- if (noteText) footnote = (footnote ? footnote + "; " : "") + noteText;
1683
+ case "endNote":
1684
+ addNote(k, ktag);
1441
1685
  break;
1442
- }
1443
1686
  // 하이퍼링크 — fieldBegin type=HYPERLINK의 Path 파라미터 (extent 오프셋 추적)
1444
1687
  case "fieldBegin":
1445
1688
  onFieldBegin(k);
@@ -1465,13 +1708,16 @@ function extractParagraphInfo(para2, styleMap, ctx) {
1465
1708
  }
1466
1709
  break;
1467
1710
  }
1711
+ // 자동번호 — 주석 머리 번호("1)"·"문1)")·캡션 번호("<그림 1>")만 그린다. 쪽번호 등은 종전대로 미방출
1712
+ case "autoNum":
1713
+ if (!isInDeletedRange(ctx)) text += noteAutoNumOf(k);
1714
+ break;
1468
1715
  // 콘텐츠 없는 제어 요소 — 스킵
1469
1716
  case "bookmark":
1470
1717
  case "pageNum":
1471
1718
  case "pageNumCtrl":
1472
1719
  case "pageHiding":
1473
1720
  case "newNum":
1474
- case "autoNum":
1475
1721
  case "indexmark":
1476
1722
  case "colPr":
1477
1723
  break;
@@ -1501,7 +1747,7 @@ function extractParagraphInfo(para2, styleMap, ctx) {
1501
1747
  ctx.warnings?.push({ page: ctx.page, message: "\uBCC0\uACBD\uCD94\uC801 \uC0AD\uC81C \uD14D\uC2A4\uD2B8 \uCD9C\uB825 \uC81C\uC678", code: "HIDDEN_TEXT_FILTERED" });
1502
1748
  }
1503
1749
  } else {
1504
- text += t.replace(/\x1E/g, "");
1750
+ text += escapeLiteralDollar(t.replace(/\x1E/g, ""));
1505
1751
  }
1506
1752
  continue;
1507
1753
  }
@@ -1554,9 +1800,14 @@ function extractParagraphInfo(para2, styleMap, ctx) {
1554
1800
  case "footNote":
1555
1801
  case "endNote":
1556
1802
  case "fn":
1557
- case "en": {
1558
- const noteText = extractTextFromNode(child);
1559
- if (noteText) footnote = (footnote ? footnote + "; " : "") + noteText;
1803
+ case "en":
1804
+ addNote(child, tag);
1805
+ break;
1806
+ // 대체 표현 묶음 — 한컴은 지원하는 hp:case 하나만 그린다(차트, 없으면 hp:default 의 OLE).
1807
+ // 둘 다 순회하면 같은 캡션이 두 번 나온다 (1790387 [그림 5] 이중 방출)
1808
+ case "switch": {
1809
+ const branch = findChildByLocalName(child, "case") ?? findChildByLocalName(child, "default");
1810
+ if (branch) walk(branch, depth + 1);
1560
1811
  break;
1561
1812
  }
1562
1813
  // 제어 요소 — 선별 순회 (머리말/꼬리말/각주/하이퍼링크/변경추적, v3.0)
@@ -1608,7 +1859,7 @@ function extractParagraphInfo(para2, styleMap, ctx) {
1608
1859
  if (raw.trim()) {
1609
1860
  try {
1610
1861
  const latex = hmlToLatex(raw).trim();
1611
- if (latex) text += " $" + latex + "$ ";
1862
+ if (latex) text += " $" + latex.replace(/\$/g, "\\$") + "$ ";
1612
1863
  } catch {
1613
1864
  if (ctx?.warnings) {
1614
1865
  ctx.warnings.push({
@@ -1657,10 +1908,21 @@ function extractParagraphInfo(para2, styleMap, ctx) {
1657
1908
  };
1658
1909
  let segments;
1659
1910
  if (text.includes("")) {
1660
- segments = text.split("").map(cleanParaText);
1911
+ segments = text.split("").map((s) => cleanParaText(stripPlaceholderMarks(s)));
1661
1912
  text = text.replace(/\x1E/g, "");
1662
1913
  }
1663
- const cleanText = cleanParaText(text);
1914
+ let cleanText = cleanParaText(segments ? stripPlaceholderMarks(text) : text);
1915
+ let placeholderSpans;
1916
+ if (cleanText.includes(PH_OPEN)) {
1917
+ placeholderSpans = [];
1918
+ for (const part of cleanText.split(/(\x1C[^\x1C\x1D]*\x1D)/)) {
1919
+ if (!part) continue;
1920
+ if (part.startsWith(PH_OPEN)) placeholderSpans.push({ text: part.slice(1, -1), placeholder: true });
1921
+ else placeholderSpans.push({ text: stripPlaceholderMarks(part) });
1922
+ }
1923
+ cleanText = stripPlaceholderMarks(cleanText);
1924
+ if (!placeholderSpans.some((s) => s.placeholder && s.text)) placeholderSpans = void 0;
1925
+ }
1664
1926
  let style;
1665
1927
  if (styleMap && charPrId) {
1666
1928
  const charProp = styleMap.charProperties.get(charPrId);
@@ -1673,99 +1935,13 @@ function extractParagraphInfo(para2, styleMap, ctx) {
1673
1935
  if (!style.fontSize && !style.bold && !style.italic) style = void 0;
1674
1936
  }
1675
1937
  }
1676
- return { text: cleanText, href, footnote, style, segments };
1938
+ return { text: cleanText, href, footnote, style, segments, placeholderSpans };
1677
1939
  }
1678
- var KORDOC_CHAR_CODE = "4";
1679
- var KORDOC_PARA_QUOTE = "6";
1680
- function spanModeOf(layout) {
1681
- if (layout === "default") return "kordoc";
1682
- if (layout === "gongmun") return "gongmun";
1683
- if (!layout) return "foreign";
1684
- return null;
1685
- }
1686
- function extractRunSpans(para2, ctx, mode, requireMixed) {
1687
- const styleMap = ctx.styleMap;
1688
- if (!styleMap) return null;
1689
- const spans = [];
1690
- let styled = false;
1691
- const kids = para2.childNodes;
1692
- if (!kids) return null;
1693
- for (let i = 0; i < kids.length; i++) {
1694
- const child = kids[i];
1695
- if (child.nodeType !== 1) continue;
1696
- const tag = (child.tagName || child.localName || "").replace(/^[^:]+:/, "");
1697
- if (tag === "linesegarray") continue;
1698
- if (tag !== "run" && tag !== "r") return null;
1699
- let text = "";
1700
- const rkids = child.childNodes;
1701
- for (let j = 0; j < (rkids?.length ?? 0); j++) {
1702
- const rc = rkids[j];
1703
- if (rc.nodeType !== 1) continue;
1704
- const rtag = (rc.tagName || rc.localName || "").replace(/^[^:]+:/, "");
1705
- if (rtag === "t") {
1706
- const tkids = rc.childNodes;
1707
- for (let k = 0; k < (tkids?.length ?? 0); k++) {
1708
- const tk = tkids[k];
1709
- if (tk.nodeType === 3 || tk.nodeType === 4) text += tk.textContent || "";
1710
- else if (tk.nodeType === 1) {
1711
- const ttag = (tk.tagName || tk.localName || "").replace(/^[^:]+:/, "");
1712
- if (ttag === "tab" || ttag === "fwSpace" || ttag === "hwSpace" || ttag === "nbSpace") text += " ";
1713
- else return null;
1714
- }
1715
- }
1716
- } else if (rtag === "secPr" || rtag === "colPr") {
1717
- } else if (rtag === "ctrl") {
1718
- if (extractTextFromNode(rc)) return null;
1719
- } else {
1720
- return null;
1721
- }
1722
- }
1723
- if (!text) continue;
1724
- const prId = child.getAttribute("charPrIDRef") ?? "";
1725
- const cp = styleMap.charProperties.get(prId);
1726
- const span = { text };
1727
- if (mode !== "foreign" && prId === KORDOC_CHAR_CODE) span.code = true;
1728
- else {
1729
- if (cp?.bold) span.bold = true;
1730
- if (cp?.italic) span.italic = true;
1731
- if (cp?.strike) span.strike = true;
1732
- if (cp?.underline) span.underline = true;
1733
- }
1734
- if (span.bold || span.italic || span.strike || span.underline || span.code) styled = true;
1735
- spans.push(span);
1736
- }
1737
- if (!styled || spans.length === 0) return null;
1738
- const merged = [];
1739
- for (const s of spans) {
1740
- const last = merged[merged.length - 1];
1741
- if (last && !!last.bold === !!s.bold && !!last.italic === !!s.italic && !!last.strike === !!s.strike && !!last.underline === !!s.underline && !!last.code === !!s.code) {
1742
- last.text += s.text;
1743
- } else {
1744
- merged.push(s);
1745
- }
1746
- }
1747
- if (requireMixed && !merged.some((s) => s.strike || s.underline) && !merged.some((s) => !(s.bold || s.italic || s.code))) return null;
1748
- return merged;
1940
+ function withPrefixSpan(spans, prefix) {
1941
+ return prefix ? [{ text: prefix + " " }, ...spans] : spans;
1749
1942
  }
1750
- function gongmunDepthFromIndent(para2, left, ctx) {
1751
- const styleMap = ctx.styleMap;
1752
- if (!styleMap) return null;
1753
- let unit = 0;
1754
- const kids = para2.childNodes;
1755
- for (let i = 0; i < (kids?.length ?? 0); i++) {
1756
- const child = kids[i];
1757
- if (child.nodeType !== 1) continue;
1758
- const tag = (child.tagName || child.localName || "").replace(/^[^:]+:/, "");
1759
- if (tag !== "run" && tag !== "r") continue;
1760
- const size = styleMap.charProperties.get(child.getAttribute("charPrIDRef") ?? "")?.fontSize;
1761
- if (size) unit = size * 100;
1762
- break;
1763
- }
1764
- if (!unit) return null;
1765
- const r = left / unit;
1766
- const nearInt = Math.round(r);
1767
- const depth = Math.abs(r - nearInt) < 0.2 ? nearInt : Math.round(2 * r - 1);
1768
- return depth >= 1 && depth < 8 ? depth : null;
1943
+ function stripPlaceholderMarks(s) {
1944
+ return s.includes(PH_OPEN) || s.includes(PH_CLOSE) ? s.replace(/[\x1C\x1D]/g, "") : s;
1769
1945
  }
1770
1946
 
1771
1947
  // src/hwpx/images.ts
@@ -2038,21 +2214,65 @@ function parseSectionPathsFromManifest(xml) {
2038
2214
  // src/hwpx/metadata.ts
2039
2215
  async function extractHwpxMetadata(zip, metadata, decompressed) {
2040
2216
  try {
2041
- const metaPaths = ["meta.xml", "META-INF/meta.xml", "docProps/core.xml"];
2042
- for (const mp of metaPaths) {
2043
- const file = zip.file(mp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === mp.toLowerCase()) || null;
2044
- if (!file) continue;
2217
+ const read = async (path) => {
2218
+ const file = zip.file(path) || Object.values(zip.files).find((f) => f.name.toLowerCase() === path.toLowerCase()) || null;
2219
+ if (!file) return null;
2045
2220
  const xml = await file.async("text");
2046
2221
  if (decompressed) {
2047
2222
  decompressed.total += xml.length * 2;
2048
2223
  if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
2049
2224
  }
2225
+ return xml;
2226
+ };
2227
+ for (const hp of ["Contents/content.hpf", "content.hpf"]) {
2228
+ const xml = await read(hp);
2229
+ if (!xml) continue;
2230
+ parseOpfMetadata(xml, metadata);
2231
+ break;
2232
+ }
2233
+ if (metadata.title || metadata.author) return;
2234
+ const metaPaths = ["meta.xml", "META-INF/meta.xml", "docProps/core.xml"];
2235
+ for (const mp of metaPaths) {
2236
+ const xml = await read(mp);
2237
+ if (!xml) continue;
2050
2238
  parseDublinCoreMetadata(xml, metadata);
2051
2239
  if (metadata.title || metadata.author) return;
2052
2240
  }
2053
2241
  } catch {
2054
2242
  }
2055
2243
  }
2244
+ function parseOpfMetadata(xml, metadata) {
2245
+ const root = createXmlParser().parseFromString(stripDtd(xml), "text/xml").documentElement;
2246
+ const md = root ? findChildByLocalName(root, "metadata") : null;
2247
+ if (!md) return;
2248
+ let title;
2249
+ const meta = /* @__PURE__ */ new Map();
2250
+ for (const el of elementChildren(md)) {
2251
+ const text = el.textContent?.trim();
2252
+ if (!text) continue;
2253
+ const tag = localName(el);
2254
+ if (tag === "title") title ??= text;
2255
+ else if (tag === "meta") {
2256
+ const name = el.getAttribute("name");
2257
+ if (name && !meta.has(name)) meta.set(name, text);
2258
+ }
2259
+ }
2260
+ const put = (k, v) => {
2261
+ if (v !== void 0 && !metadata[k]) metadata[k] = v;
2262
+ };
2263
+ const keywords = meta.get("keyword")?.split(/[,;]/).map((k) => k.trim()).filter(Boolean);
2264
+ put("title", title);
2265
+ put("author", meta.get("creator"));
2266
+ put("description", meta.get("description") || meta.get("subject"));
2267
+ put("keywords", keywords?.length ? keywords : void 0);
2268
+ put("createdAt", isoDate(meta.get("CreatedDate")));
2269
+ put("modifiedAt", isoDate(meta.get("ModifiedDate")));
2270
+ }
2271
+ function isoDate(s) {
2272
+ if (!s || s.startsWith("1601-01-01")) return void 0;
2273
+ const m = /^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2})$/.exec(s);
2274
+ return m ? `${m[1]}T${m[2]}` : s;
2275
+ }
2056
2276
  function parseDublinCoreMetadata(xml, metadata) {
2057
2277
  const parser = createXmlParser();
2058
2278
  const doc = parser.parseFromString(stripDtd(xml), "text/xml");
@@ -14526,6 +14746,32 @@ function decodeHwp3Extra(ch) {
14526
14746
  case 12038:
14527
14747
  return 9632;
14528
14748
  // ■ hwp3-sample10 "제목차례" 좌우 장식 2건
14749
+ // SO-SUEOP ↔ 한컴 HWP3→HWPX 변환본 문맥 정렬 — 코드 개수와 변환본 글자 증가분이 전부 일치
14750
+ // (“ 13·” 14·‧ 4·《 3·》 3·— 1·∣ 6·↳ 18). 앞 셋은 항등, 0x3062~0x30BD 는 사적 문장부호
14751
+ case 8212:
14752
+ return 8212;
14753
+ // —
14754
+ case 8739:
14755
+ return 8739;
14756
+ // ∣
14757
+ case 8627:
14758
+ return 8627;
14759
+ // ↳
14760
+ case 12386:
14761
+ return 8220;
14762
+ // “
14763
+ case 12387:
14764
+ return 8221;
14765
+ // ”
14766
+ case 12390:
14767
+ return 8231;
14768
+ // ‧
14769
+ case 12475:
14770
+ return 12298;
14771
+ // 《
14772
+ case 12477:
14773
+ return 12299;
14774
+ // 》
14529
14775
  // 관인·서명란 도장 기호. rhwp 는 U+F012B 로 보존하고 렌더러가 `(인)` 으로 편다 —
14530
14776
  // kordoc 도 PUA 를 그대로 내보내면 shared/pua.ts 의 검증표가 같은 문자열로 편다.
14531
14777
  case 11214:
@@ -14677,16 +14923,78 @@ function decryptHwp3Document(input, password) {
14677
14923
  return out;
14678
14924
  }
14679
14925
 
14926
+ // src/hwp3/table.ts
14927
+ var NEIGHBOR_SLACK = 10;
14928
+ var MAX_NEIGHBOR_SCAN = 2048;
14929
+ var uniqSorted = (values) => [...new Set(values)].sort((a, b) => a - b);
14930
+ function edgeAt(edges, v) {
14931
+ let lo = 0, hi = edges.length - 1;
14932
+ while (lo < hi) {
14933
+ const mid = lo + hi >> 1;
14934
+ if (edges[mid] < v) lo = mid + 1;
14935
+ else hi = mid;
14936
+ }
14937
+ return lo;
14938
+ }
14939
+ function nextStarts(geo, axis) {
14940
+ const start = axis === "x" ? (g) => g.x : (g) => g.y;
14941
+ const end = axis === "x" ? (g) => g.r : (g) => g.b;
14942
+ const overlap = axis === "x" ? (a, b) => b.y < a.b && b.b > a.y : (a, b) => b.x < a.r && b.r > a.x;
14943
+ const order = geo.map((_, i) => i).sort((i, j) => start(geo[i]) - start(geo[j]));
14944
+ const starts = order.map((i) => start(geo[i]));
14945
+ return geo.map((a) => {
14946
+ let k = edgeAt(starts, Math.max(end(a) - NEIGHBOR_SLACK, start(a) + 1));
14947
+ for (let steps = 0; k < order.length && steps < MAX_NEIGHBOR_SCAN; k++, steps++) {
14948
+ const b = geo[order[k]];
14949
+ if (start(b) <= start(a)) continue;
14950
+ if (overlap(a, b)) return start(b);
14951
+ }
14952
+ return void 0;
14953
+ });
14954
+ }
14955
+ function hwp3CellGrid(info, count) {
14956
+ const geo = [];
14957
+ let right = 0, bottom = 0;
14958
+ for (let k = 0; k < count; k++) {
14959
+ const o = k * 27;
14960
+ const x = info.readUInt16LE(o + 4), y = info.readUInt16LE(o + 6);
14961
+ const g = { x, y, r: x + info.readUInt16LE(o + 8), b: y + info.readUInt16LE(o + 10) };
14962
+ geo.push(g);
14963
+ if (g.r > right) right = g.r;
14964
+ if (g.b > bottom) bottom = g.b;
14965
+ }
14966
+ const xs = uniqSorted([...geo.map((g) => g.x), right]);
14967
+ const ys = uniqSorted([...geo.map((g) => g.y), bottom]);
14968
+ const nextX = nextStarts(geo, "x");
14969
+ const nextY = nextStarts(geo, "y");
14970
+ const cells = geo.map((g, k) => {
14971
+ const col = edgeAt(xs, g.x), row = edgeAt(ys, g.y);
14972
+ const c2 = nextX[k] !== void 0 ? edgeAt(xs, nextX[k]) : xs.length - 1;
14973
+ const r2 = nextY[k] !== void 0 ? edgeAt(ys, nextY[k]) : ys.length - 1;
14974
+ return { row, col, rowSpan: Math.min(Math.max(1, r2 - row), MAX_ROWS), colSpan: Math.min(Math.max(1, c2 - col), MAX_COLS) };
14975
+ });
14976
+ return { rows: Math.min(Math.max(1, ys.length - 1), MAX_ROWS), cols: Math.min(Math.max(1, xs.length - 1), MAX_COLS), cells };
14977
+ }
14978
+
14680
14979
  // src/hwp3/parser.ts
14681
14980
  var MAX_DECOMPRESS_SIZE2 = 100 * 1024 * 1024;
14682
14981
  var PIC_TYPE_DRAWING = 3;
14982
+ var OBJ_EQUATION = 2;
14983
+ var OPT_HYPERTEXT = 16;
14984
+ var LEADER_TAB_MARK = "";
14985
+ var OUTLINE_FORMATS = [
14986
+ { fmt: "romanUpper", before: "", after: "." },
14987
+ { fmt: "digit", before: "", after: ")" },
14988
+ { fmt: "digit", before: "(", after: ")" },
14989
+ { fmt: "ganada", before: "", after: "." },
14990
+ { fmt: "ganada", before: "", after: ")" },
14991
+ { fmt: "ganada", before: "(", after: ")" },
14992
+ { fmt: "circled", before: "", after: "" }
14993
+ ];
14683
14994
  var PARA_SHAPE_SIZE = 187;
14684
14995
  var LINE_INFO_SIZE = 14;
14685
14996
  var INLINE_CHAR_SHAPE_SIZE = 31;
14686
14997
  var SIMPLE_CTRL = /* @__PURE__ */ new Map([
14687
- [9, { extraBytes: 6, extraHchar: 3, emit: " " }],
14688
- [18, { extraBytes: 6, extraHchar: 3, emit: " " }],
14689
- // AutoNumber → 공백 (HWP5 패턴)
14690
14998
  // 19/20/21 (새 번호·쪽 번호 위치·쪽 감추기) 는 비가시 마커다 — 한글도 그 자리에
14691
14999
  // 아무 글자를 내지 않는다 (rhwp #4957). 종전엔 U+FFFC 를 본문에 심어 마크다운에
14692
15000
  // 대체 문자가 그대로 남았다 (samples 33개 중 6문서 11건). 차례 표식(25) 과 같은 계약.
@@ -14699,8 +15007,7 @@ var SIMPLE_CTRL = /* @__PURE__ */ new Map([
14699
15007
  [25, { extraBytes: 4, extraHchar: 2, emit: null }],
14700
15008
  // 차례 표식 — 비가시 (rhwp #2765)
14701
15009
  [26, { extraBytes: 244, extraHchar: 122, emit: "\uFFFC" }],
14702
- [28, { extraBytes: 62, extraHchar: 31, emit: "\uFFFC" }],
14703
- [30, { extraBytes: 2, extraHchar: 1, emit: "\xA0" }],
15010
+ [30, { extraBytes: 2, extraHchar: 1, emit: " " }],
14704
15011
  [31, { extraBytes: 2, extraHchar: 1, emit: " " }]
14705
15012
  ]);
14706
15013
  function parseHwp3Document(buffer, options) {
@@ -14733,18 +15040,18 @@ function parseHwp3Document(buffer, options) {
14733
15040
  body = tail;
14734
15041
  }
14735
15042
  const bodyReader = new Reader(body);
14736
- const ctx = { paragraphs: [], warnings };
15043
+ const ctx = { warnings, headerBlocks: [], footerBlocks: [], headerTexts: /* @__PURE__ */ new Set(), pageNumbers: 0, noteSeq: [0, 0] };
15044
+ const bodyBlocks = [];
14737
15045
  try {
14738
15046
  skipFontFacesAndStyles(bodyReader);
14739
- parseParagraphList(bodyReader, ctx);
15047
+ parseParagraphList(bodyReader, ctx, bodyBlocks);
14740
15048
  } catch (err) {
14741
15049
  warnings.push({
14742
15050
  code: "PARTIAL_PARSE",
14743
15051
  message: `HWP3 paragraph stream \uB3C4\uC911 \uD30C\uC2F1 \uC911\uB2E8: ${err instanceof Error ? err.message : String(err)}`
14744
15052
  });
14745
15053
  }
14746
- const text = ctx.paragraphs.filter((p) => p.length > 0).join("\n\n");
14747
- const blocks = ctx.paragraphs.map((p) => ({ type: "paragraph", text: p }));
15054
+ const blocks = flattenLayoutTables([...ctx.headerBlocks, ...bodyBlocks, ...ctx.footerBlocks]);
14748
15055
  const metadata = {
14749
15056
  title: header.title || void 0,
14750
15057
  author: header.author || void 0,
@@ -14753,7 +15060,7 @@ function parseHwp3Document(buffer, options) {
14753
15060
  version: "3.0"
14754
15061
  };
14755
15062
  return {
14756
- markdown: text,
15063
+ markdown: blocksToMarkdown(blocks),
14757
15064
  blocks,
14758
15065
  metadata,
14759
15066
  warnings: warnings.length ? warnings : void 0
@@ -14768,7 +15075,7 @@ function skipFontFacesAndStyles(reader) {
14768
15075
  const nStyles = reader.readU16();
14769
15076
  reader.skip(nStyles * STYLE_RECORD_SIZE);
14770
15077
  }
14771
- function parseParagraphList(reader, ctx) {
15078
+ function parseParagraphList(reader, ctx, sink) {
14772
15079
  for (; ; ) {
14773
15080
  if (reader.eof()) return;
14774
15081
  const followPrev = reader.readU8();
@@ -14798,40 +15105,77 @@ function parseParagraphList(reader, ctx) {
14798
15105
  if (flag !== 1) reader.skip(INLINE_CHAR_SHAPE_SIZE);
14799
15106
  }
14800
15107
  }
15108
+ const para2 = { text: "", objects: [], footnotes: [] };
14801
15109
  try {
14802
- const text = parseCharStream(reader, charCount, ctx);
14803
- ctx.paragraphs.push(text);
15110
+ parseCharStream(reader, charCount, ctx, para2);
14804
15111
  } catch (err) {
15112
+ sink.push(...emitHwp3Paragraph(para2));
14805
15113
  ctx.warnings.push({
14806
15114
  code: "PARTIAL_PARSE",
14807
- message: `HWP3 paragraph #${ctx.paragraphs.length} char stream \uD30C\uC2F1 \uC2E4\uD328: ${err instanceof Error ? err.message : String(err)}`
15115
+ message: `HWP3 paragraph char stream \uD30C\uC2F1 \uC2E4\uD328: ${err instanceof Error ? err.message : String(err)}`
14808
15116
  });
14809
15117
  return;
14810
15118
  }
15119
+ sink.push(...emitHwp3Paragraph(para2));
14811
15120
  }
14812
15121
  }
14813
- function parseCharStream(reader, charCount, ctx) {
14814
- let out = "";
15122
+ function emitHwp3Paragraph(para2) {
15123
+ let text = para2.text;
15124
+ const leaderAt = text.indexOf(LEADER_TAB_MARK);
15125
+ if (leaderAt >= 0) text = text.slice(0, leaderAt);
15126
+ let headMarker = null;
15127
+ if (para2.outline) {
15128
+ const { level, numbers } = para2.outline;
15129
+ const f = OUTLINE_FORMATS[level];
15130
+ const n = numbers[level];
15131
+ if (f && n > 0) headMarker = f.before + formatNumber(n, f.fmt) + f.after;
15132
+ text = text.replace(/^ +/, "");
15133
+ }
15134
+ return emitParagraphBlocks({ text: text.trim(), headMarker, headingLevel: 0, footnotes: para2.footnotes, objects: para2.objects });
15135
+ }
15136
+ function parseCharStream(reader, charCount, ctx, para2) {
14815
15137
  let i = 0;
14816
15138
  while (i < charCount) {
14817
15139
  const ch = reader.readU16();
14818
15140
  i += 1;
14819
15141
  if (ch === 13) {
14820
- out += "\n";
15142
+ para2.text += "\n";
14821
15143
  continue;
14822
15144
  }
14823
15145
  if (ch === 0) {
14824
15146
  continue;
14825
15147
  }
14826
15148
  if (ch >= 32) {
14827
- out += decodeJohabText(ch) ?? "";
15149
+ para2.text += escapeLiteralDollar(decodeJohabText(ch) ?? "");
15150
+ continue;
15151
+ }
15152
+ if (ch === 9) {
15153
+ reader.skip(2);
15154
+ const leader = reader.readU16();
15155
+ reader.skip(2);
15156
+ i += 3;
15157
+ para2.text += leader ? LEADER_TAB_MARK : " ";
15158
+ continue;
15159
+ }
15160
+ if (ch === 18) {
15161
+ const info = reader.readBytes(6);
15162
+ i += 3;
15163
+ para2.text += autoNumberText(info.readUInt16LE(0), info.readUInt16LE(2), ctx);
15164
+ continue;
15165
+ }
15166
+ if (ch === 28) {
15167
+ const info = reader.readBytes(62);
15168
+ i += 31;
15169
+ if (info.readUInt16LE(0) === 1) {
15170
+ para2.outline = { level: Math.min(info[3], 6), numbers: Array.from({ length: 7 }, (_, k) => info.readUInt16LE(4 + k * 2)) };
15171
+ }
14828
15172
  continue;
14829
15173
  }
14830
15174
  const simple = SIMPLE_CTRL.get(ch);
14831
15175
  if (simple) {
14832
15176
  reader.skip(simple.extraBytes);
14833
15177
  i += simple.extraHchar;
14834
- if (simple.emit) out += simple.emit;
15178
+ if (simple.emit) para2.text += simple.emit;
14835
15179
  continue;
14836
15180
  }
14837
15181
  const headerVal1 = reader.readU32();
@@ -14839,27 +15183,31 @@ function parseCharStream(reader, charCount, ctx) {
14839
15183
  i += 3;
14840
15184
  switch (ch) {
14841
15185
  case 10:
14842
- out += parseTableLike(reader, ctx);
15186
+ parseTableLike(reader, ctx, para2);
14843
15187
  break;
14844
15188
  case 11:
14845
- parsePicture(reader, ctx);
15189
+ para2.objects.push({ blocks: parsePicture(reader, ctx), table: false, inline: false });
14846
15190
  break;
14847
15191
  case 14:
14848
15192
  reader.skip(84);
14849
15193
  break;
14850
15194
  case 15: {
14851
15195
  reader.skip(8);
14852
- parseParagraphList(reader, ctx);
15196
+ const hidden = [];
15197
+ parseParagraphList(reader, ctx, hidden);
15198
+ if (hidden.length > 0 && !ctx.warnings.some((w) => w.code === "HIDDEN_TEXT_FILTERED")) {
15199
+ ctx.warnings.push({ code: "HIDDEN_TEXT_FILTERED", message: "HWP3 \uC228\uC740 \uC124\uBA85 \uD14D\uC2A4\uD2B8 \uBCF8\uBB38 \uC81C\uC678" });
15200
+ }
14853
15201
  break;
14854
15202
  }
14855
15203
  case 16: {
14856
- reader.skip(10);
14857
- parseParagraphList(reader, ctx);
15204
+ const info = reader.readBytes(10);
15205
+ parseHeaderFooter(reader, ctx, info[8] === 1);
14858
15206
  break;
14859
15207
  }
14860
15208
  case 17: {
14861
- reader.skip(14);
14862
- parseParagraphList(reader, ctx);
15209
+ const info = reader.readBytes(14);
15210
+ parseNote(reader, ctx, para2, info.readUInt16LE(8), info.readUInt16LE(10) === 1 ? 1 : 0);
14863
15211
  break;
14864
15212
  }
14865
15213
  case 5:
@@ -14887,9 +15235,38 @@ function parseCharStream(reader, charCount, ctx) {
14887
15235
  break;
14888
15236
  }
14889
15237
  }
14890
- return out.trim();
15238
+ para2.text = para2.text.trim();
15239
+ }
15240
+ function autoNumberText(type, num2, ctx) {
15241
+ if (type === 0) {
15242
+ ctx.pageNumbers++;
15243
+ return "";
15244
+ }
15245
+ if (num2 <= 0) return " ";
15246
+ return type === 1 || type === 2 ? `${num2})` : String(num2);
15247
+ }
15248
+ function parseNote(reader, ctx, para2, number, kind) {
15249
+ const blocks = [];
15250
+ parseParagraphList(reader, ctx, blocks);
15251
+ const n = number > 0 ? number : ++ctx.noteSeq[kind];
15252
+ const marker = `${n})`;
15253
+ para2.text += marker;
15254
+ const content = blocksPlainText(blocks, " ");
15255
+ if (content) para2.footnotes.push(content.startsWith(marker) ? content : `${marker} ${content}`);
15256
+ }
15257
+ function parseHeaderFooter(reader, ctx, isFooter) {
15258
+ const pagesBefore = ctx.pageNumbers;
15259
+ const blocks = [];
15260
+ parseParagraphList(reader, ctx, blocks);
15261
+ const text = blocksPlainText(blocks, "\n");
15262
+ if (!text) return;
15263
+ if (ctx.pageNumbers > pagesBefore && !/\p{L}/u.test(text)) return;
15264
+ const key = (isFooter ? "f:" : "h:") + text;
15265
+ if (ctx.headerTexts.has(key)) return;
15266
+ ctx.headerTexts.add(key);
15267
+ (isFooter ? ctx.footerBlocks : ctx.headerBlocks).push({ type: "paragraph", text });
14891
15268
  }
14892
- function parseTableLike(reader, ctx) {
15269
+ function parseTableLike(reader, ctx, para2) {
14893
15270
  const info = reader.readBytes(84);
14894
15271
  const cellCount = info.readUInt16LE(80) || 1;
14895
15272
  if (27 * cellCount > reader.remaining()) {
@@ -14899,22 +15276,63 @@ function parseTableLike(reader, ctx) {
14899
15276
  });
14900
15277
  throw new Error(`HWP3 \uBE44\uC815\uC0C1 cell_count=${cellCount}`);
14901
15278
  }
14902
- reader.skip(27 * cellCount);
14903
- for (let i = 0; i < cellCount; i++) {
14904
- parseParagraphList(reader, ctx);
15279
+ const cellInfo = reader.readBytes(27 * cellCount);
15280
+ const cellBlocks = [];
15281
+ try {
15282
+ for (let i = 0; i < cellCount; i++) {
15283
+ const blocks = [];
15284
+ cellBlocks.push(blocks);
15285
+ parseParagraphList(reader, ctx, blocks);
15286
+ }
15287
+ } catch (err) {
15288
+ para2.objects.push({ blocks: cellBlocks.flat(), table: false, inline: false });
15289
+ throw err;
15290
+ }
15291
+ const captionBlocks = [];
15292
+ parseParagraphList(reader, ctx, captionBlocks);
15293
+ const caption = blocksPlainText(captionBlocks, " ");
15294
+ if (info.readUInt16LE(78) === OBJ_EQUATION) {
15295
+ const script = (cellBlocks[0] ?? []).map((b) => b.text ?? "").join("\n").trim();
15296
+ const latex = script ? hwpEquationToLatex(script) : "";
15297
+ if (latex) para2.text += `$${latex.replace(/\$/g, "\\$")}$`;
15298
+ if (caption) para2.objects.push({ blocks: [{ type: "paragraph", text: caption }], table: false, inline: false });
15299
+ return;
14905
15300
  }
14906
- parseParagraphList(reader, ctx);
14907
- return "";
15301
+ if (info.readUInt16LE(14) & OPT_HYPERTEXT) {
15302
+ para2.text += blocksPlainText(cellBlocks.flat(), " ");
15303
+ if (caption) para2.objects.push({ blocks: [{ type: "paragraph", text: caption }], table: false, inline: false });
15304
+ return;
15305
+ }
15306
+ const grid = hwp3CellGrid(cellInfo, cellCount);
15307
+ const cells = cellBlocks.map((blocks, k) => {
15308
+ const pos = grid.cells[k];
15309
+ const { text, hasStructure } = cellTextFromBlocks(blocks);
15310
+ const cell2 = { text, colSpan: pos.colSpan, rowSpan: pos.rowSpan, colAddr: pos.col, rowAddr: pos.row };
15311
+ if (hasStructure && blocks.length > 0) cell2.blocks = blocks;
15312
+ return cell2;
15313
+ });
15314
+ cells.sort((a, b) => a.rowAddr - b.rowAddr || a.colAddr - b.colAddr);
15315
+ const table2 = buildAddressedTable(cells, grid.rows, grid.cols);
15316
+ if (!table2) {
15317
+ para2.objects.push({ blocks: [...cellBlocks.flat(), ...captionBlocks], table: false, inline: false });
15318
+ return;
15319
+ }
15320
+ if (caption) table2.caption = caption;
15321
+ const inline = info[8] === 0;
15322
+ if (inline) para2.text += INLINE_TABLE_MARK;
15323
+ para2.objects.push({ blocks: [{ type: "table", table: table2 }], table: true, inline });
14908
15324
  }
14909
15325
  function parsePicture(reader, ctx) {
14910
15326
  const info = reader.readBytes(348);
14911
15327
  const nExt = info.readUInt32LE(0);
14912
15328
  let ext = null;
14913
15329
  if (nExt > 0 && nExt < 100 * 1024 * 1024) ext = reader.readBytes(nExt);
14914
- if (ext && info[74] === PIC_TYPE_DRAWING) parseDrawingObject(ext, ctx);
14915
- parseParagraphList(reader, ctx);
15330
+ const blocks = [];
15331
+ if (ext && info[74] === PIC_TYPE_DRAWING) parseDrawingObject(ext, ctx, blocks);
15332
+ parseParagraphList(reader, ctx, blocks);
15333
+ return blocks;
14916
15334
  }
14917
- function parseDrawingObject(ext, ctx) {
15335
+ function parseDrawingObject(ext, ctx, sink) {
14918
15336
  let lists;
14919
15337
  try {
14920
15338
  lists = collectDrawingTextBoxLists(ext);
@@ -14924,7 +15342,7 @@ function parseDrawingObject(ext, ctx) {
14924
15342
  }
14925
15343
  for (const list of lists) {
14926
15344
  try {
14927
- parseParagraphList(new Reader(list), ctx);
15345
+ parseParagraphList(new Reader(list), ctx, sink);
14928
15346
  } catch (err) {
14929
15347
  pushDrawingWarning(ctx, `HWP3 \uAE00\uC0C1\uC790 \uBB38\uB2E8 \uB9AC\uC2A4\uD2B8 \uD30C\uC2F1 \uC2E4\uD328: ${errText(err)}`);
14930
15348
  }
@@ -18192,7 +18610,9 @@ function scanClickHereRegions(xml) {
18192
18610
  placeholder: extractPlaceholder(fieldBeginXml),
18193
18611
  start,
18194
18612
  end,
18195
- prefix
18613
+ prefix,
18614
+ beginTagStart: m.index,
18615
+ beginTagEnd: m.index + m[0].length
18196
18616
  });
18197
18617
  }
18198
18618
  return regions;
@@ -18216,6 +18636,13 @@ function fillClickHereInXml(xml, cursor, blockedLabels) {
18216
18636
  const current = xml.slice(region.start, region.end);
18217
18637
  matchedKeys.add(key);
18218
18638
  filled.push({ label: region.name, value, row: -1, col: -1, key, source: "clickhere" });
18639
+ if (value !== "") {
18640
+ const tag = xml.slice(region.beginTagStart, region.beginTagEnd);
18641
+ if (attrOf2(tag, "dirty") !== "1") {
18642
+ const dirtyTag = /\sdirty="[^"]*"/.test(tag) ? tag.replace(/(\sdirty=")[^"]*"/, (_, head) => head + '1"') : tag.replace(/(\/?>)$/, ' dirty="1"$1');
18643
+ splices.push({ start: region.beginTagStart, end: region.beginTagEnd, replacement: dirtyTag });
18644
+ }
18645
+ }
18219
18646
  if (current === replacement) continue;
18220
18647
  splices.push({ start: region.start, end: region.end, replacement });
18221
18648
  }
@@ -19562,7 +19989,7 @@ function parseMarkdownToBlocks(md) {
19562
19989
  }
19563
19990
  function parseInlineMarkdown(text) {
19564
19991
  const literals = [];
19565
- text = text.replace(/\x00/g, "").replace(/\\([\\`*_{}[\]()#+\-.!|>~])/g, (_, c) => {
19992
+ text = text.replace(/\x00/g, "").replace(/\\([\\`*_{}[\]()#+\-.!|<>~$])/g, (_, c) => {
19566
19993
  literals.push(c);
19567
19994
  return `\0${literals.length - 1}\0`;
19568
19995
  });
@@ -20773,16 +21200,6 @@ function bestSimInRange(arr, from, to, target) {
20773
21200
  }
20774
21201
  return best;
20775
21202
  }
20776
- function escapeGfm(text) {
20777
- const NUL = String.fromCharCode(0);
20778
- const spans = [];
20779
- const masked = text.replace(/!\[[^\]]*\]\([^)\n]*\)|\]\((?:https?:|mailto:|tel:|#)[^)\n]*\)/gi, (m) => {
20780
- spans.push(m);
20781
- return NUL + (spans.length - 1) + NUL;
20782
- });
20783
- const escaped = masked.replace(/([~*_`])/g, "\\$1");
20784
- return escaped.replace(new RegExp(NUL + "(\\d+)" + NUL, "g"), (_, n) => spans[Number(n)]);
20785
- }
20786
21203
  var HWP_SHAPE_ALT_TEXT_RE = /^(?:모서리가 둥근 |둥근 )?(?:사각형|직사각형|정사각형|원|타원|삼각형|이등변 삼각형|직각 삼각형|선|직선|곡선|화살표|굵은 화살표|이중 화살표|오각형|육각형|팔각형|별|[4-8]점별|십자|십자형|구름|구름형|마름모|도넛|평행사변형|사다리꼴|부채꼴|호|반원|물결|번개|하트|빗금|블록 화살표|수식|표|그림|개체|그리기\s?개체|묶음\s?개체|글상자|수식\s?개체|OLE\s?개체)\s?입니다\.?$/gm;
20787
21204
  function sanitizeText(text) {
20788
21205
  let result = mapPuaText(text).replace(/[\u{F0000}-\u{FFFFD}]/gu, "").replace(HWP_SHAPE_ALT_TEXT_RE, "").replace(/ +/g, " ").trim();
@@ -20796,10 +21213,10 @@ function sanitizeText(text) {
20796
21213
  return result;
20797
21214
  }
20798
21215
  function normForMatch(text) {
20799
- return sanitizeText(text).replace(/\s+/g, " ").trim();
21216
+ return sanitizeText(text.replace(/\\\$/g, "$")).replace(/\s+/g, " ").trim();
20800
21217
  }
20801
21218
  function unescapeGfm(text) {
20802
- return text.replace(/\\([~*_`])/g, "$1");
21219
+ return text.replace(/\\([~*_`|#<$\\])/g, "$1");
20803
21220
  }
20804
21221
  function summarize(text) {
20805
21222
  const t = text.replace(/\s+/g, " ").trim();
@@ -20818,7 +21235,7 @@ function replicateGfmTable(table2) {
20818
21235
  const cell2 = cells[r]?.[c];
20819
21236
  if (!cell2) continue;
20820
21237
  display[r][c] = {
20821
- text: escapeGfm(sanitizeText(cell2.text)).replace(/\|/g, "\\|").replace(/\n/g, "<br>"),
21238
+ text: escapeGfm(sanitizeText(cell2.text)).replace(/(?<!\\)\|/g, "\\|").replace(/\n/g, "<br>"),
20822
21239
  gridR: r,
20823
21240
  gridC: c
20824
21241
  };
@@ -20866,7 +21283,7 @@ function parseGfmTable(lines) {
20866
21283
  return rows;
20867
21284
  }
20868
21285
  function unescapeGfmCell(text) {
20869
- return text.replace(/<br\s*\/?>/gi, "\n").replace(/\\\|/g, "|").replace(/\\([~*_`])/g, "$1");
21286
+ return text.replace(/(?<!\\)<br\s*\/?>/gi, "\n").replace(/\\\|/g, "|").replace(/\\([~*_`#<$\\])/g, "$1");
20870
21287
  }
20871
21288
  function replicateCellInnerHtml(cell2) {
20872
21289
  if (cell2.blocks?.length) {
@@ -20877,7 +21294,7 @@ function replicateCellInnerHtml(cell2) {
20877
21294
  }
20878
21295
  if (b.type === "image" && b.text) return `<img src="${b.text}" alt="image">`;
20879
21296
  const t = sanitizeText(b.text ?? "");
20880
- return t ? t.replace(/\n/g, "<br>") : "";
21297
+ return t ? (t + noteSuffix(b)).replace(/\n/g, "<br>") : "";
20881
21298
  }).filter(Boolean).join("<br>");
20882
21299
  }
20883
21300
  return sanitizeText(cell2.text).replace(/\n/g, "<br>");
@@ -20894,7 +21311,7 @@ function replicateTableToHtml(table2) {
20894
21311
  const attrStr = attrs.length ? " " + attrs.join(" ") : "";
20895
21312
  return `<${tag}${attrStr}>${cell2.inner}</${tag}>`;
20896
21313
  });
20897
- if (rowHtml.length) lines.push(`<tr>${rowHtml.join("")}</tr>`);
21314
+ lines.push(`<tr>${rowHtml.join("")}</tr>`);
20898
21315
  }
20899
21316
  lines.push("</table>");
20900
21317
  return lines.join("\n");
@@ -20924,7 +21341,7 @@ function replicateHtmlTable(table2) {
20924
21341
  gridC: c
20925
21342
  });
20926
21343
  }
20927
- if (rowCells.length) result.push({ tag, cells: rowCells });
21344
+ result.push({ tag, cells: rowCells });
20928
21345
  }
20929
21346
  return result;
20930
21347
  }
@@ -26398,6 +26815,11 @@ function resolveParagraphMappings(blocks, scans) {
26398
26815
  if (b.type !== "paragraph" && b.type !== "heading" || !b.text || !footerNorms.has(normForMatch(b.text))) break;
26399
26816
  pageText.add(i);
26400
26817
  }
26818
+ const noted = scans.flatMap((scan) => {
26819
+ if (!/<(?:\w+:)?(?:footNote|endNote)\b/.test(scan.xml)) return [];
26820
+ const fmts = sectionNoteFormats(scan.xml);
26821
+ return scan.bodyParagraphs.map((p) => ({ key: normForMatch(p.text), marks: paraNoteMarks(scan.xml, p, fmts) })).filter((x) => x.key && x.marks.length > 0);
26822
+ });
26401
26823
  const counters = /* @__PURE__ */ new Map();
26402
26824
  const result = /* @__PURE__ */ new Map();
26403
26825
  for (let i = 0; i < blocks.length; i++) {
@@ -26409,13 +26831,26 @@ function resolveParagraphMappings(blocks, scans) {
26409
26831
  }
26410
26832
  let key = normForMatch(b.text);
26411
26833
  let prefixStripped = false;
26834
+ let noteMarks;
26412
26835
  if (!buckets.has(key)) {
26413
26836
  const sp = b.text.indexOf(" ");
26414
- if (sp > 0) {
26415
- const alt = normForMatch(b.text.slice(sp + 1));
26416
- if (alt && buckets.has(alt)) {
26417
- key = alt;
26418
- prefixStripped = true;
26837
+ const alt = sp > 0 ? normForMatch(b.text.slice(sp + 1)) : "";
26838
+ if (alt && buckets.has(alt)) {
26839
+ key = alt;
26840
+ prefixStripped = true;
26841
+ } else {
26842
+ for (const n of noted) {
26843
+ if (textWithoutMarks(key, n.key, n.marks)) {
26844
+ key = n.key;
26845
+ noteMarks = n.marks;
26846
+ break;
26847
+ }
26848
+ if (alt && textWithoutMarks(alt, n.key, n.marks)) {
26849
+ key = n.key;
26850
+ noteMarks = n.marks;
26851
+ prefixStripped = true;
26852
+ break;
26853
+ }
26419
26854
  }
26420
26855
  }
26421
26856
  }
@@ -26426,10 +26861,90 @@ function resolveParagraphMappings(blocks, scans) {
26426
26861
  }
26427
26862
  const occ = counters.get(key) ?? 0;
26428
26863
  counters.set(key, occ + 1);
26429
- result.set(i, occ < list.length ? { para: list[occ], prefixStripped } : {});
26864
+ result.set(i, occ < list.length ? { para: list[occ], prefixStripped, noteMarks } : {});
26430
26865
  }
26431
26866
  return result;
26432
26867
  }
26868
+ function sectionNoteFormats(xml) {
26869
+ const pick2 = (tag) => {
26870
+ const m = new RegExp(`<(?:\\w+:)?${tag}\\b[^>]*>\\s*<(?:\\w+:)?autoNumFormat\\b([^>]*?)/?>`).exec(xml);
26871
+ return m ? noteFormatFrom((n) => xmlAttr(m[1], n)) : void 0;
26872
+ };
26873
+ return { footnote: pick2("footNotePr"), endnote: pick2("endNotePr") };
26874
+ }
26875
+ function xmlAttr(attrs, name) {
26876
+ const m = new RegExp(`(?:^|\\s)${name}\\s*=\\s*"([^"]*)"`).exec(attrs);
26877
+ return m ? decodeXmlEntities(m[1]) : void 0;
26878
+ }
26879
+ function paraNoteMarks(xml, para2, fmts) {
26880
+ const end = findElementEnd(xml, para2.start);
26881
+ if (end < 0) return [];
26882
+ const seg = xml.slice(para2.start, end);
26883
+ if (!/<(?:\w+:)?(?:footNote|endNote)\b/.test(seg)) return [];
26884
+ const marks = [];
26885
+ let depth = 0;
26886
+ for (const m of seg.matchAll(/<(\/?)(?:\w+:)?(tbl|drawText|caption|footNote|endNote|header|footer|memo|hiddenComment)\b([^>]*?)(\/?)>/g)) {
26887
+ const [, close, tag, attrs, selfClose] = m;
26888
+ if (selfClose) continue;
26889
+ if (close) {
26890
+ depth = Math.max(0, depth - 1);
26891
+ continue;
26892
+ }
26893
+ if (depth === 0 && (tag === "footNote" || tag === "endNote")) {
26894
+ const get = (n) => xmlAttr(attrs, n);
26895
+ marks.push(noteRefMark(
26896
+ { number: get("number"), prefixChar: get("prefixChar"), suffixChar: get("suffixChar"), userChar: get("userChar") },
26897
+ tag === "endNote" ? fmts.endnote : fmts.footnote
26898
+ ));
26899
+ }
26900
+ depth++;
26901
+ }
26902
+ return marks;
26903
+ }
26904
+ function textWithoutMarks(irKey, scanKey, marks) {
26905
+ let i = 0, j = 0, k = 0;
26906
+ while (i < irKey.length) {
26907
+ if (j < scanKey.length && irKey[i] === scanKey[j]) {
26908
+ i++;
26909
+ j++;
26910
+ continue;
26911
+ }
26912
+ if (k < marks.length && marks[k] && irKey.startsWith(marks[k], i)) {
26913
+ i += marks[k].length;
26914
+ k++;
26915
+ continue;
26916
+ }
26917
+ if (irKey[i] === " " && (j === 0 || scanKey[j - 1] === " ")) {
26918
+ i++;
26919
+ continue;
26920
+ }
26921
+ return false;
26922
+ }
26923
+ return j === scanKey.length && k === marks.length;
26924
+ }
26925
+ function removeNoteMarks(text, marks) {
26926
+ let out = text;
26927
+ let cursor = 0;
26928
+ const missing = [];
26929
+ for (const mark of marks) {
26930
+ if (!mark) continue;
26931
+ let at = -1;
26932
+ for (let p = out.indexOf(mark, cursor); p !== -1; p = out.indexOf(mark, p + 1)) {
26933
+ if (p === 0 || !/\s/.test(out[p - 1])) {
26934
+ at = p;
26935
+ break;
26936
+ }
26937
+ if (at === -1) at = p;
26938
+ }
26939
+ if (at === -1) {
26940
+ missing.push(mark);
26941
+ continue;
26942
+ }
26943
+ out = out.slice(0, at) + out.slice(at + mark.length);
26944
+ cursor = at;
26945
+ }
26946
+ return { text: out, missing };
26947
+ }
26433
26948
  function handleModifiedUnit(orig, edited, ctx) {
26434
26949
  const block = ctx.origBlocks[orig.blockIdx];
26435
26950
  const skip = (reason) => {
@@ -26512,6 +27027,11 @@ function patchParagraphUnit(block, orig, edited, ctx, skip) {
26512
27027
  ctx.skipped.push({ reason: "\uAC01\uC8FC \uD45C\uAE30 \uC0AD\uC81C\uB294 \uBBF8\uC9C0\uC6D0 \u2014 \uAC01\uC8FC \uC720\uC9C0, \uBCF8\uBB38\uB9CC \uC801\uC6A9", before: `(\uC8FC: ${block.footnoteText})` });
26513
27028
  }
26514
27029
  }
27030
+ if (mapping.noteMarks?.length) {
27031
+ const r = removeNoteMarks(newPlain, mapping.noteMarks);
27032
+ newPlain = r.text;
27033
+ if (r.missing.length) ctx.skipped.push({ reason: "\uAC01\uC8FC \uCC38\uC870 \uBD80\uD638 \uC0AD\uC81C\uB294 \uBBF8\uC9C0\uC6D0 \u2014 \uAC01\uC8FC \uC720\uC9C0, \uBCF8\uBB38\uB9CC \uC801\uC6A9", before: r.missing.join(" ") });
27034
+ }
26515
27035
  if (mapping.prefixStripped) {
26516
27036
  const origPrefix = block.text.split(" ", 1)[0];
26517
27037
  const sp = newPlain.indexOf(" ");
@@ -26611,6 +27131,11 @@ function replaceOleStream(file, path, newData) {
26611
27131
  surgeon.replace(path, newData);
26612
27132
  return surgeon.finish();
26613
27133
  }
27134
+ function wipeFreeSectors(file) {
27135
+ const surgeon = new Surgeon(file);
27136
+ surgeon.wipeUnallocated();
27137
+ return surgeon.finish();
27138
+ }
26614
27139
  var Surgeon = class {
26615
27140
  buf;
26616
27141
  /** 논리 파일 길이 — buf는 성장 시 기하급수 capacity로 재할당되므로 buf.length와 다를 수 있음 */
@@ -26900,6 +27425,47 @@ var Surgeon = class {
26900
27425
  entry.size = newData.length;
26901
27426
  this.writeDirEntry(entry);
26902
27427
  }
27428
+ /**
27429
+ * wipeFreeSectors 본체 — 어느 체인에도 닿지 않는 섹터·미니섹터와 스트림 꼬리 슬랙을 0으로.
27430
+ * FREESECT 표시를 믿지 않고 도달성으로 판정한다(쓰는 쪽에 따라 빈 FAT 칸을 ENDOFCHAIN 으로 채우기도 한다).
27431
+ */
27432
+ wipeUnallocated() {
27433
+ const used = /* @__PURE__ */ new Set([...this.fatSectors, ...this.miniFatSectors, ...this.dirSectors]);
27434
+ let d = this.buf.readUInt32LE(68);
27435
+ for (let guard = 0; d !== ENDOFCHAIN && d !== FREESECT && guard < 1e6; guard++) {
27436
+ used.add(d);
27437
+ d = this.buf.readUInt32LE(this.sectorOffset(d) + 127 * 4);
27438
+ }
27439
+ const root = this.rootEntry();
27440
+ const rootChain = root.start === ENDOFCHAIN || root.size === 0 ? [] : this.chain(root.start);
27441
+ for (const s of rootChain) used.add(s);
27442
+ const usedMini = /* @__PURE__ */ new Set();
27443
+ for (const e of this.entries) {
27444
+ if (e.type !== 2 || e.size === 0 || e.start === ENDOFCHAIN) continue;
27445
+ if (e.size < MINI_CUTOFF) {
27446
+ const chain = this.miniChain(e.start);
27447
+ for (const s of chain) usedMini.add(s);
27448
+ const tail = e.size - (chain.length - 1) * MINI_SECTOR;
27449
+ const off = this.miniOffset(chain[chain.length - 1], rootChain);
27450
+ if (tail > 0 && tail < MINI_SECTOR) this.buf.fill(0, off + tail, off + MINI_SECTOR);
27451
+ } else {
27452
+ const chain = this.chain(e.start);
27453
+ for (const s of chain) used.add(s);
27454
+ const tail = e.size - (chain.length - 1) * SECTOR;
27455
+ const off = this.sectorOffset(chain[chain.length - 1]);
27456
+ if (tail > 0 && tail < SECTOR) this.buf.fill(0, off + tail, off + SECTOR);
27457
+ }
27458
+ }
27459
+ for (let i = 0; SECTOR + (i + 1) * SECTOR <= this.len; i++) {
27460
+ if (!used.has(i)) this.buf.fill(0, SECTOR + i * SECTOR, SECTOR + (i + 1) * SECTOR);
27461
+ }
27462
+ const capacity = rootChain.length * (SECTOR / MINI_SECTOR);
27463
+ for (let i = 0; i < capacity; i++) {
27464
+ if (usedMini.has(i)) continue;
27465
+ const off = this.miniOffset(i, rootChain);
27466
+ this.buf.fill(0, off, off + MINI_SECTOR);
27467
+ }
27468
+ }
26903
27469
  finish() {
26904
27470
  this.wipeFreedSectors();
26905
27471
  this.flushFat();
@@ -27232,6 +27798,11 @@ function resolveTableMappings(blocks, scanTables) {
27232
27798
  for (let k = si; k < scanTables.length; k++) {
27233
27799
  if (scanTables[k].rows === table2.rows && scanTables[k].cols === table2.cols) cands.push(k);
27234
27800
  }
27801
+ if (cands.length === 0) {
27802
+ for (let k = si; k < scanTables.length; k++) {
27803
+ if (scanTables[k].rows === table2.rows && scanTables[k].cols > table2.cols) cands.push(k);
27804
+ }
27805
+ }
27235
27806
  if (cands.length === 0) continue;
27236
27807
  let pick2 = cands[0];
27237
27808
  if (cands.length > 1) {
@@ -28122,6 +28693,11 @@ var HwpxSession = class _HwpxSession {
28122
28693
  return skip("\uBB38\uB2E8 \uC18C\uC2A4\uB9F5 \uB9E4\uD551 \uC2E4\uD328 (\uBA38\uB9AC\uB9D0/\uAE00\uC0C1\uC790/\uCEA1\uC158 \uC601\uC5ED\uC774\uAC70\uB098 \uD14D\uC2A4\uD2B8 \uBD88\uC77C\uCE58)");
28123
28694
  }
28124
28695
  let newPlain = newTextRaw.split("\n").map((l) => l.trim()).filter(Boolean).join(" ");
28696
+ if (mapping.noteMarks?.length) {
28697
+ const r = removeNoteMarks(newPlain, mapping.noteMarks);
28698
+ newPlain = r.text;
28699
+ if (r.missing.length) skipped.push({ reason: "\uAC01\uC8FC \uCC38\uC870 \uBD80\uD638 \uC0AD\uC81C\uB294 \uBBF8\uC9C0\uC6D0 \u2014 \uAC01\uC8FC \uC720\uC9C0, \uBCF8\uBB38\uB9CC \uC801\uC6A9", before: r.missing.join(" ") });
28700
+ }
28125
28701
  if (mapping.prefixStripped) {
28126
28702
  const origPrefix = block.text.split(" ", 1)[0];
28127
28703
  const sp = newPlain.indexOf(" ");
@@ -28202,6 +28778,10 @@ async function parse(input, options) {
28202
28778
  }
28203
28779
  const format = detectFormat(buffer);
28204
28780
  const result = await dispatch(format, buffer, opts);
28781
+ if (result.success && opts?.images === false) {
28782
+ delete result.images;
28783
+ dropImageBytes(result.blocks);
28784
+ }
28205
28785
  if (result.success && opts?.classifyTables) classifyTableTree(result.blocks);
28206
28786
  if (result.success && !result.pages) {
28207
28787
  const pages = blocksToPages(result.blocks);
@@ -28209,6 +28789,17 @@ async function parse(input, options) {
28209
28789
  }
28210
28790
  return result;
28211
28791
  }
28792
+ function dropImageBytes(blocks, depth = 0) {
28793
+ if (!blocks || depth > 64) return;
28794
+ for (const b of blocks) {
28795
+ if (b.imageData) delete b.imageData;
28796
+ dropImageBytes(b.children, depth + 1);
28797
+ if (b.table) {
28798
+ for (const row of b.table.cells) for (const cell2 of row) dropImageBytes(cell2.blocks, depth + 1);
28799
+ dropImageBytes(b.table.captionBlocks, depth + 1);
28800
+ }
28801
+ }
28802
+ }
28212
28803
  async function dispatch(format, buffer, opts) {
28213
28804
  switch (format) {
28214
28805
  case "hwpx": {
@@ -28239,7 +28830,7 @@ async function dispatch(format, buffer, opts) {
28239
28830
  }
28240
28831
  async function parseImage(buffer, options) {
28241
28832
  try {
28242
- const { parseImageDocument } = await import("./image-ocr-WCAYXLOA.js");
28833
+ const { parseImageDocument } = await import("./image-ocr-72RAR4WR.js");
28243
28834
  const { blocks, warnings } = await parseImageDocument(buffer, options);
28244
28835
  return {
28245
28836
  success: true,
@@ -28298,7 +28889,7 @@ async function parseHwp(buffer, options) {
28298
28889
  async function parsePdf(buffer, options) {
28299
28890
  let parsePdfDocument;
28300
28891
  try {
28301
- const mod = await import("./parser-SCUTZAIJ.js");
28892
+ const mod = await import("./parser-4NKAYWAX.js");
28302
28893
  parsePdfDocument = mod.parsePdfDocument;
28303
28894
  } catch {
28304
28895
  return {
@@ -28490,7 +29081,10 @@ export {
28490
29081
  lintMuncheText,
28491
29082
  muncheLintWarnings,
28492
29083
  patchHwpx,
29084
+ replaceOleStream,
29085
+ wipeFreeSectors,
28493
29086
  patchHwp,
29087
+ compressWithTail,
28494
29088
  validateHwpx,
28495
29089
  HwpxSession,
28496
29090
  openHwpxDocument,
@@ -28508,4 +29102,4 @@ export {
28508
29102
  parseHwpml,
28509
29103
  fillForm
28510
29104
  };
28511
- //# sourceMappingURL=chunk-EISO3POT.js.map
29105
+ //# sourceMappingURL=chunk-CVEERPMP.js.map