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
package/dist/index.js CHANGED
@@ -5,11 +5,14 @@ import {
5
5
  buildTable,
6
6
  convertTableToText,
7
7
  dedupeRunningHeaders,
8
+ escapeGfm,
9
+ escapeLiteralDollar,
8
10
  flattenLayoutTables,
9
11
  hasStructuredCellContent,
10
12
  inlineImagesIntoMarkdown,
11
- mapPuaText
12
- } from "./chunk-VQ5W4MYO.js";
13
+ mapPuaText,
14
+ noteSuffix
15
+ } from "./chunk-VYOGXNQ3.js";
13
16
  import {
14
17
  parsePageRange
15
18
  } from "./chunk-GE43BE46.js";
@@ -17,7 +20,7 @@ import {
17
20
  HEADING_RATIO_H1,
18
21
  HEADING_RATIO_H2,
19
22
  HEADING_RATIO_H3
20
- } from "./chunk-FOJZNMMK.js";
23
+ } from "./chunk-2LQGSDZ6.js";
21
24
  import {
22
25
  KordocError,
23
26
  VERSION,
@@ -30,7 +33,7 @@ import {
30
33
  sanitizeHref,
31
34
  stripDtd,
32
35
  toArrayBuffer
33
- } from "./chunk-JQEFP45L.js";
36
+ } from "./chunk-L5EH2NVO.js";
34
37
 
35
38
  // src/index.ts
36
39
  import { readFile as readFile3 } from "fs/promises";
@@ -1292,7 +1295,7 @@ function replaceBracket(strList) {
1292
1295
  }
1293
1296
  function hmlToLatex(hmlEqStr) {
1294
1297
  if (!hmlEqStr) return "";
1295
- let s = hmlEqStr.replace(/`/g, " ");
1298
+ let s = hmlEqStr.replace(/[\r\n\t]+/g, " ").replace(/`/g, " ");
1296
1299
  s = s.replace(/\{/g, " { ").replace(/\}/g, " } ").replace(/&/g, " & ");
1297
1300
  let tokens = s.split(" ");
1298
1301
  for (let i = 0; i < tokens.length; i++) {
@@ -1342,9 +1345,9 @@ function hangulAmount(n) {
1342
1345
  const groups = [];
1343
1346
  for (let end = digits.length; end > 0; end -= 4) groups.unshift(digits.slice(Math.max(0, end - 4), end));
1344
1347
  let out = "";
1345
- groups.forEach((g, i) => {
1348
+ groups.forEach((g2, i) => {
1346
1349
  let part = "";
1347
- const padded = g.padStart(4, "0");
1350
+ const padded = g2.padStart(4, "0");
1348
1351
  for (let k = 0; k < 4; k++) {
1349
1352
  const d = Number(padded[k]);
1350
1353
  if (d) part += AMOUNT_DIGITS[d] + AMOUNT_SMALL[3 - k];
@@ -1477,7 +1480,7 @@ function buildTableWithCellMeta(state, keepAnchoredEmptyCols, keepEmptyParagraph
1477
1480
  for (const row of state.rows) {
1478
1481
  for (const src of row) {
1479
1482
  flatIdx++;
1480
- const needsBlocks = !!src.blocks && src.blocks.length > 0 && (src.hasStructure || src.blocks.some((b) => b.spans));
1483
+ const needsBlocks = !!src.blocks && src.blocks.length > 0 && (src.hasStructure || src.blocks.some((b) => b.spans || b.footnoteText));
1481
1484
  if (!needsBlocks && !src.isHeader) continue;
1482
1485
  let target;
1483
1486
  const trimmed = keepEmptyParagraphs ? src.text : src.text.trim();
@@ -1509,7 +1512,7 @@ function buildTableWithCellMeta(state, keepAnchoredEmptyCols, keepEmptyParagraph
1509
1512
  }
1510
1513
  function completeTable(newTable, tableStack, blocks, ctx) {
1511
1514
  const parentTable = tableStack.length > 0 ? tableStack.pop() : null;
1512
- if (newTable.rows.length === 0) {
1515
+ if (newTable.rows.every((r) => r.length === 0)) {
1513
1516
  if (newTable.caption) blocks.push({ type: "paragraph", text: newTable.caption, pageNumber: ctx.page });
1514
1517
  return parentTable;
1515
1518
  }
@@ -1659,6 +1662,162 @@ function detectHwpxSectionPages(root) {
1659
1662
  };
1660
1663
  }
1661
1664
 
1665
+ // src/hwpx/notes.ts
1666
+ function codePointAttr(v) {
1667
+ if (v == null || v === "") return void 0;
1668
+ const n = parseInt(v, 10);
1669
+ if (!Number.isFinite(n) || n < 0 || n > 1114111) return void 0;
1670
+ return n === 0 ? "" : String.fromCodePoint(n);
1671
+ }
1672
+ function noteFormatFrom(get) {
1673
+ return {
1674
+ type: get("type") || "DIGIT",
1675
+ userChar: get("userChar") ?? "",
1676
+ prefixChar: get("prefixChar") ?? "",
1677
+ suffixChar: get("suffixChar") ?? ""
1678
+ };
1679
+ }
1680
+ function noteCore(num4, fmt, userChar) {
1681
+ if (fmt.type === "USER_CHAR") return userChar ?? fmt.userChar;
1682
+ return formatHeadNumber(num4, fmt.type);
1683
+ }
1684
+ function noteRefMark(attrs, fmt) {
1685
+ const f = fmt ?? { type: "DIGIT", userChar: "", prefixChar: "", suffixChar: ")" };
1686
+ const n = parseInt(attrs.number ?? "", 10);
1687
+ const prefix = codePointAttr(attrs.prefixChar) ?? f.prefixChar;
1688
+ const suffix = codePointAttr(attrs.suffixChar) ?? f.suffixChar;
1689
+ return prefix + noteCore(Number.isFinite(n) ? n : 1, f, codePointAttr(attrs.userChar)) + suffix;
1690
+ }
1691
+ function noteAutoNumText(num4, fmt) {
1692
+ const n = parseInt(num4 ?? "", 10);
1693
+ return fmt.prefixChar + noteCore(Number.isFinite(n) ? n : 1, fmt) + fmt.suffixChar;
1694
+ }
1695
+ function isRenderedAutoNum(numType) {
1696
+ return numType === "FOOTNOTE" || numType === "ENDNOTE" || numType === "PICTURE" || numType === "TABLE" || numType === "EQUATION";
1697
+ }
1698
+ function noteAttrsOf(el) {
1699
+ return {
1700
+ number: el.getAttribute("number"),
1701
+ prefixChar: el.getAttribute("prefixChar"),
1702
+ suffixChar: el.getAttribute("suffixChar"),
1703
+ userChar: el.getAttribute("userChar")
1704
+ };
1705
+ }
1706
+ function readSectionNoteFormats(root) {
1707
+ const out = {};
1708
+ const pick2 = (names) => {
1709
+ for (const name of names) {
1710
+ const els = root.getElementsByTagName(name);
1711
+ if (els.length === 0) continue;
1712
+ const f = findChildByLocalName(els[0], "autoNumFormat");
1713
+ return f ? noteFormatFrom((n) => f.getAttribute(n)) : void 0;
1714
+ }
1715
+ return void 0;
1716
+ };
1717
+ out.footnote = pick2(["hp:footNotePr", "footNotePr"]);
1718
+ out.endnote = pick2(["hp:endNotePr", "endNotePr"]);
1719
+ return out;
1720
+ }
1721
+ function noteAutoNumOf(el) {
1722
+ if (!isRenderedAutoNum(el.getAttribute("numType"))) return "";
1723
+ const f = findChildByLocalName(el, "autoNumFormat");
1724
+ return noteAutoNumText(el.getAttribute("num"), noteFormatFrom((n) => f?.getAttribute(n)));
1725
+ }
1726
+
1727
+ // src/hwpx/run-spans.ts
1728
+ var KORDOC_CHAR_CODE = "4";
1729
+ var KORDOC_PARA_QUOTE = "6";
1730
+ function spanModeOf(layout) {
1731
+ if (layout === "default") return "kordoc";
1732
+ if (layout === "gongmun") return "gongmun";
1733
+ if (!layout) return "foreign";
1734
+ return null;
1735
+ }
1736
+ function extractRunSpans(para2, ctx, mode, requireMixed) {
1737
+ const styleMap = ctx.styleMap;
1738
+ if (!styleMap) return null;
1739
+ const spans = [];
1740
+ let styled = false;
1741
+ const kids = para2.childNodes;
1742
+ if (!kids) return null;
1743
+ for (let i = 0; i < kids.length; i++) {
1744
+ const child = kids[i];
1745
+ if (child.nodeType !== 1) continue;
1746
+ const tag = (child.tagName || child.localName || "").replace(/^[^:]+:/, "");
1747
+ if (tag === "linesegarray") continue;
1748
+ if (tag !== "run" && tag !== "r") return null;
1749
+ let text = "";
1750
+ const rkids = child.childNodes;
1751
+ for (let j = 0; j < (rkids?.length ?? 0); j++) {
1752
+ const rc = rkids[j];
1753
+ if (rc.nodeType !== 1) continue;
1754
+ const rtag = (rc.tagName || rc.localName || "").replace(/^[^:]+:/, "");
1755
+ if (rtag === "t") {
1756
+ const tkids = rc.childNodes;
1757
+ for (let k = 0; k < (tkids?.length ?? 0); k++) {
1758
+ const tk = tkids[k];
1759
+ if (tk.nodeType === 3 || tk.nodeType === 4) text += escapeLiteralDollar(tk.textContent || "");
1760
+ else if (tk.nodeType === 1) {
1761
+ const ttag = (tk.tagName || tk.localName || "").replace(/^[^:]+:/, "");
1762
+ if (ttag === "tab" || ttag === "fwSpace" || ttag === "hwSpace" || ttag === "nbSpace") text += " ";
1763
+ else return null;
1764
+ }
1765
+ }
1766
+ } else if (rtag === "secPr" || rtag === "colPr") {
1767
+ } else if (rtag === "ctrl") {
1768
+ if (extractTextFromNode(rc)) return null;
1769
+ } else {
1770
+ return null;
1771
+ }
1772
+ }
1773
+ if (!text) continue;
1774
+ const prId = child.getAttribute("charPrIDRef") ?? "";
1775
+ const cp = styleMap.charProperties.get(prId);
1776
+ const span = { text };
1777
+ if (mode !== "foreign" && prId === KORDOC_CHAR_CODE) span.code = true;
1778
+ else {
1779
+ if (cp?.bold) span.bold = true;
1780
+ if (cp?.italic) span.italic = true;
1781
+ if (cp?.strike) span.strike = true;
1782
+ if (cp?.underline) span.underline = true;
1783
+ }
1784
+ if (span.bold || span.italic || span.strike || span.underline || span.code) styled = true;
1785
+ spans.push(span);
1786
+ }
1787
+ if (!styled || spans.length === 0) return null;
1788
+ const merged = [];
1789
+ for (const s of spans) {
1790
+ const last = merged[merged.length - 1];
1791
+ if (last && !!last.bold === !!s.bold && !!last.italic === !!s.italic && !!last.strike === !!s.strike && !!last.underline === !!s.underline && !!last.code === !!s.code) {
1792
+ last.text += s.text;
1793
+ } else {
1794
+ merged.push(s);
1795
+ }
1796
+ }
1797
+ if (requireMixed && !merged.some((s) => s.strike || s.underline) && !merged.some((s) => !(s.bold || s.italic || s.code))) return null;
1798
+ return merged;
1799
+ }
1800
+ function gongmunDepthFromIndent(para2, left, ctx) {
1801
+ const styleMap = ctx.styleMap;
1802
+ if (!styleMap) return null;
1803
+ let unit = 0;
1804
+ const kids = para2.childNodes;
1805
+ for (let i = 0; i < (kids?.length ?? 0); i++) {
1806
+ const child = kids[i];
1807
+ if (child.nodeType !== 1) continue;
1808
+ const tag = (child.tagName || child.localName || "").replace(/^[^:]+:/, "");
1809
+ if (tag !== "run" && tag !== "r") continue;
1810
+ const size = styleMap.charProperties.get(child.getAttribute("charPrIDRef") ?? "")?.fontSize;
1811
+ if (size) unit = size * 100;
1812
+ break;
1813
+ }
1814
+ if (!unit) return null;
1815
+ const r = left / unit;
1816
+ const nearInt = Math.round(r);
1817
+ const depth = Math.abs(r - nearInt) < 0.2 ? nearInt : Math.round(2 * r - 1);
1818
+ return depth >= 1 && depth < 8 ? depth : null;
1819
+ }
1820
+
1662
1821
  // src/hwpx/section-walker.ts
1663
1822
  function parseSectionXml(xml, styleMap, warnings, sectionNum, shared) {
1664
1823
  const parser = createXmlParser(warnings);
@@ -1666,13 +1825,13 @@ function parseSectionXml(xml, styleMap, warnings, sectionNum, shared) {
1666
1825
  if (!doc.documentElement) return [];
1667
1826
  const ctx = { styleMap, warnings, sectionNum, shared: shared ?? createSectionShared() };
1668
1827
  ctx.shared.track.deleteDepth = 0;
1669
- const detect = detectHwpxSectionPages(doc.documentElement);
1828
+ const detect2 = detectHwpxSectionPages(doc.documentElement);
1670
1829
  const ps = ctx.shared.pageState;
1671
- if (!detect.usable) ps.allUsable = false;
1672
- ctx.paraPage = detect.paraPage;
1830
+ if (!detect2.usable) ps.allUsable = false;
1831
+ ctx.paraPage = detect2.paraPage;
1673
1832
  ctx.pageBase = ps.base;
1674
1833
  ctx.page = ps.base + 1;
1675
- ps.base += detect.pages;
1834
+ ps.base += detect2.pages;
1676
1835
  for (const tagName of ["hp:secPr", "secPr"]) {
1677
1836
  const els = doc.getElementsByTagName(tagName);
1678
1837
  if (els.length > 0) {
@@ -1681,6 +1840,7 @@ function parseSectionXml(xml, styleMap, warnings, sectionNum, shared) {
1681
1840
  break;
1682
1841
  }
1683
1842
  }
1843
+ ctx.noteFormats = readSectionNoteFormats(doc.documentElement);
1684
1844
  const blocks = [];
1685
1845
  walkSection(doc.documentElement, blocks, null, [], ctx);
1686
1846
  return blocks;
@@ -1747,7 +1907,7 @@ function walkSection(node, blocks, tableCtx, tableStack, ctx, depth = 0) {
1747
1907
  if (tableCtx) {
1748
1908
  tableCtx.currentRow = [];
1749
1909
  walkSection(el, blocks, tableCtx, tableStack, ctx, depth + 1);
1750
- if (tableCtx.currentRow.length > 0) tableCtx.rows.push(tableCtx.currentRow);
1910
+ tableCtx.rows.push(tableCtx.currentRow);
1751
1911
  tableCtx.currentRow = [];
1752
1912
  }
1753
1913
  break;
@@ -1783,7 +1943,7 @@ function walkSection(node, blocks, tableCtx, tableStack, ctx, depth = 0) {
1783
1943
  case "p": {
1784
1944
  const paraPg = ctx.paraPage?.get(el);
1785
1945
  if (paraPg !== void 0 && ctx.pageBase !== void 0) ctx.page = ctx.pageBase + paraPg + 1;
1786
- const { text: rawText, href, footnote, style, segments } = extractParagraphInfo(el, ctx.styleMap, ctx);
1946
+ const { text: rawText, href, footnote, style, segments, placeholderSpans } = extractParagraphInfo(el, ctx.styleMap, ctx);
1787
1947
  let text = rawText;
1788
1948
  let headingLevel;
1789
1949
  const ph = resolveParaHeading(el, ctx);
@@ -1801,10 +1961,12 @@ function walkSection(node, blocks, tableCtx, tableStack, ctx, depth = 0) {
1801
1961
  }
1802
1962
  let segIdx = 0;
1803
1963
  let first = true;
1964
+ let lastCellBlock;
1804
1965
  const flush = () => {
1805
1966
  const s = segs[segIdx++];
1806
1967
  if (!s) return;
1807
1968
  const block = { type: "paragraph", text: s, pageNumber: ctx.page };
1969
+ if (cell2) lastCellBlock = block;
1808
1970
  if (first && !cell2) {
1809
1971
  first = false;
1810
1972
  if (style) block.style = style;
@@ -1819,16 +1981,19 @@ function walkSection(node, blocks, tableCtx, tableStack, ctx, depth = 0) {
1819
1981
  };
1820
1982
  tableCtx = walkParagraphChildren(el, blocks, tableCtx, tableStack, ctx, depth + 1, flush);
1821
1983
  while (segIdx < segs.length) flush();
1822
- if (footnote && cell2) cell2.text += (cell2.text ? "\n" : "") + `(\uC8FC: ${footnote})`;
1984
+ if (footnote && cell2) {
1985
+ cell2.text += (cell2.text ? "\n" : "") + `(\uC8FC: ${footnote})`;
1986
+ if (lastCellBlock) lastCellBlock.footnoteText = footnote;
1987
+ }
1823
1988
  break;
1824
1989
  }
1825
1990
  if (text) {
1826
1991
  if (tableCtx?.cell) {
1827
1992
  const cell2 = tableCtx.cell;
1828
- if (footnote) text += ` (\uC8FC: ${footnote})`;
1829
- cell2.text += (cell2.text || cell2.paraSeen ? "\n" : "") + text;
1993
+ cell2.text += (cell2.text || cell2.paraSeen ? "\n" : "") + (footnote ? `${text} (\uC8FC: ${footnote})` : text);
1830
1994
  cell2.paraSeen = true;
1831
1995
  const cellBlock = { type: "paragraph", text, pageNumber: ctx.page };
1996
+ if (footnote) cellBlock.footnoteText = footnote;
1832
1997
  const cellSpanMode = spanModeOf(ctx.shared.kordocLayout);
1833
1998
  if (cellSpanMode && !ph?.prefix) {
1834
1999
  const spans = extractRunSpans(el, ctx, cellSpanMode, cellSpanMode !== "kordoc");
@@ -1836,7 +2001,7 @@ function walkSection(node, blocks, tableCtx, tableStack, ctx, depth = 0) {
1836
2001
  cellBlock.spans = spans;
1837
2002
  }
1838
2003
  }
1839
- ;
2004
+ if (!cellBlock.spans && placeholderSpans) cellBlock.spans = withPrefixSpan(placeholderSpans, ph?.prefix);
1840
2005
  (cell2.blocks ??= []).push(cellBlock);
1841
2006
  } else if (!tableCtx) {
1842
2007
  if (ctx.shared.kordocLayout && /^─{10,}$/.test(text)) {
@@ -1868,6 +2033,7 @@ function walkSection(node, blocks, tableCtx, tableStack, ctx, depth = 0) {
1868
2033
  }
1869
2034
  if (spanMode !== "foreign" && el.getAttribute("paraPrIDRef") === KORDOC_PARA_QUOTE) block.quote = true;
1870
2035
  }
2036
+ if (!headingLevel && !block.spans && placeholderSpans) block.spans = withPrefixSpan(placeholderSpans, ph?.prefix);
1871
2037
  blocks.push(block);
1872
2038
  } else {
1873
2039
  blocks.push({ type: "paragraph", text, pageNumber: ctx.page });
@@ -1947,7 +2113,7 @@ function userShapeComment(el) {
1947
2113
  function mergeBlocksIntoCell(cell2, sink) {
1948
2114
  for (const b of sink) {
1949
2115
  if ((b.type === "paragraph" || b.type === "heading") && b.text) {
1950
- cell2.text += (cell2.text ? "\n" : "") + b.text;
2116
+ cell2.text += (cell2.text ? "\n" : "") + b.text + (b.footnoteText ? ` (\uC8FC: ${b.footnoteText})` : "");
1951
2117
  (cell2.blocks ??= []).push(b);
1952
2118
  } else if (b.type === "image" || b.type === "table") {
1953
2119
  if (b.type === "image" && b.text) {
@@ -2003,7 +2169,7 @@ function hasPageAutoNum(el, depth = 0) {
2003
2169
  function collectSubListText(el, ctx, depth = 0) {
2004
2170
  return collectSubListContent(el, ctx, depth).text;
2005
2171
  }
2006
- function collectSubListContent(el, ctx, depth = 0) {
2172
+ function collectSubListContent(el, ctx, depth = 0, sep = "\n") {
2007
2173
  const out = { text: "", blocks: [], hasStructure: false };
2008
2174
  if (depth > 10) return out;
2009
2175
  const parts = [];
@@ -2014,7 +2180,9 @@ function collectSubListContent(el, ctx, depth = 0) {
2014
2180
  if (ch.nodeType !== 1) continue;
2015
2181
  const tag = (ch.tagName || ch.localName || "").replace(/^[^:]+:/, "");
2016
2182
  if (tag === "p" || tag === "para") {
2017
- const t = extractParagraphInfo(ch, ctx.styleMap, ctx).text;
2183
+ let t = extractParagraphInfo(ch, ctx.styleMap, ctx).text;
2184
+ const ph = resolveParaHeading(ch, ctx);
2185
+ if (t && ph?.prefix) t = ph.prefix + " " + t;
2018
2186
  if (t) {
2019
2187
  parts.push(t);
2020
2188
  out.blocks.push({ type: "paragraph", text: t, pageNumber: ctx.page });
@@ -2029,6 +2197,10 @@ function collectSubListContent(el, ctx, depth = 0) {
2029
2197
  out.hasStructure = true;
2030
2198
  }
2031
2199
  }
2200
+ for (const t2 of drawTextParaTexts(ch, ctx)) {
2201
+ parts.push(t2);
2202
+ out.blocks.push({ type: "paragraph", text: t2, pageNumber: ctx.page });
2203
+ }
2032
2204
  } else if (tag === "tbl") {
2033
2205
  const built = buildSubListTable(ch, ctx, depth);
2034
2206
  if (built.text) parts.push(built.text);
@@ -2037,13 +2209,41 @@ function collectSubListContent(el, ctx, depth = 0) {
2037
2209
  out.hasStructure = true;
2038
2210
  }
2039
2211
  } else {
2040
- const sub = collectSubListContent(ch, ctx, depth + 1);
2212
+ const sub = collectSubListContent(ch, ctx, depth + 1, sep);
2041
2213
  if (sub.text) parts.push(sub.text);
2042
2214
  out.blocks.push(...sub.blocks);
2043
2215
  if (sub.hasStructure) out.hasStructure = true;
2044
2216
  }
2045
2217
  }
2046
- out.text = parts.join("\n").trim();
2218
+ out.text = parts.join(sep).trim();
2219
+ return out;
2220
+ }
2221
+ function drawTextParaTexts(para2, ctx) {
2222
+ const out = [];
2223
+ const visit = (node, depth, inDrawText) => {
2224
+ if (depth > MAX_XML_DEPTH) return;
2225
+ const kids = node.childNodes;
2226
+ if (!kids) return;
2227
+ for (let i = 0; i < kids.length; i++) {
2228
+ const ch = kids[i];
2229
+ if (ch.nodeType !== 1) continue;
2230
+ const tag = (ch.tagName || ch.localName || "").replace(/^[^:]+:/, "");
2231
+ if (tag === "tbl" || tag === "footNote" || tag === "endNote") continue;
2232
+ if (tag === "drawText") {
2233
+ if (!inDrawText) visit(ch, depth + 1, true);
2234
+ continue;
2235
+ }
2236
+ if (inDrawText && (tag === "p" || tag === "para")) {
2237
+ let t = extractParagraphInfo(ch, ctx.styleMap, ctx).text.trim();
2238
+ const ph = resolveParaHeading(ch, ctx);
2239
+ if (t && ph?.prefix) t = ph.prefix + " " + t;
2240
+ if (t) out.push(t);
2241
+ continue;
2242
+ }
2243
+ visit(ch, depth + 1, inDrawText);
2244
+ }
2245
+ };
2246
+ visit(para2, 0, false);
2047
2247
  return out;
2048
2248
  }
2049
2249
  function buildSubListTable(el, ctx, depth) {
@@ -2191,6 +2391,29 @@ function extractDrawTextBlocks(drawTextNode, blocks, ctx) {
2191
2391
  }
2192
2392
  }
2193
2393
  }
2394
+ var PH_OPEN = "";
2395
+ var PH_CLOSE = "";
2396
+ function clickHereGuide(fieldBegin) {
2397
+ if ((fieldBegin.getAttribute("type") || "").toUpperCase() !== "CLICK_HERE") return void 0;
2398
+ if (fieldBegin.getAttribute("dirty") === "1") return void 0;
2399
+ const children = findChildByLocalName(fieldBegin, "parameters")?.childNodes;
2400
+ if (!children) return void 0;
2401
+ let fromCommand;
2402
+ for (let i = 0; i < children.length; i++) {
2403
+ const ch = children[i];
2404
+ if (ch.nodeType !== 1) continue;
2405
+ const tag = (ch.tagName || ch.localName || "").replace(/^[^:]+:/, "");
2406
+ if (tag !== "stringParam") continue;
2407
+ const name = ch.getAttribute("name");
2408
+ if (name === "Direction") return ch.textContent || void 0;
2409
+ if (name === "Command") {
2410
+ const cmd = ch.textContent || "";
2411
+ const m = /Direction:wstring:(\d+):/.exec(cmd);
2412
+ if (m) fromCommand = cmd.slice(m.index + m[0].length, m.index + m[0].length + Number(m[1])) || void 0;
2413
+ }
2414
+ }
2415
+ return fromCommand;
2416
+ }
2194
2417
  function extractHyperlinkHref(fieldBegin) {
2195
2418
  if ((fieldBegin.getAttribute("type") || "").toUpperCase() !== "HYPERLINK") return void 0;
2196
2419
  const params = findChildByLocalName(fieldBegin, "parameters");
@@ -2227,12 +2450,26 @@ function extractParagraphInfo(para2, styleMap, ctx) {
2227
2450
  openFields.push({ rangeIdx: linkRanges.length - 1 });
2228
2451
  if (!href) href = url;
2229
2452
  } else {
2230
- openFields.push({});
2453
+ const guide = clickHereGuide(el);
2454
+ openFields.push(guide ? { guide, start: text.length } : {});
2231
2455
  }
2232
2456
  };
2233
2457
  const onFieldEnd = () => {
2234
2458
  const open = openFields.pop();
2235
2459
  if (open?.rangeIdx !== void 0) linkRanges[open.rangeIdx].end = text.length;
2460
+ if (open?.guide !== void 0 && open.start !== void 0) {
2461
+ const value = text.slice(open.start).replace(/\\\$/g, "$");
2462
+ if (value && (value === open.guide || value.trimEnd() === open.guide)) {
2463
+ text = text.slice(0, open.start) + PH_OPEN + text.slice(open.start) + PH_CLOSE;
2464
+ }
2465
+ }
2466
+ };
2467
+ const addNote = (noteEl, tag) => {
2468
+ if (isInDeletedRange(ctx)) return;
2469
+ const endnote = tag === "endNote" || tag === "en";
2470
+ text += noteRefMark(noteAttrsOf(noteEl), endnote ? ctx?.noteFormats?.endnote : ctx?.noteFormats?.footnote);
2471
+ const noteText = ctx ? collectSubListContent(noteEl, ctx, 0, " ").text : extractTextFromNode(noteEl);
2472
+ if (noteText) footnote = (footnote ? footnote + "; " : "") + noteText;
2236
2473
  };
2237
2474
  const handleCtrl = (ctrlEl) => {
2238
2475
  const kids = ctrlEl.childNodes;
@@ -2256,13 +2493,11 @@ function extractParagraphInfo(para2, styleMap, ctx) {
2256
2493
  }
2257
2494
  break;
2258
2495
  }
2259
- // 각주/미주 — 해당 문단의 footnote로 인라인 보존
2496
+ // 각주/미주 — 개체 자리에 참조 부호, 본문은 해당 문단의 footnote로 인라인 보존
2260
2497
  case "footNote":
2261
- case "endNote": {
2262
- const noteText = extractTextFromNode(k);
2263
- if (noteText) footnote = (footnote ? footnote + "; " : "") + noteText;
2498
+ case "endNote":
2499
+ addNote(k, ktag);
2264
2500
  break;
2265
- }
2266
2501
  // 하이퍼링크 — fieldBegin type=HYPERLINK의 Path 파라미터 (extent 오프셋 추적)
2267
2502
  case "fieldBegin":
2268
2503
  onFieldBegin(k);
@@ -2288,13 +2523,16 @@ function extractParagraphInfo(para2, styleMap, ctx) {
2288
2523
  }
2289
2524
  break;
2290
2525
  }
2526
+ // 자동번호 — 주석 머리 번호("1)"·"문1)")·캡션 번호("<그림 1>")만 그린다. 쪽번호 등은 종전대로 미방출
2527
+ case "autoNum":
2528
+ if (!isInDeletedRange(ctx)) text += noteAutoNumOf(k);
2529
+ break;
2291
2530
  // 콘텐츠 없는 제어 요소 — 스킵
2292
2531
  case "bookmark":
2293
2532
  case "pageNum":
2294
2533
  case "pageNumCtrl":
2295
2534
  case "pageHiding":
2296
2535
  case "newNum":
2297
- case "autoNum":
2298
2536
  case "indexmark":
2299
2537
  case "colPr":
2300
2538
  break;
@@ -2324,7 +2562,7 @@ function extractParagraphInfo(para2, styleMap, ctx) {
2324
2562
  ctx.warnings?.push({ page: ctx.page, message: "\uBCC0\uACBD\uCD94\uC801 \uC0AD\uC81C \uD14D\uC2A4\uD2B8 \uCD9C\uB825 \uC81C\uC678", code: "HIDDEN_TEXT_FILTERED" });
2325
2563
  }
2326
2564
  } else {
2327
- text += t.replace(/\x1E/g, "");
2565
+ text += escapeLiteralDollar(t.replace(/\x1E/g, ""));
2328
2566
  }
2329
2567
  continue;
2330
2568
  }
@@ -2377,9 +2615,14 @@ function extractParagraphInfo(para2, styleMap, ctx) {
2377
2615
  case "footNote":
2378
2616
  case "endNote":
2379
2617
  case "fn":
2380
- case "en": {
2381
- const noteText = extractTextFromNode(child);
2382
- if (noteText) footnote = (footnote ? footnote + "; " : "") + noteText;
2618
+ case "en":
2619
+ addNote(child, tag);
2620
+ break;
2621
+ // 대체 표현 묶음 — 한컴은 지원하는 hp:case 하나만 그린다(차트, 없으면 hp:default 의 OLE).
2622
+ // 둘 다 순회하면 같은 캡션이 두 번 나온다 (1790387 [그림 5] 이중 방출)
2623
+ case "switch": {
2624
+ const branch = findChildByLocalName(child, "case") ?? findChildByLocalName(child, "default");
2625
+ if (branch) walk(branch, depth + 1);
2383
2626
  break;
2384
2627
  }
2385
2628
  // 제어 요소 — 선별 순회 (머리말/꼬리말/각주/하이퍼링크/변경추적, v3.0)
@@ -2431,7 +2674,7 @@ function extractParagraphInfo(para2, styleMap, ctx) {
2431
2674
  if (raw.trim()) {
2432
2675
  try {
2433
2676
  const latex = hmlToLatex(raw).trim();
2434
- if (latex) text += " $" + latex + "$ ";
2677
+ if (latex) text += " $" + latex.replace(/\$/g, "\\$") + "$ ";
2435
2678
  } catch {
2436
2679
  if (ctx?.warnings) {
2437
2680
  ctx.warnings.push({
@@ -2480,10 +2723,21 @@ function extractParagraphInfo(para2, styleMap, ctx) {
2480
2723
  };
2481
2724
  let segments;
2482
2725
  if (text.includes("")) {
2483
- segments = text.split("").map(cleanParaText);
2726
+ segments = text.split("").map((s) => cleanParaText(stripPlaceholderMarks(s)));
2484
2727
  text = text.replace(/\x1E/g, "");
2485
2728
  }
2486
- const cleanText = cleanParaText(text);
2729
+ let cleanText = cleanParaText(segments ? stripPlaceholderMarks(text) : text);
2730
+ let placeholderSpans;
2731
+ if (cleanText.includes(PH_OPEN)) {
2732
+ placeholderSpans = [];
2733
+ for (const part of cleanText.split(/(\x1C[^\x1C\x1D]*\x1D)/)) {
2734
+ if (!part) continue;
2735
+ if (part.startsWith(PH_OPEN)) placeholderSpans.push({ text: part.slice(1, -1), placeholder: true });
2736
+ else placeholderSpans.push({ text: stripPlaceholderMarks(part) });
2737
+ }
2738
+ cleanText = stripPlaceholderMarks(cleanText);
2739
+ if (!placeholderSpans.some((s) => s.placeholder && s.text)) placeholderSpans = void 0;
2740
+ }
2487
2741
  let style;
2488
2742
  if (styleMap && charPrId) {
2489
2743
  const charProp = styleMap.charProperties.get(charPrId);
@@ -2496,99 +2750,13 @@ function extractParagraphInfo(para2, styleMap, ctx) {
2496
2750
  if (!style.fontSize && !style.bold && !style.italic) style = void 0;
2497
2751
  }
2498
2752
  }
2499
- return { text: cleanText, href, footnote, style, segments };
2753
+ return { text: cleanText, href, footnote, style, segments, placeholderSpans };
2500
2754
  }
2501
- var KORDOC_CHAR_CODE = "4";
2502
- var KORDOC_PARA_QUOTE = "6";
2503
- function spanModeOf(layout) {
2504
- if (layout === "default") return "kordoc";
2505
- if (layout === "gongmun") return "gongmun";
2506
- if (!layout) return "foreign";
2507
- return null;
2755
+ function withPrefixSpan(spans, prefix) {
2756
+ return prefix ? [{ text: prefix + " " }, ...spans] : spans;
2508
2757
  }
2509
- function extractRunSpans(para2, ctx, mode, requireMixed) {
2510
- const styleMap = ctx.styleMap;
2511
- if (!styleMap) return null;
2512
- const spans = [];
2513
- let styled = false;
2514
- const kids = para2.childNodes;
2515
- if (!kids) return null;
2516
- for (let i = 0; i < kids.length; i++) {
2517
- const child = kids[i];
2518
- if (child.nodeType !== 1) continue;
2519
- const tag = (child.tagName || child.localName || "").replace(/^[^:]+:/, "");
2520
- if (tag === "linesegarray") continue;
2521
- if (tag !== "run" && tag !== "r") return null;
2522
- let text = "";
2523
- const rkids = child.childNodes;
2524
- for (let j = 0; j < (rkids?.length ?? 0); j++) {
2525
- const rc = rkids[j];
2526
- if (rc.nodeType !== 1) continue;
2527
- const rtag = (rc.tagName || rc.localName || "").replace(/^[^:]+:/, "");
2528
- if (rtag === "t") {
2529
- const tkids = rc.childNodes;
2530
- for (let k = 0; k < (tkids?.length ?? 0); k++) {
2531
- const tk = tkids[k];
2532
- if (tk.nodeType === 3 || tk.nodeType === 4) text += tk.textContent || "";
2533
- else if (tk.nodeType === 1) {
2534
- const ttag = (tk.tagName || tk.localName || "").replace(/^[^:]+:/, "");
2535
- if (ttag === "tab" || ttag === "fwSpace" || ttag === "hwSpace" || ttag === "nbSpace") text += " ";
2536
- else return null;
2537
- }
2538
- }
2539
- } else if (rtag === "secPr" || rtag === "colPr") {
2540
- } else if (rtag === "ctrl") {
2541
- if (extractTextFromNode(rc)) return null;
2542
- } else {
2543
- return null;
2544
- }
2545
- }
2546
- if (!text) continue;
2547
- const prId = child.getAttribute("charPrIDRef") ?? "";
2548
- const cp = styleMap.charProperties.get(prId);
2549
- const span = { text };
2550
- if (mode !== "foreign" && prId === KORDOC_CHAR_CODE) span.code = true;
2551
- else {
2552
- if (cp?.bold) span.bold = true;
2553
- if (cp?.italic) span.italic = true;
2554
- if (cp?.strike) span.strike = true;
2555
- if (cp?.underline) span.underline = true;
2556
- }
2557
- if (span.bold || span.italic || span.strike || span.underline || span.code) styled = true;
2558
- spans.push(span);
2559
- }
2560
- if (!styled || spans.length === 0) return null;
2561
- const merged = [];
2562
- for (const s of spans) {
2563
- const last = merged[merged.length - 1];
2564
- if (last && !!last.bold === !!s.bold && !!last.italic === !!s.italic && !!last.strike === !!s.strike && !!last.underline === !!s.underline && !!last.code === !!s.code) {
2565
- last.text += s.text;
2566
- } else {
2567
- merged.push(s);
2568
- }
2569
- }
2570
- if (requireMixed && !merged.some((s) => s.strike || s.underline) && !merged.some((s) => !(s.bold || s.italic || s.code))) return null;
2571
- return merged;
2572
- }
2573
- function gongmunDepthFromIndent(para2, left, ctx) {
2574
- const styleMap = ctx.styleMap;
2575
- if (!styleMap) return null;
2576
- let unit = 0;
2577
- const kids = para2.childNodes;
2578
- for (let i = 0; i < (kids?.length ?? 0); i++) {
2579
- const child = kids[i];
2580
- if (child.nodeType !== 1) continue;
2581
- const tag = (child.tagName || child.localName || "").replace(/^[^:]+:/, "");
2582
- if (tag !== "run" && tag !== "r") continue;
2583
- const size = styleMap.charProperties.get(child.getAttribute("charPrIDRef") ?? "")?.fontSize;
2584
- if (size) unit = size * 100;
2585
- break;
2586
- }
2587
- if (!unit) return null;
2588
- const r = left / unit;
2589
- const nearInt = Math.round(r);
2590
- const depth = Math.abs(r - nearInt) < 0.2 ? nearInt : Math.round(2 * r - 1);
2591
- return depth >= 1 && depth < 8 ? depth : null;
2758
+ function stripPlaceholderMarks(s) {
2759
+ return s.includes(PH_OPEN) || s.includes(PH_CLOSE) ? s.replace(/[\x1C\x1D]/g, "") : s;
2592
2760
  }
2593
2761
 
2594
2762
  // src/hwp5/record.ts
@@ -2776,6 +2944,8 @@ function parseDocInfo(records) {
2776
2944
  }
2777
2945
  return { charShapes, paraShapes, styles, binData, numberings, bullets };
2778
2946
  }
2947
+ var LEADER_TAB_MARK = "";
2948
+ var LITERAL_DOLLAR_MARK = "\uFDD0";
2779
2949
  function createParaTextState() {
2780
2950
  return { text: "", ctrlIdx: 0, fieldStack: [], fieldRanges: [] };
2781
2951
  }
@@ -2829,8 +2999,9 @@ function appendParaText(state, data, resolveControl) {
2829
2999
  break;
2830
3000
  // 고정폭 공백
2831
3001
  // ── inline 타입 (2바이트 + 14바이트 확장) ──
3002
+ // 확장 u16[7] 중 [2] 의 하위 바이트 = 채움 모양(0 없음·3 점선 …), 상위 = 탭 종류+1 (rhwp tab_extended 실측)
2832
3003
  case CHAR_TAB:
2833
- result += " ";
3004
+ result += state.leaderMark && i + 14 <= data.length && data[i + 4] !== 0 ? LEADER_TAB_MARK : " ";
2834
3005
  if (i + 14 <= data.length) i += 14;
2835
3006
  break;
2836
3007
  default:
@@ -2863,7 +3034,7 @@ function appendParaText(state, data, resolveControl) {
2863
3034
  break;
2864
3035
  }
2865
3036
  }
2866
- result += String.fromCharCode(ch);
3037
+ result += ch === 36 && state.dollarMark ? LITERAL_DOLLAR_MARK : String.fromCharCode(ch);
2867
3038
  }
2868
3039
  break;
2869
3040
  }
@@ -2888,9 +3059,21 @@ function detectImageMime(data) {
2888
3059
  if (data[0] === 71 && data[1] === 73 && data[2] === 70) return "image/gif";
2889
3060
  if (data[0] === 66 && data[1] === 77) return "image/bmp";
2890
3061
  if (data[0] === 215 && data[1] === 205 && data[2] === 198 && data[3] === 154) return "image/wmf";
3062
+ if ((data[0] === 1 || data[0] === 2) && data[1] === 0 && data[2] === 9 && data[3] === 0) return "image/wmf";
2891
3063
  if (data[0] === 1 && data[1] === 0 && data[2] === 0 && data[3] === 0) return "image/emf";
3064
+ if (data[0] === 73 && data[1] === 73 && data[2] === 42 && data[3] === 0 || data[0] === 77 && data[1] === 77 && data[2] === 0 && data[3] === 42) return "image/tiff";
2892
3065
  return null;
2893
3066
  }
3067
+ function mimeExt(mime) {
3068
+ if (mime.includes("jpeg")) return "jpg";
3069
+ if (mime.includes("png")) return "png";
3070
+ if (mime.includes("gif")) return "gif";
3071
+ if (mime.includes("bmp")) return "bmp";
3072
+ if (mime.includes("tiff")) return "tif";
3073
+ if (mime.includes("wmf")) return "wmf";
3074
+ if (mime.includes("emf")) return "emf";
3075
+ return "bin";
3076
+ }
2894
3077
  function normalizeBinPayload(data) {
2895
3078
  if (detectImageMime(data)) return data;
2896
3079
  try {
@@ -2916,29 +3099,28 @@ function collectImageBlocks(blocks, out, depth = 0) {
2916
3099
  if (b.children) collectImageBlocks(b.children, out, depth + 1);
2917
3100
  }
2918
3101
  }
2919
- function forEachTableCell(blocks, fn) {
3102
+ var IMAGE_SENTINEL_RE = /!\[image\]\(hwp5bin:(\d+)\)/g;
3103
+ function resolveImageSentinels(blocks, renamed, depth = 0) {
3104
+ if (depth > MAX_BLOCK_DEPTH) return;
3105
+ const fix = (s) => s.includes("hwp5bin:") ? s.replace(IMAGE_SENTINEL_RE, (_m, idStr) => {
3106
+ const filename = renamed.get(Number(idStr));
3107
+ return filename ? `![image](${filename})` : "[\uC774\uBBF8\uC9C0]";
3108
+ }) : s;
2920
3109
  for (const b of blocks) {
3110
+ if (b.text) b.text = fix(b.text);
3111
+ if (b.footnoteText) b.footnoteText = fix(b.footnoteText);
2921
3112
  if (b.table) {
3113
+ if (b.table.caption) b.table.caption = fix(b.table.caption);
2922
3114
  for (const row of b.table.cells) {
2923
3115
  for (const cell2 of row) {
2924
- fn(cell2);
2925
- if (cell2.blocks) forEachTableCell(cell2.blocks, fn);
3116
+ cell2.text = fix(cell2.text);
3117
+ if (cell2.blocks) resolveImageSentinels(cell2.blocks, renamed, depth + 1);
2926
3118
  }
2927
3119
  }
2928
3120
  }
2929
- if (b.children) forEachTableCell(b.children, fn);
3121
+ if (b.children) resolveImageSentinels(b.children, renamed, depth + 1);
2930
3122
  }
2931
3123
  }
2932
- var CELL_IMAGE_SENTINEL_RE = /!\[image\]\(hwp5bin:(\d+)\)/g;
2933
- function resolveCellImageSentinels(blocks, renamed) {
2934
- forEachTableCell(blocks, (cell2) => {
2935
- if (!cell2.text.includes("hwp5bin:")) return;
2936
- cell2.text = cell2.text.replace(CELL_IMAGE_SENTINEL_RE, (_m, idStr) => {
2937
- const filename = renamed.get(Number(idStr));
2938
- return filename ? `![image](${filename})` : "[\uC774\uBBF8\uC9C0]";
2939
- });
2940
- });
2941
- }
2942
3124
  function resolveImageBlocks(binDataMap, blocks, warnings, sweepUnreferenced) {
2943
3125
  const imageBlocks = [];
2944
3126
  collectImageBlocks(blocks, imageBlocks);
@@ -2963,7 +3145,7 @@ function resolveImageBlocks(binDataMap, blocks, warnings, sweepUnreferenced) {
2963
3145
  resolved.set(storageId, null);
2964
3146
  } else {
2965
3147
  imageIndex++;
2966
- const ext = mime.includes("jpeg") ? "jpg" : mime.includes("png") ? "png" : mime.includes("gif") ? "gif" : mime.includes("bmp") ? "bmp" : "bin";
3148
+ const ext = mimeExt(mime);
2967
3149
  img = { filename: `image_${String(imageIndex).padStart(3, "0")}.${ext}`, data: new Uint8Array(bin.data), mime };
2968
3150
  resolved.set(storageId, img);
2969
3151
  images.push({ filename: img.filename, data: img.data, mimeType: img.mime, source: bin.name });
@@ -2987,14 +3169,15 @@ function resolveImageBlocks(binDataMap, blocks, warnings, sweepUnreferenced) {
2987
3169
  const mime = detectImageMime(bin.data);
2988
3170
  if (!mime) continue;
2989
3171
  imageIndex++;
2990
- const ext = mime.includes("jpeg") ? "jpg" : mime.includes("png") ? "png" : mime.includes("gif") ? "gif" : mime.includes("bmp") ? "bmp" : "bin";
3172
+ const ext = mimeExt(mime);
2991
3173
  const filename = `image_${String(imageIndex).padStart(3, "0")}.${ext}`;
2992
3174
  const data = new Uint8Array(bin.data);
2993
3175
  images.push({ filename, data, mimeType: mime, source: bin.name });
2994
3176
  blocks.push({ type: "image", text: filename, imageData: { data, mimeType: mime, filename: bin.name } });
3177
+ renamed.set(storageId, filename);
2995
3178
  }
2996
3179
  }
2997
- resolveCellImageSentinels(blocks, renamed);
3180
+ resolveImageSentinels(blocks, renamed);
2998
3181
  return images;
2999
3182
  }
3000
3183
  function collectHwp5BinData(fileIndex) {
@@ -3027,7 +3210,7 @@ function collectHwp5BinDataLenient(lcfb) {
3027
3210
  function extractHwp5Images(fileIndex, blocks, warnings, sweepUnreferenced) {
3028
3211
  const binDataMap = collectHwp5BinData(fileIndex);
3029
3212
  if (binDataMap.size === 0) {
3030
- resolveCellImageSentinels(blocks, /* @__PURE__ */ new Map());
3213
+ resolveImageSentinels(blocks, /* @__PURE__ */ new Map());
3031
3214
  return [];
3032
3215
  }
3033
3216
  return resolveImageBlocks(binDataMap, blocks, warnings, sweepUnreferenced);
@@ -3035,7 +3218,7 @@ function extractHwp5Images(fileIndex, blocks, warnings, sweepUnreferenced) {
3035
3218
  function extractHwp5ImagesLenient(lcfb, blocks, warnings, sweepUnreferenced) {
3036
3219
  const binDataMap = collectHwp5BinDataLenient(lcfb);
3037
3220
  if (binDataMap.size === 0) {
3038
- resolveCellImageSentinels(blocks, /* @__PURE__ */ new Map());
3221
+ resolveImageSentinels(blocks, /* @__PURE__ */ new Map());
3039
3222
  return [];
3040
3223
  }
3041
3224
  return resolveImageBlocks(binDataMap, blocks, warnings, sweepUnreferenced);
@@ -3311,21 +3494,65 @@ function parseSectionPathsFromManifest(xml) {
3311
3494
  // src/hwpx/metadata.ts
3312
3495
  async function extractHwpxMetadata(zip, metadata, decompressed) {
3313
3496
  try {
3314
- const metaPaths = ["meta.xml", "META-INF/meta.xml", "docProps/core.xml"];
3315
- for (const mp of metaPaths) {
3316
- const file = zip.file(mp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === mp.toLowerCase()) || null;
3317
- if (!file) continue;
3497
+ const read = async (path) => {
3498
+ const file = zip.file(path) || Object.values(zip.files).find((f) => f.name.toLowerCase() === path.toLowerCase()) || null;
3499
+ if (!file) return null;
3318
3500
  const xml = await file.async("text");
3319
3501
  if (decompressed) {
3320
3502
  decompressed.total += xml.length * 2;
3321
3503
  if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
3322
3504
  }
3505
+ return xml;
3506
+ };
3507
+ for (const hp of ["Contents/content.hpf", "content.hpf"]) {
3508
+ const xml = await read(hp);
3509
+ if (!xml) continue;
3510
+ parseOpfMetadata(xml, metadata);
3511
+ break;
3512
+ }
3513
+ if (metadata.title || metadata.author) return;
3514
+ const metaPaths = ["meta.xml", "META-INF/meta.xml", "docProps/core.xml"];
3515
+ for (const mp of metaPaths) {
3516
+ const xml = await read(mp);
3517
+ if (!xml) continue;
3323
3518
  parseDublinCoreMetadata(xml, metadata);
3324
3519
  if (metadata.title || metadata.author) return;
3325
3520
  }
3326
3521
  } catch {
3327
3522
  }
3328
3523
  }
3524
+ function parseOpfMetadata(xml, metadata) {
3525
+ const root = createXmlParser().parseFromString(stripDtd(xml), "text/xml").documentElement;
3526
+ const md2 = root ? findChildByLocalName(root, "metadata") : null;
3527
+ if (!md2) return;
3528
+ let title;
3529
+ const meta = /* @__PURE__ */ new Map();
3530
+ for (const el of elementChildren(md2)) {
3531
+ const text = el.textContent?.trim();
3532
+ if (!text) continue;
3533
+ const tag = localName(el);
3534
+ if (tag === "title") title ??= text;
3535
+ else if (tag === "meta") {
3536
+ const name = el.getAttribute("name");
3537
+ if (name && !meta.has(name)) meta.set(name, text);
3538
+ }
3539
+ }
3540
+ const put = (k, v) => {
3541
+ if (v !== void 0 && !metadata[k]) metadata[k] = v;
3542
+ };
3543
+ const keywords = meta.get("keyword")?.split(/[,;]/).map((k) => k.trim()).filter(Boolean);
3544
+ put("title", title);
3545
+ put("author", meta.get("creator"));
3546
+ put("description", meta.get("description") || meta.get("subject"));
3547
+ put("keywords", keywords?.length ? keywords : void 0);
3548
+ put("createdAt", isoDate(meta.get("CreatedDate")));
3549
+ put("modifiedAt", isoDate(meta.get("ModifiedDate")));
3550
+ }
3551
+ function isoDate(s) {
3552
+ if (!s || s.startsWith("1601-01-01")) return void 0;
3553
+ const m = /^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2})$/.exec(s);
3554
+ return m ? `${m[1]}T${m[2]}` : s;
3555
+ }
3329
3556
  function parseDublinCoreMetadata(xml, metadata) {
3330
3557
  const parser = createXmlParser();
3331
3558
  const doc = parser.parseFromString(stripDtd(xml), "text/xml");
@@ -4594,6 +4821,176 @@ function hwpEquationToLatex(equation) {
4594
4821
  return hmlToLatex(s).replace(/\s+/g, " ").trim();
4595
4822
  }
4596
4823
 
4824
+ // src/hwp5/ir-assemble.ts
4825
+ var INLINE_TABLE_MARK = "";
4826
+ var INLINE_JOIN = /* @__PURE__ */ new WeakSet();
4827
+ function emitParagraphBlocks(p) {
4828
+ const { headMarker, style, footnotes, objects, pageNumber } = p;
4829
+ const blocks = [];
4830
+ const decorate = (t) => {
4831
+ let deco = style?.strike ? `~~${t}~~` : t;
4832
+ if (style?.underline) deco = `<u>${deco}</u>`;
4833
+ return deco;
4834
+ };
4835
+ if (p.text.includes(INLINE_TABLE_MARK)) {
4836
+ const segments = p.text.split(INLINE_TABLE_MARK);
4837
+ let segIdx = 0;
4838
+ let first = true;
4839
+ let lineOpen = false;
4840
+ let markerPending = !!headMarker;
4841
+ const flush = () => {
4842
+ if (segIdx >= segments.length) return;
4843
+ const s = segments[segIdx++].replace(/\$\$/g, "$ $").trim();
4844
+ if (!s) return;
4845
+ let t = decorate(s);
4846
+ if (markerPending) {
4847
+ t = `${headMarker} ${t}`;
4848
+ markerPending = false;
4849
+ }
4850
+ const block = { type: "paragraph", text: t, pageNumber };
4851
+ if (first) {
4852
+ if (style) block.style = style;
4853
+ if (footnotes.length > 0) block.footnoteText = footnotes.join("; ");
4854
+ first = false;
4855
+ }
4856
+ if (lineOpen) INLINE_JOIN.add(block);
4857
+ lineOpen = true;
4858
+ blocks.push(block);
4859
+ };
4860
+ for (const obj of objects) {
4861
+ if (obj.table) {
4862
+ flush();
4863
+ if (obj.inline && lineOpen) for (const b of obj.blocks) INLINE_JOIN.add(b);
4864
+ lineOpen = obj.inline;
4865
+ }
4866
+ blocks.push(...obj.blocks);
4867
+ }
4868
+ while (segIdx < segments.length) flush();
4869
+ if (first && footnotes.length > 0) blocks.unshift({ type: "paragraph", text: `(\uC8FC: ${footnotes.join("; ")})`, pageNumber });
4870
+ return blocks;
4871
+ }
4872
+ const trimmed = p.text.replace(/\$\$/g, "$ $").trim();
4873
+ if (trimmed) {
4874
+ const block = {
4875
+ type: p.headingLevel > 0 ? "heading" : "paragraph",
4876
+ text: headMarker ? `${headMarker} ${trimmed}` : trimmed,
4877
+ pageNumber
4878
+ };
4879
+ if (p.headingLevel > 0) block.level = p.headingLevel;
4880
+ if (style) {
4881
+ block.style = style;
4882
+ if (style.strike || style.underline) block.text = headMarker ? `${headMarker} ${decorate(trimmed)}` : decorate(trimmed);
4883
+ }
4884
+ if (footnotes.length > 0) block.footnoteText = footnotes.join("; ");
4885
+ blocks.push(block);
4886
+ } else if (footnotes.length > 0) {
4887
+ blocks.push({ type: "paragraph", text: `(\uC8FC: ${footnotes.join("; ")})`, pageNumber });
4888
+ }
4889
+ for (const obj of objects) blocks.push(...obj.blocks);
4890
+ return blocks;
4891
+ }
4892
+ function tableFlatText(table2) {
4893
+ const flat = convertTableToText(table2.cells);
4894
+ return table2.caption ? table2.caption + (flat ? "\n" + flat : "") : flat;
4895
+ }
4896
+ function blocksPlainText(blocks, sep) {
4897
+ const parts = [];
4898
+ for (const b of blocks) {
4899
+ if (b.type === "image") continue;
4900
+ if (b.type === "table") {
4901
+ const flat = b.table ? tableFlatText(b.table) : "";
4902
+ if (flat) parts.push(flat);
4903
+ continue;
4904
+ }
4905
+ if (b.text) {
4906
+ let t = b.text;
4907
+ if (b.footnoteText) t += ` (\uC8FC: ${b.footnoteText})`;
4908
+ parts.push(t);
4909
+ }
4910
+ }
4911
+ return parts.join(sep).trim();
4912
+ }
4913
+ function cellTextFromBlocks(blocks) {
4914
+ let text = "";
4915
+ const add = (part, b) => {
4916
+ text += (text ? INLINE_JOIN.has(b) ? " " : "\n" : "") + part;
4917
+ };
4918
+ let hasStructure = false;
4919
+ for (const b of blocks) {
4920
+ if (b.type === "image" && b.text) {
4921
+ add(`![image](hwp5bin:${b.text})`, b);
4922
+ hasStructure = true;
4923
+ } else if (b.type === "table" && b.table) {
4924
+ const flat = tableFlatText(b.table);
4925
+ if (flat) add(flat, b);
4926
+ hasStructure = true;
4927
+ } else if (b.text) {
4928
+ if (b.footnoteText) {
4929
+ b.text += ` (\uC8FC: ${b.footnoteText})`;
4930
+ delete b.footnoteText;
4931
+ }
4932
+ add(b.text, b);
4933
+ }
4934
+ }
4935
+ return { text, hasStructure };
4936
+ }
4937
+ function resolveCellOverlaps(cells, rows, cols) {
4938
+ const owner = /* @__PURE__ */ new Map();
4939
+ const out = [];
4940
+ for (const cell2 of cells) {
4941
+ const r0 = cell2.rowAddr ?? 0, c0 = cell2.colAddr ?? 0;
4942
+ const hit = owner.get(r0 * cols + c0);
4943
+ if (hit) {
4944
+ if (cell2.blocks?.length) {
4945
+ hit.blocks = [...hit.blocks ?? (hit.text ? [{ type: "paragraph", text: hit.text }] : []), ...cell2.blocks];
4946
+ }
4947
+ if (cell2.text.trim()) hit.text = hit.text.trim() ? `${hit.text}
4948
+ ${cell2.text}` : cell2.text;
4949
+ continue;
4950
+ }
4951
+ const csIn = Math.max(1, Math.min(cell2.colSpan, cols - c0));
4952
+ let cs = cell2.colSpan;
4953
+ for (let c = c0 + 1; c < c0 + csIn; c++) if (owner.has(r0 * cols + c)) {
4954
+ cs = c - c0;
4955
+ break;
4956
+ }
4957
+ const rsIn = Math.max(1, Math.min(cell2.rowSpan, rows - r0));
4958
+ let rs = cell2.rowSpan;
4959
+ scan: for (let r = r0 + 1; r < r0 + rsIn; r++) {
4960
+ for (let c = c0; c < c0 + Math.min(cs, csIn); c++) if (owner.has(r * cols + c)) {
4961
+ rs = r - r0;
4962
+ break scan;
4963
+ }
4964
+ }
4965
+ const placed = cs === cell2.colSpan && rs === cell2.rowSpan ? cell2 : { ...cell2, colSpan: cs, rowSpan: rs };
4966
+ for (let r = r0; r < Math.min(r0 + rs, rows); r++) for (let c = c0; c < Math.min(c0 + cs, cols); c++) owner.set(r * cols + c, placed);
4967
+ out.push(placed);
4968
+ }
4969
+ return out;
4970
+ }
4971
+ function buildAddressedTable(cells, rows, cols, keepAnchoredEmptyCols) {
4972
+ for (const c of cells) {
4973
+ rows = Math.max(rows, Math.min((c.rowAddr ?? 0) + 1, MAX_ROWS));
4974
+ cols = Math.max(cols, Math.min((c.colAddr ?? 0) + 1, MAX_COLS));
4975
+ }
4976
+ const placed = resolveCellOverlaps(cells, rows, cols);
4977
+ const byRow = Array.from({ length: rows }, () => []);
4978
+ for (const c of placed) {
4979
+ const r = c.rowAddr ?? 0;
4980
+ if (r < rows && (c.colAddr ?? 0) < cols) byRow[r].push(c);
4981
+ }
4982
+ const table2 = buildTable(byRow, { keepAnchoredEmptyCols });
4983
+ if (table2.rows === 0) return null;
4984
+ for (const c of placed) {
4985
+ if (!c.blocks?.length && !c.isHeader) continue;
4986
+ const target = table2.cells[c.rowAddr ?? 0]?.[c.colAddr ?? 0];
4987
+ if (!target || target.text !== c.text.trim()) continue;
4988
+ if (c.blocks?.length) target.blocks = c.blocks;
4989
+ if (c.isHeader) target.isHeader = true;
4990
+ }
4991
+ return table2;
4992
+ }
4993
+
4597
4994
  // src/hwp5/body.ts
4598
4995
  var MAX_NEST_DEPTH = 8;
4599
4996
  function cid(s) {
@@ -4743,8 +5140,13 @@ function parseParagraph(records, start, end, ctx) {
4743
5140
  }
4744
5141
  for (const ctrl of ctrls) {
4745
5142
  applyCtrlEffect(ctrl, records, ctx);
5143
+ if (ctrl.id === CTRL_TBL && ctrl.afterBlocks && ctrl.data.length >= 8 && ctrl.data.readUInt32LE(4) & 1) {
5144
+ ctrl.inlineText = INLINE_TABLE_MARK;
5145
+ }
4746
5146
  }
4747
5147
  const state = createParaTextState();
5148
+ state.leaderMark = true;
5149
+ state.dollarMark = true;
4748
5150
  const resolver = (idx, id) => {
4749
5151
  let ctrl = idx >= 0 && idx < ctrls.length ? ctrls[idx] : void 0;
4750
5152
  if (!ctrl || ctrl.idRaw !== id && ctrl.id !== id) {
@@ -4763,17 +5165,28 @@ function parseParagraph(records, start, end, ctx) {
4763
5165
  const applied = [];
4764
5166
  for (const r of ranges) {
4765
5167
  const ctrl = ctrls[r.ctrlIdx];
4766
- if (!ctrl?.href || r.end <= r.start) continue;
5168
+ if (!ctrl || r.end <= r.start) continue;
4767
5169
  if (applied.some(([s, e]) => r.start < e && r.end > s)) continue;
5170
+ const anchor = text.slice(r.start, r.end);
5171
+ if (ctrl.guide !== void 0) {
5172
+ const plain4 = anchor.replaceAll(LITERAL_DOLLAR_MARK, "$");
5173
+ if (plain4 === ctrl.guide || plain4.trimEnd() === ctrl.guide) {
5174
+ text = text.slice(0, r.start) + text.slice(r.end);
5175
+ applied.push([r.start, r.end]);
5176
+ }
5177
+ continue;
5178
+ }
5179
+ if (!ctrl.href) continue;
4768
5180
  const href = sanitizeHref(ctrl.href);
4769
5181
  if (!href) continue;
4770
- const anchor = text.slice(r.start, r.end);
4771
- if (!anchor.trim()) continue;
5182
+ if (!anchor.trim() || anchor.includes(INLINE_TABLE_MARK) || anchor.includes(LEADER_TAB_MARK)) continue;
4772
5183
  text = text.slice(0, r.start) + `[${anchor}](${href})` + text.slice(r.end);
4773
5184
  applied.push([r.start, r.end]);
4774
5185
  }
4775
5186
  }
4776
- const trimmed = text.replace(/\$\$/g, "$ $").trim();
5187
+ const leaderAt = text.indexOf(LEADER_TAB_MARK);
5188
+ if (leaderAt >= 0) text = text.slice(0, leaderAt);
5189
+ if (text.includes(LITERAL_DOLLAR_MARK)) text = text.replaceAll(LITERAL_DOLLAR_MARK, "\\$");
4777
5190
  let headingLevel = 0;
4778
5191
  let headMarker = null;
4779
5192
  const ps = ctx.docInfo && paraShapeId >= 0 && paraShapeId < ctx.docInfo.paraShapes.length ? ctx.docInfo.paraShapes[paraShapeId] : null;
@@ -4797,35 +5210,16 @@ function parseParagraph(records, start, end, ctx) {
4797
5210
  if (bullet && bullet.char !== "\uFFFF") headMarker = bullet.char;
4798
5211
  }
4799
5212
  }
4800
- const blocks = [];
4801
- const footnotes = ctrls.filter((c) => c.footnote).map((c) => c.footnote);
4802
- if (trimmed) {
4803
- const block = {
4804
- type: headingLevel > 0 ? "heading" : "paragraph",
4805
- text: headMarker ? `${headMarker} ${trimmed}` : trimmed,
4806
- pageNumber: ctx.page
4807
- };
4808
- if (headingLevel > 0) block.level = headingLevel;
4809
- if (ctx.docInfo && charShapeIds.length > 0) {
4810
- const style = resolveCharStyle(charShapeIds, ctx.docInfo);
4811
- if (style) {
4812
- block.style = style;
4813
- if (style.strike || style.underline) {
4814
- let deco = style.strike ? `~~${trimmed}~~` : trimmed;
4815
- if (style.underline) deco = `<u>${deco}</u>`;
4816
- block.text = headMarker ? `${headMarker} ${deco}` : deco;
4817
- }
4818
- }
4819
- }
4820
- if (footnotes.length > 0) block.footnoteText = footnotes.join("; ");
4821
- blocks.push(block);
4822
- } else if (footnotes.length > 0) {
4823
- blocks.push({ type: "paragraph", text: `(\uC8FC: ${footnotes.join("; ")})`, pageNumber: ctx.page });
4824
- }
4825
- for (const ctrl of ctrls) {
4826
- if (ctrl.afterBlocks) blocks.push(...ctrl.afterBlocks);
4827
- }
4828
- return blocks;
5213
+ return emitParagraphBlocks({
5214
+ text,
5215
+ headMarker,
5216
+ headingLevel,
5217
+ style: ctx.docInfo && charShapeIds.length > 0 ? resolveCharStyle(charShapeIds, ctx.docInfo) : void 0,
5218
+ footnotes: ctrls.filter((c) => c.footnote).map((c) => c.footnote),
5219
+ // 컨트롤 파생 블록 (표/이미지/글상자) — 컨트롤 순서대로
5220
+ objects: ctrls.filter((c) => c.afterBlocks).map((c) => ({ blocks: c.afterBlocks, table: c.id === CTRL_TBL, inline: c.inlineText === INLINE_TABLE_MARK })),
5221
+ pageNumber: ctx.page
5222
+ });
4829
5223
  }
4830
5224
  function applyCtrlEffect(ctrl, records, ctx) {
4831
5225
  switch (ctrl.id) {
@@ -4920,33 +5314,20 @@ function parseListHeaderParagraphs(ctrl, records, ctx) {
4920
5314
  }
4921
5315
  return [];
4922
5316
  }
4923
- function blocksPlainText(blocks, sep) {
4924
- const parts = [];
4925
- for (const b of blocks) {
4926
- if (b.type === "image") continue;
4927
- if (b.type === "table") continue;
4928
- if (b.text) {
4929
- let t = b.text;
4930
- if (b.footnoteText) t += ` (\uC8FC: ${b.footnoteText})`;
4931
- parts.push(t);
4932
- }
4933
- }
4934
- return parts.join(sep).trim();
4935
- }
4936
5317
  function applyNoteEffect(ctrl, records, ctx, autoType) {
4937
5318
  const num4 = ctx.doc.autoCounters.get(autoType) ?? 1;
4938
- let before = "";
4939
- let after = "";
5319
+ let before2 = "";
5320
+ let after2 = "";
4940
5321
  let shape = 0;
4941
5322
  if (ctrl.data.length >= 12) {
4942
- before = wcharAt(ctrl.data, 8);
4943
- after = wcharAt(ctrl.data, 10);
5323
+ before2 = wcharAt(ctrl.data, 8);
5324
+ after2 = wcharAt(ctrl.data, 10);
4944
5325
  }
4945
5326
  if (ctrl.data.length >= 16) {
4946
5327
  shape = ctrl.data.readUInt32LE(12) & 255;
4947
5328
  }
4948
5329
  const formatted = formatNumber(num4, shapeFormatToNumFmt(shape));
4949
- const marker = before || after ? `${before}${formatted}${after}` : `${formatted})`;
5330
+ const marker = before2 || after2 ? `${before2}${formatted}${after2}` : `${formatted})`;
4950
5331
  const content = blocksPlainText(parseListHeaderParagraphs(ctrl, records, ctx), " ");
4951
5332
  if ((ctx.doc.autoCounters.get(autoType) ?? 1) <= num4) {
4952
5333
  ctx.doc.autoCounters.set(autoType, num4 + 1);
@@ -4971,8 +5352,18 @@ function applyFieldEffect(ctrl) {
4971
5352
  const url = hyperlinkUrlFromCommand(command);
4972
5353
  if (url) ctrl.href = url;
4973
5354
  }
5355
+ } else if (ctrl.id === FIELD_CLK && ctrl.data.length >= 8 && !(ctrl.data.readUInt32LE(4) & 1 << 15)) {
5356
+ const command = parseFieldCommand(ctrl.data);
5357
+ const guide = command ? clickHereGuide2(command) : void 0;
5358
+ if (guide) ctrl.guide = guide;
4974
5359
  }
4975
5360
  }
5361
+ function clickHereGuide2(command) {
5362
+ const m = /Direction:wstring:(\d+):/.exec(command);
5363
+ if (!m) return void 0;
5364
+ const start = m.index + m[0].length;
5365
+ return command.slice(start, start + Number(m[1])) || void 0;
5366
+ }
4976
5367
  function parseFieldCommand(data) {
4977
5368
  if (data.length < 11) return null;
4978
5369
  const cmdLen = data.readUInt16LE(9);
@@ -5001,18 +5392,19 @@ function parseTableControl(ctrl, records, ctx) {
5001
5392
  if (ctx.depth >= MAX_NEST_DEPTH) return null;
5002
5393
  const { childStart, childEnd } = ctrl;
5003
5394
  const sourceId = ctx.tableIds?.get(childStart - 1);
5395
+ const directLevel = records[childStart - 1].level + 1;
5004
5396
  let rows = 0;
5005
5397
  let cols = 0;
5006
5398
  let tableIdx = -1;
5007
5399
  for (let i2 = childStart; i2 < childEnd; i2++) {
5008
- if (records[i2].tagId === TAG_TABLE && records[i2].data.length >= 8) {
5400
+ if (records[i2].tagId === TAG_TABLE && records[i2].level === directLevel && records[i2].data.length >= 8) {
5009
5401
  rows = Math.min(records[i2].data.readUInt16LE(4), MAX_ROWS);
5010
5402
  cols = Math.min(records[i2].data.readUInt16LE(6), MAX_COLS);
5011
5403
  tableIdx = i2;
5012
5404
  break;
5013
5405
  }
5014
5406
  }
5015
- if (tableIdx < 0 || rows === 0 || cols === 0) return null;
5407
+ if (tableIdx < 0) return null;
5016
5408
  let caption;
5017
5409
  for (let i2 = childStart; i2 < tableIdx; i2++) {
5018
5410
  if (records[i2].tagId === TAG_LIST_HEADER) {
@@ -5042,20 +5434,17 @@ function parseTableControl(ctrl, records, ctx) {
5042
5434
  i++;
5043
5435
  }
5044
5436
  if (cells.length === 0) return null;
5045
- const hasAddr = cells.some((c) => c.colAddr !== void 0 && c.rowAddr !== void 0);
5046
- if (hasAddr) {
5047
- const cellRows2 = arrangeCells(rows, cols, cells);
5048
- const irCells = cellRows2.map((row) => row.map((c) => {
5049
- const ir = { text: c.text.trim(), colSpan: c.colSpan, rowSpan: c.rowSpan };
5050
- if (c.blocks?.length) ir.blocks = c.blocks;
5051
- if (c.isHeader) ir.isHeader = true;
5052
- return ir;
5053
- }));
5054
- const table3 = { rows, cols, cells: irCells, hasHeader: rows > 1 };
5437
+ if (cells.some((c) => c.colAddr !== void 0 && c.rowAddr !== void 0)) {
5438
+ const table3 = buildAddressedTable(cells, rows, cols, ctx.doc.keepTrailingEmptyCols);
5439
+ if (!table3) return null;
5055
5440
  if (caption) table3.caption = caption;
5056
5441
  if (sourceId) table3.sourceId = sourceId;
5057
5442
  return table3;
5058
5443
  }
5444
+ if (rows === 0 || cols === 0) {
5445
+ rows = Math.min(cells.length, MAX_ROWS);
5446
+ cols = 1;
5447
+ }
5059
5448
  const cellRows = arrangeCells(rows, cols, cells);
5060
5449
  const table2 = buildTable(cellRows, { keepAnchoredEmptyCols: ctx.doc.keepTrailingEmptyCols });
5061
5450
  if (caption && table2.rows > 0) table2.caption = caption;
@@ -5079,38 +5468,19 @@ function parseCell(records, lhIdx, end, ctx) {
5079
5468
  if (rs > 0) rowSpan = Math.min(rs, MAX_ROWS);
5080
5469
  }
5081
5470
  const blocks = ctx.depth < MAX_NEST_DEPTH ? parseParagraphList(records, lhIdx + 1, end, { ...ctx, depth: ctx.depth + 1 }) : [];
5082
- const parts = [];
5083
- let hasStructure = false;
5084
- for (const b of blocks) {
5085
- if (b.type === "image" && b.text) {
5086
- parts.push(`![image](hwp5bin:${b.text})`);
5087
- hasStructure = true;
5088
- } else if (b.type === "table" && b.table) {
5089
- const flat = convertTableToText(b.table.cells);
5090
- if (flat) parts.push(flat);
5091
- hasStructure = true;
5092
- } else if (b.text) {
5093
- let t = b.text;
5094
- if (b.footnoteText) {
5095
- t += ` (\uC8FC: ${b.footnoteText})`;
5096
- hasStructure = true;
5097
- }
5098
- parts.push(t);
5099
- }
5100
- }
5101
- const cell2 = { text: parts.join("\n"), colSpan, rowSpan, colAddr, rowAddr };
5471
+ const { text, hasStructure } = cellTextFromBlocks(blocks);
5472
+ const cell2 = { text, colSpan, rowSpan, colAddr, rowAddr };
5102
5473
  if (hasStructure && blocks.length > 0) cell2.blocks = blocks;
5103
5474
  if (isHeader) cell2.isHeader = true;
5104
5475
  return cell2;
5105
5476
  }
5106
5477
  function arrangeCells(rows, cols, cells) {
5107
5478
  const grid = Array.from({ length: rows }, () => Array(cols).fill(null));
5108
- const hasAddr = cells.some((c) => c.colAddr !== void 0 && c.rowAddr !== void 0);
5109
- if (hasAddr) {
5110
- for (const cell2 of cells) {
5111
- const r = cell2.rowAddr ?? 0;
5112
- const c = cell2.colAddr ?? 0;
5113
- if (r >= rows || c >= cols) continue;
5479
+ let cellIdx = 0;
5480
+ for (let r = 0; r < rows && cellIdx < cells.length; r++) {
5481
+ for (let c = 0; c < cols && cellIdx < cells.length; c++) {
5482
+ if (grid[r][c] !== null) continue;
5483
+ const cell2 = cells[cellIdx++];
5114
5484
  grid[r][c] = cell2;
5115
5485
  for (let dr = 0; dr < cell2.rowSpan; dr++) {
5116
5486
  for (let dc = 0; dc < cell2.colSpan; dc++) {
@@ -5120,22 +5490,6 @@ function arrangeCells(rows, cols, cells) {
5120
5490
  }
5121
5491
  }
5122
5492
  }
5123
- } else {
5124
- let cellIdx = 0;
5125
- for (let r = 0; r < rows && cellIdx < cells.length; r++) {
5126
- for (let c = 0; c < cols && cellIdx < cells.length; c++) {
5127
- if (grid[r][c] !== null) continue;
5128
- const cell2 = cells[cellIdx++];
5129
- grid[r][c] = cell2;
5130
- for (let dr = 0; dr < cell2.rowSpan; dr++) {
5131
- for (let dc = 0; dc < cell2.colSpan; dc++) {
5132
- if (dr === 0 && dc === 0) continue;
5133
- if (r + dr < rows && c + dc < cols)
5134
- grid[r + dr][c + dc] = { text: "", colSpan: 1, rowSpan: 1 };
5135
- }
5136
- }
5137
- }
5138
- }
5139
5493
  }
5140
5494
  return grid.map((row) => row.map((c) => c || { text: "", colSpan: 1, rowSpan: 1 }));
5141
5495
  }
@@ -5350,7 +5704,7 @@ function parseHwp5Document(buffer, options) {
5350
5704
  for (let si = 0; si < sections.length; si++) {
5351
5705
  const records = sectionRecords[si];
5352
5706
  if (!records) continue;
5353
- const detect = sectionDetects[si];
5707
+ const detect2 = sectionDetects[si];
5354
5708
  if (!layoutPages && pageFilter && !pageFilter.has(si + 1)) continue;
5355
5709
  try {
5356
5710
  const sectionBlocks = parseSection(
@@ -5359,7 +5713,7 @@ function parseHwp5Document(buffer, options) {
5359
5713
  warnings,
5360
5714
  si + 1,
5361
5715
  doc,
5362
- layoutPages ? { base: pageBase, pageAtPara: detect.pageAtPara } : void 0,
5716
+ layoutPages ? { base: pageBase, pageAtPara: detect2.pageAtPara } : void 0,
5363
5717
  sectionTableIds[si]
5364
5718
  );
5365
5719
  bodyBlocks.push(...sectionBlocks);
@@ -5369,7 +5723,7 @@ function parseHwp5Document(buffer, options) {
5369
5723
  if (secErr instanceof KordocError) throw secErr;
5370
5724
  warnings.push({ page: si + 1, message: `\uC139\uC158 ${si + 1} \uD30C\uC2F1 \uC2E4\uD328: ${secErr instanceof Error ? secErr.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958"}`, code: "PARTIAL_PARSE" });
5371
5725
  } finally {
5372
- pageBase += detect.pages;
5726
+ pageBase += detect2.pages;
5373
5727
  }
5374
5728
  }
5375
5729
  if (pageFilter && layoutPages) {
@@ -5389,7 +5743,7 @@ function parseHwp5Document(buffer, options) {
5389
5743
  }
5390
5744
  const outline = flatBlocks.filter((b) => b.type === "heading" && b.level && b.text).map((b) => ({ level: b.level, text: b.text, pageNumber: b.pageNumber }));
5391
5745
  let markdown = blocksToMarkdown(flatBlocks);
5392
- if (options?.inlineImages && images.length > 0) {
5746
+ if (options?.inlineImages && options.images !== false && images.length > 0) {
5393
5747
  try {
5394
5748
  markdown = inlineImagesIntoMarkdown(markdown, images, { compress: true });
5395
5749
  } catch (inlineErr) {
@@ -5470,6 +5824,11 @@ function detectHwp5Headings(blocks, docInfo) {
5470
5824
  }
5471
5825
  }
5472
5826
  }
5827
+ var VT_I2 = 2;
5828
+ var VT_LPSTR = 30;
5829
+ var VT_LPWSTR = 31;
5830
+ var VT_FILETIME = 64;
5831
+ var CODE_PAGE_LABELS = { 949: "euc-kr", 932: "shift_jis", 936: "gbk", 950: "big5", 65001: "utf-8" };
5473
5832
  function extractHwp5Metadata(cfb, metadata) {
5474
5833
  try {
5475
5834
  const summaryEntry = CFB.find(cfb, "/HwpSummaryInformation") || CFB.find(cfb, "/SummaryInformation");
@@ -5482,26 +5841,60 @@ function extractHwp5Metadata(cfb, metadata) {
5482
5841
  if (setOffset >= data.length - 8) return;
5483
5842
  const numProps = data.readUInt32LE(setOffset + 4);
5484
5843
  if (numProps === 0 || numProps > 100) return;
5844
+ const props = /* @__PURE__ */ new Map();
5485
5845
  for (let i = 0; i < numProps; i++) {
5486
5846
  const entryOffset = setOffset + 8 + i * 8;
5487
5847
  if (entryOffset + 8 > data.length) break;
5488
- const propId = data.readUInt32LE(entryOffset);
5489
5848
  const propOffset = setOffset + data.readUInt32LE(entryOffset + 4);
5490
- if (propOffset + 8 > data.length) continue;
5491
- if (propId !== 2 && propId !== 4 && propId !== 6) continue;
5492
- const propType = data.readUInt32LE(propOffset);
5493
- if (propType !== 30) continue;
5494
- const strLen = data.readUInt32LE(propOffset + 4);
5495
- if (strLen === 0 || strLen > 1e4 || propOffset + 8 + strLen > data.length) continue;
5496
- const str = data.subarray(propOffset + 8, propOffset + 8 + strLen).toString("utf8").replace(/\0+$/, "").trim();
5497
- if (!str) continue;
5498
- if (propId === 2) metadata.title = str;
5499
- else if (propId === 4) metadata.author = str;
5500
- else if (propId === 6) metadata.description = str;
5849
+ if (propOffset + 8 <= data.length) props.set(data.readUInt32LE(entryOffset), propOffset);
5501
5850
  }
5851
+ const cpAt = props.get(1);
5852
+ const codePage = cpAt !== void 0 && data.readUInt32LE(cpAt) === VT_I2 ? data.readUInt16LE(cpAt + 4) : 0;
5853
+ const text = (id) => {
5854
+ const at = props.get(id);
5855
+ return at === void 0 ? void 0 : readPropString(data, at, codePage);
5856
+ };
5857
+ const time = (id) => {
5858
+ const at = props.get(id);
5859
+ return at === void 0 ? void 0 : readPropFileTime(data, at);
5860
+ };
5861
+ const title = text(2), author = text(4), description = text(6) || text(3);
5862
+ if (title) metadata.title = title;
5863
+ if (author) metadata.author = author;
5864
+ if (description) metadata.description = description;
5865
+ const keywords = text(5)?.split(/[,;]/).map((k) => k.trim()).filter(Boolean);
5866
+ if (keywords?.length) metadata.keywords = keywords;
5867
+ const createdAt = time(12), modifiedAt = time(13);
5868
+ if (createdAt) metadata.createdAt = createdAt;
5869
+ if (modifiedAt) metadata.modifiedAt = modifiedAt;
5502
5870
  } catch {
5503
5871
  }
5504
5872
  }
5873
+ function readPropString(data, at, codePage) {
5874
+ const type = data.readUInt32LE(at);
5875
+ const unit = type === VT_LPWSTR ? 2 : type === VT_LPSTR ? 1 : 0;
5876
+ const count = data.readUInt32LE(at + 4);
5877
+ if (!unit || count === 0 || count > 1e4 || at + 8 + count * unit > data.length) return void 0;
5878
+ const bytes = data.subarray(at + 8, at + 8 + count * unit);
5879
+ let s;
5880
+ if (unit === 2 || codePage === 1200) s = bytes.toString("utf16le");
5881
+ else {
5882
+ const label = CODE_PAGE_LABELS[codePage] ?? (codePage >= 1250 && codePage <= 1258 ? `windows-${codePage}` : "utf-8");
5883
+ try {
5884
+ s = new TextDecoder(label).decode(bytes);
5885
+ } catch {
5886
+ s = bytes.toString("utf8");
5887
+ }
5888
+ }
5889
+ const nul = s.indexOf("\0");
5890
+ return (nul >= 0 ? s.slice(0, nul) : s).trim() || void 0;
5891
+ }
5892
+ function readPropFileTime(data, at) {
5893
+ if (data.readUInt32LE(at) !== VT_FILETIME || at + 12 > data.length) return void 0;
5894
+ const ticks = data.readBigUInt64LE(at + 4);
5895
+ if (ticks === 0n) return void 0;
5896
+ return new Date((Number(ticks / 10000000n) - 11644473600) * 1e3).toISOString().replace(".000Z", "Z");
5897
+ }
5505
5898
  function findViewTextSections(cfb, compressed) {
5506
5899
  const sections = [];
5507
5900
  for (let i = 0; i < MAX_SECTIONS; i++) {
@@ -17923,6 +18316,32 @@ function decodeHwp3Extra(ch) {
17923
18316
  case 12038:
17924
18317
  return 9632;
17925
18318
  // ■ hwp3-sample10 "제목차례" 좌우 장식 2건
18319
+ // SO-SUEOP ↔ 한컴 HWP3→HWPX 변환본 문맥 정렬 — 코드 개수와 변환본 글자 증가분이 전부 일치
18320
+ // (“ 13·” 14·‧ 4·《 3·》 3·— 1·∣ 6·↳ 18). 앞 셋은 항등, 0x3062~0x30BD 는 사적 문장부호
18321
+ case 8212:
18322
+ return 8212;
18323
+ // —
18324
+ case 8739:
18325
+ return 8739;
18326
+ // ∣
18327
+ case 8627:
18328
+ return 8627;
18329
+ // ↳
18330
+ case 12386:
18331
+ return 8220;
18332
+ // “
18333
+ case 12387:
18334
+ return 8221;
18335
+ // ”
18336
+ case 12390:
18337
+ return 8231;
18338
+ // ‧
18339
+ case 12475:
18340
+ return 12298;
18341
+ // 《
18342
+ case 12477:
18343
+ return 12299;
18344
+ // 》
17926
18345
  // 관인·서명란 도장 기호. rhwp 는 U+F012B 로 보존하고 렌더러가 `(인)` 으로 편다 —
17927
18346
  // kordoc 도 PUA 를 그대로 내보내면 shared/pua.ts 의 검증표가 같은 문자열로 편다.
17928
18347
  case 11214:
@@ -18074,16 +18493,78 @@ function decryptHwp3Document(input, password) {
18074
18493
  return out;
18075
18494
  }
18076
18495
 
18496
+ // src/hwp3/table.ts
18497
+ var NEIGHBOR_SLACK = 10;
18498
+ var MAX_NEIGHBOR_SCAN = 2048;
18499
+ var uniqSorted = (values) => [...new Set(values)].sort((a, b) => a - b);
18500
+ function edgeAt(edges, v) {
18501
+ let lo = 0, hi = edges.length - 1;
18502
+ while (lo < hi) {
18503
+ const mid = lo + hi >> 1;
18504
+ if (edges[mid] < v) lo = mid + 1;
18505
+ else hi = mid;
18506
+ }
18507
+ return lo;
18508
+ }
18509
+ function nextStarts(geo, axis) {
18510
+ const start = axis === "x" ? (g2) => g2.x : (g2) => g2.y;
18511
+ const end = axis === "x" ? (g2) => g2.r : (g2) => g2.b;
18512
+ const overlap = axis === "x" ? (a, b) => b.y < a.b && b.b > a.y : (a, b) => b.x < a.r && b.r > a.x;
18513
+ const order = geo.map((_, i) => i).sort((i, j) => start(geo[i]) - start(geo[j]));
18514
+ const starts = order.map((i) => start(geo[i]));
18515
+ return geo.map((a) => {
18516
+ let k = edgeAt(starts, Math.max(end(a) - NEIGHBOR_SLACK, start(a) + 1));
18517
+ for (let steps = 0; k < order.length && steps < MAX_NEIGHBOR_SCAN; k++, steps++) {
18518
+ const b = geo[order[k]];
18519
+ if (start(b) <= start(a)) continue;
18520
+ if (overlap(a, b)) return start(b);
18521
+ }
18522
+ return void 0;
18523
+ });
18524
+ }
18525
+ function hwp3CellGrid(info, count) {
18526
+ const geo = [];
18527
+ let right = 0, bottom = 0;
18528
+ for (let k = 0; k < count; k++) {
18529
+ const o = k * 27;
18530
+ const x = info.readUInt16LE(o + 4), y = info.readUInt16LE(o + 6);
18531
+ const g2 = { x, y, r: x + info.readUInt16LE(o + 8), b: y + info.readUInt16LE(o + 10) };
18532
+ geo.push(g2);
18533
+ if (g2.r > right) right = g2.r;
18534
+ if (g2.b > bottom) bottom = g2.b;
18535
+ }
18536
+ const xs = uniqSorted([...geo.map((g2) => g2.x), right]);
18537
+ const ys = uniqSorted([...geo.map((g2) => g2.y), bottom]);
18538
+ const nextX = nextStarts(geo, "x");
18539
+ const nextY = nextStarts(geo, "y");
18540
+ const cells = geo.map((g2, k) => {
18541
+ const col = edgeAt(xs, g2.x), row = edgeAt(ys, g2.y);
18542
+ const c2 = nextX[k] !== void 0 ? edgeAt(xs, nextX[k]) : xs.length - 1;
18543
+ const r2 = nextY[k] !== void 0 ? edgeAt(ys, nextY[k]) : ys.length - 1;
18544
+ return { row, col, rowSpan: Math.min(Math.max(1, r2 - row), MAX_ROWS), colSpan: Math.min(Math.max(1, c2 - col), MAX_COLS) };
18545
+ });
18546
+ return { rows: Math.min(Math.max(1, ys.length - 1), MAX_ROWS), cols: Math.min(Math.max(1, xs.length - 1), MAX_COLS), cells };
18547
+ }
18548
+
18077
18549
  // src/hwp3/parser.ts
18078
18550
  var MAX_DECOMPRESS_SIZE3 = 100 * 1024 * 1024;
18079
18551
  var PIC_TYPE_DRAWING = 3;
18552
+ var OBJ_EQUATION = 2;
18553
+ var OPT_HYPERTEXT = 16;
18554
+ var LEADER_TAB_MARK2 = "";
18555
+ var OUTLINE_FORMATS = [
18556
+ { fmt: "romanUpper", before: "", after: "." },
18557
+ { fmt: "digit", before: "", after: ")" },
18558
+ { fmt: "digit", before: "(", after: ")" },
18559
+ { fmt: "ganada", before: "", after: "." },
18560
+ { fmt: "ganada", before: "", after: ")" },
18561
+ { fmt: "ganada", before: "(", after: ")" },
18562
+ { fmt: "circled", before: "", after: "" }
18563
+ ];
18080
18564
  var PARA_SHAPE_SIZE = 187;
18081
18565
  var LINE_INFO_SIZE = 14;
18082
18566
  var INLINE_CHAR_SHAPE_SIZE = 31;
18083
18567
  var SIMPLE_CTRL = /* @__PURE__ */ new Map([
18084
- [9, { extraBytes: 6, extraHchar: 3, emit: " " }],
18085
- [18, { extraBytes: 6, extraHchar: 3, emit: " " }],
18086
- // AutoNumber → 공백 (HWP5 패턴)
18087
18568
  // 19/20/21 (새 번호·쪽 번호 위치·쪽 감추기) 는 비가시 마커다 — 한글도 그 자리에
18088
18569
  // 아무 글자를 내지 않는다 (rhwp #4957). 종전엔 U+FFFC 를 본문에 심어 마크다운에
18089
18570
  // 대체 문자가 그대로 남았다 (samples 33개 중 6문서 11건). 차례 표식(25) 과 같은 계약.
@@ -18096,8 +18577,7 @@ var SIMPLE_CTRL = /* @__PURE__ */ new Map([
18096
18577
  [25, { extraBytes: 4, extraHchar: 2, emit: null }],
18097
18578
  // 차례 표식 — 비가시 (rhwp #2765)
18098
18579
  [26, { extraBytes: 244, extraHchar: 122, emit: "\uFFFC" }],
18099
- [28, { extraBytes: 62, extraHchar: 31, emit: "\uFFFC" }],
18100
- [30, { extraBytes: 2, extraHchar: 1, emit: "\xA0" }],
18580
+ [30, { extraBytes: 2, extraHchar: 1, emit: " " }],
18101
18581
  [31, { extraBytes: 2, extraHchar: 1, emit: " " }]
18102
18582
  ]);
18103
18583
  function parseHwp3Document(buffer, options) {
@@ -18130,18 +18610,18 @@ function parseHwp3Document(buffer, options) {
18130
18610
  body = tail;
18131
18611
  }
18132
18612
  const bodyReader = new Reader(body);
18133
- const ctx = { paragraphs: [], warnings };
18613
+ const ctx = { warnings, headerBlocks: [], footerBlocks: [], headerTexts: /* @__PURE__ */ new Set(), pageNumbers: 0, noteSeq: [0, 0] };
18614
+ const bodyBlocks = [];
18134
18615
  try {
18135
18616
  skipFontFacesAndStyles(bodyReader);
18136
- parseParagraphList2(bodyReader, ctx);
18617
+ parseParagraphList2(bodyReader, ctx, bodyBlocks);
18137
18618
  } catch (err) {
18138
18619
  warnings.push({
18139
18620
  code: "PARTIAL_PARSE",
18140
18621
  message: `HWP3 paragraph stream \uB3C4\uC911 \uD30C\uC2F1 \uC911\uB2E8: ${err instanceof Error ? err.message : String(err)}`
18141
18622
  });
18142
18623
  }
18143
- const text = ctx.paragraphs.filter((p) => p.length > 0).join("\n\n");
18144
- const blocks = ctx.paragraphs.map((p) => ({ type: "paragraph", text: p }));
18624
+ const blocks = flattenLayoutTables([...ctx.headerBlocks, ...bodyBlocks, ...ctx.footerBlocks]);
18145
18625
  const metadata = {
18146
18626
  title: header.title || void 0,
18147
18627
  author: header.author || void 0,
@@ -18150,7 +18630,7 @@ function parseHwp3Document(buffer, options) {
18150
18630
  version: "3.0"
18151
18631
  };
18152
18632
  return {
18153
- markdown: text,
18633
+ markdown: blocksToMarkdown(blocks),
18154
18634
  blocks,
18155
18635
  metadata,
18156
18636
  warnings: warnings.length ? warnings : void 0
@@ -18165,7 +18645,7 @@ function skipFontFacesAndStyles(reader) {
18165
18645
  const nStyles = reader.readU16();
18166
18646
  reader.skip(nStyles * STYLE_RECORD_SIZE);
18167
18647
  }
18168
- function parseParagraphList2(reader, ctx) {
18648
+ function parseParagraphList2(reader, ctx, sink) {
18169
18649
  for (; ; ) {
18170
18650
  if (reader.eof()) return;
18171
18651
  const followPrev = reader.readU8();
@@ -18195,40 +18675,77 @@ function parseParagraphList2(reader, ctx) {
18195
18675
  if (flag !== 1) reader.skip(INLINE_CHAR_SHAPE_SIZE);
18196
18676
  }
18197
18677
  }
18678
+ const para2 = { text: "", objects: [], footnotes: [] };
18198
18679
  try {
18199
- const text = parseCharStream(reader, charCount, ctx);
18200
- ctx.paragraphs.push(text);
18680
+ parseCharStream(reader, charCount, ctx, para2);
18201
18681
  } catch (err) {
18682
+ sink.push(...emitHwp3Paragraph(para2));
18202
18683
  ctx.warnings.push({
18203
18684
  code: "PARTIAL_PARSE",
18204
- message: `HWP3 paragraph #${ctx.paragraphs.length} char stream \uD30C\uC2F1 \uC2E4\uD328: ${err instanceof Error ? err.message : String(err)}`
18685
+ message: `HWP3 paragraph char stream \uD30C\uC2F1 \uC2E4\uD328: ${err instanceof Error ? err.message : String(err)}`
18205
18686
  });
18206
18687
  return;
18207
18688
  }
18689
+ sink.push(...emitHwp3Paragraph(para2));
18208
18690
  }
18209
18691
  }
18210
- function parseCharStream(reader, charCount, ctx) {
18211
- let out = "";
18692
+ function emitHwp3Paragraph(para2) {
18693
+ let text = para2.text;
18694
+ const leaderAt = text.indexOf(LEADER_TAB_MARK2);
18695
+ if (leaderAt >= 0) text = text.slice(0, leaderAt);
18696
+ let headMarker = null;
18697
+ if (para2.outline) {
18698
+ const { level, numbers } = para2.outline;
18699
+ const f = OUTLINE_FORMATS[level];
18700
+ const n = numbers[level];
18701
+ if (f && n > 0) headMarker = f.before + formatNumber(n, f.fmt) + f.after;
18702
+ text = text.replace(/^ +/, "");
18703
+ }
18704
+ return emitParagraphBlocks({ text: text.trim(), headMarker, headingLevel: 0, footnotes: para2.footnotes, objects: para2.objects });
18705
+ }
18706
+ function parseCharStream(reader, charCount, ctx, para2) {
18212
18707
  let i = 0;
18213
18708
  while (i < charCount) {
18214
18709
  const ch = reader.readU16();
18215
18710
  i += 1;
18216
18711
  if (ch === 13) {
18217
- out += "\n";
18712
+ para2.text += "\n";
18218
18713
  continue;
18219
18714
  }
18220
18715
  if (ch === 0) {
18221
18716
  continue;
18222
18717
  }
18223
18718
  if (ch >= 32) {
18224
- out += decodeJohabText(ch) ?? "";
18719
+ para2.text += escapeLiteralDollar(decodeJohabText(ch) ?? "");
18720
+ continue;
18721
+ }
18722
+ if (ch === 9) {
18723
+ reader.skip(2);
18724
+ const leader = reader.readU16();
18725
+ reader.skip(2);
18726
+ i += 3;
18727
+ para2.text += leader ? LEADER_TAB_MARK2 : " ";
18728
+ continue;
18729
+ }
18730
+ if (ch === 18) {
18731
+ const info = reader.readBytes(6);
18732
+ i += 3;
18733
+ para2.text += autoNumberText(info.readUInt16LE(0), info.readUInt16LE(2), ctx);
18734
+ continue;
18735
+ }
18736
+ if (ch === 28) {
18737
+ const info = reader.readBytes(62);
18738
+ i += 31;
18739
+ if (info.readUInt16LE(0) === 1) {
18740
+ para2.outline = { level: Math.min(info[3], 6), numbers: Array.from({ length: 7 }, (_, k) => info.readUInt16LE(4 + k * 2)) };
18741
+ }
18225
18742
  continue;
18226
18743
  }
18227
18744
  const simple = SIMPLE_CTRL.get(ch);
18228
18745
  if (simple) {
18229
18746
  reader.skip(simple.extraBytes);
18230
18747
  i += simple.extraHchar;
18231
- if (simple.emit) out += simple.emit;
18748
+ if (simple.emit) para2.text += simple.emit;
18232
18749
  continue;
18233
18750
  }
18234
18751
  const headerVal1 = reader.readU32();
@@ -18236,27 +18753,31 @@ function parseCharStream(reader, charCount, ctx) {
18236
18753
  i += 3;
18237
18754
  switch (ch) {
18238
18755
  case 10:
18239
- out += parseTableLike(reader, ctx);
18756
+ parseTableLike(reader, ctx, para2);
18240
18757
  break;
18241
18758
  case 11:
18242
- parsePicture(reader, ctx);
18759
+ para2.objects.push({ blocks: parsePicture(reader, ctx), table: false, inline: false });
18243
18760
  break;
18244
18761
  case 14:
18245
18762
  reader.skip(84);
18246
18763
  break;
18247
18764
  case 15: {
18248
18765
  reader.skip(8);
18249
- parseParagraphList2(reader, ctx);
18766
+ const hidden = [];
18767
+ parseParagraphList2(reader, ctx, hidden);
18768
+ if (hidden.length > 0 && !ctx.warnings.some((w) => w.code === "HIDDEN_TEXT_FILTERED")) {
18769
+ ctx.warnings.push({ code: "HIDDEN_TEXT_FILTERED", message: "HWP3 \uC228\uC740 \uC124\uBA85 \uD14D\uC2A4\uD2B8 \uBCF8\uBB38 \uC81C\uC678" });
18770
+ }
18250
18771
  break;
18251
18772
  }
18252
18773
  case 16: {
18253
- reader.skip(10);
18254
- parseParagraphList2(reader, ctx);
18774
+ const info = reader.readBytes(10);
18775
+ parseHeaderFooter(reader, ctx, info[8] === 1);
18255
18776
  break;
18256
18777
  }
18257
18778
  case 17: {
18258
- reader.skip(14);
18259
- parseParagraphList2(reader, ctx);
18779
+ const info = reader.readBytes(14);
18780
+ parseNote(reader, ctx, para2, info.readUInt16LE(8), info.readUInt16LE(10) === 1 ? 1 : 0);
18260
18781
  break;
18261
18782
  }
18262
18783
  case 5:
@@ -18284,9 +18805,38 @@ function parseCharStream(reader, charCount, ctx) {
18284
18805
  break;
18285
18806
  }
18286
18807
  }
18287
- return out.trim();
18808
+ para2.text = para2.text.trim();
18288
18809
  }
18289
- function parseTableLike(reader, ctx) {
18810
+ function autoNumberText(type, num4, ctx) {
18811
+ if (type === 0) {
18812
+ ctx.pageNumbers++;
18813
+ return "";
18814
+ }
18815
+ if (num4 <= 0) return " ";
18816
+ return type === 1 || type === 2 ? `${num4})` : String(num4);
18817
+ }
18818
+ function parseNote(reader, ctx, para2, number, kind) {
18819
+ const blocks = [];
18820
+ parseParagraphList2(reader, ctx, blocks);
18821
+ const n = number > 0 ? number : ++ctx.noteSeq[kind];
18822
+ const marker = `${n})`;
18823
+ para2.text += marker;
18824
+ const content = blocksPlainText(blocks, " ");
18825
+ if (content) para2.footnotes.push(content.startsWith(marker) ? content : `${marker} ${content}`);
18826
+ }
18827
+ function parseHeaderFooter(reader, ctx, isFooter) {
18828
+ const pagesBefore = ctx.pageNumbers;
18829
+ const blocks = [];
18830
+ parseParagraphList2(reader, ctx, blocks);
18831
+ const text = blocksPlainText(blocks, "\n");
18832
+ if (!text) return;
18833
+ if (ctx.pageNumbers > pagesBefore && !/\p{L}/u.test(text)) return;
18834
+ const key = (isFooter ? "f:" : "h:") + text;
18835
+ if (ctx.headerTexts.has(key)) return;
18836
+ ctx.headerTexts.add(key);
18837
+ (isFooter ? ctx.footerBlocks : ctx.headerBlocks).push({ type: "paragraph", text });
18838
+ }
18839
+ function parseTableLike(reader, ctx, para2) {
18290
18840
  const info = reader.readBytes(84);
18291
18841
  const cellCount = info.readUInt16LE(80) || 1;
18292
18842
  if (27 * cellCount > reader.remaining()) {
@@ -18296,22 +18846,63 @@ function parseTableLike(reader, ctx) {
18296
18846
  });
18297
18847
  throw new Error(`HWP3 \uBE44\uC815\uC0C1 cell_count=${cellCount}`);
18298
18848
  }
18299
- reader.skip(27 * cellCount);
18300
- for (let i = 0; i < cellCount; i++) {
18301
- parseParagraphList2(reader, ctx);
18849
+ const cellInfo = reader.readBytes(27 * cellCount);
18850
+ const cellBlocks = [];
18851
+ try {
18852
+ for (let i = 0; i < cellCount; i++) {
18853
+ const blocks = [];
18854
+ cellBlocks.push(blocks);
18855
+ parseParagraphList2(reader, ctx, blocks);
18856
+ }
18857
+ } catch (err) {
18858
+ para2.objects.push({ blocks: cellBlocks.flat(), table: false, inline: false });
18859
+ throw err;
18860
+ }
18861
+ const captionBlocks = [];
18862
+ parseParagraphList2(reader, ctx, captionBlocks);
18863
+ const caption = blocksPlainText(captionBlocks, " ");
18864
+ if (info.readUInt16LE(78) === OBJ_EQUATION) {
18865
+ const script = (cellBlocks[0] ?? []).map((b) => b.text ?? "").join("\n").trim();
18866
+ const latex = script ? hwpEquationToLatex(script) : "";
18867
+ if (latex) para2.text += `$${latex.replace(/\$/g, "\\$")}$`;
18868
+ if (caption) para2.objects.push({ blocks: [{ type: "paragraph", text: caption }], table: false, inline: false });
18869
+ return;
18302
18870
  }
18303
- parseParagraphList2(reader, ctx);
18304
- return "";
18871
+ if (info.readUInt16LE(14) & OPT_HYPERTEXT) {
18872
+ para2.text += blocksPlainText(cellBlocks.flat(), " ");
18873
+ if (caption) para2.objects.push({ blocks: [{ type: "paragraph", text: caption }], table: false, inline: false });
18874
+ return;
18875
+ }
18876
+ const grid = hwp3CellGrid(cellInfo, cellCount);
18877
+ const cells = cellBlocks.map((blocks, k) => {
18878
+ const pos = grid.cells[k];
18879
+ const { text, hasStructure } = cellTextFromBlocks(blocks);
18880
+ const cell2 = { text, colSpan: pos.colSpan, rowSpan: pos.rowSpan, colAddr: pos.col, rowAddr: pos.row };
18881
+ if (hasStructure && blocks.length > 0) cell2.blocks = blocks;
18882
+ return cell2;
18883
+ });
18884
+ cells.sort((a, b) => a.rowAddr - b.rowAddr || a.colAddr - b.colAddr);
18885
+ const table2 = buildAddressedTable(cells, grid.rows, grid.cols);
18886
+ if (!table2) {
18887
+ para2.objects.push({ blocks: [...cellBlocks.flat(), ...captionBlocks], table: false, inline: false });
18888
+ return;
18889
+ }
18890
+ if (caption) table2.caption = caption;
18891
+ const inline = info[8] === 0;
18892
+ if (inline) para2.text += INLINE_TABLE_MARK;
18893
+ para2.objects.push({ blocks: [{ type: "table", table: table2 }], table: true, inline });
18305
18894
  }
18306
18895
  function parsePicture(reader, ctx) {
18307
18896
  const info = reader.readBytes(348);
18308
18897
  const nExt = info.readUInt32LE(0);
18309
18898
  let ext = null;
18310
18899
  if (nExt > 0 && nExt < 100 * 1024 * 1024) ext = reader.readBytes(nExt);
18311
- if (ext && info[74] === PIC_TYPE_DRAWING) parseDrawingObject(ext, ctx);
18312
- parseParagraphList2(reader, ctx);
18900
+ const blocks = [];
18901
+ if (ext && info[74] === PIC_TYPE_DRAWING) parseDrawingObject(ext, ctx, blocks);
18902
+ parseParagraphList2(reader, ctx, blocks);
18903
+ return blocks;
18313
18904
  }
18314
- function parseDrawingObject(ext, ctx) {
18905
+ function parseDrawingObject(ext, ctx, sink) {
18315
18906
  let lists;
18316
18907
  try {
18317
18908
  lists = collectDrawingTextBoxLists(ext);
@@ -18321,7 +18912,7 @@ function parseDrawingObject(ext, ctx) {
18321
18912
  }
18322
18913
  for (const list of lists) {
18323
18914
  try {
18324
- parseParagraphList2(new Reader(list), ctx);
18915
+ parseParagraphList2(new Reader(list), ctx, sink);
18325
18916
  } catch (err) {
18326
18917
  pushDrawingWarning(ctx, `HWP3 \uAE00\uC0C1\uC790 \uBB38\uB2E8 \uB9AC\uC2A4\uD2B8 \uD30C\uC2F1 \uC2E4\uD328: ${errText(err)}`);
18327
18918
  }
@@ -22118,7 +22709,9 @@ function scanClickHereRegions(xml) {
22118
22709
  placeholder: extractPlaceholder(fieldBeginXml),
22119
22710
  start,
22120
22711
  end,
22121
- prefix
22712
+ prefix,
22713
+ beginTagStart: m.index,
22714
+ beginTagEnd: m.index + m[0].length
22122
22715
  });
22123
22716
  }
22124
22717
  return regions;
@@ -22142,6 +22735,13 @@ function fillClickHereInXml(xml, cursor, blockedLabels) {
22142
22735
  const current = xml.slice(region.start, region.end);
22143
22736
  matchedKeys.add(key);
22144
22737
  filled.push({ label: region.name, value, row: -1, col: -1, key, source: "clickhere" });
22738
+ if (value !== "") {
22739
+ const tag = xml.slice(region.beginTagStart, region.beginTagEnd);
22740
+ if (attrOf2(tag, "dirty") !== "1") {
22741
+ const dirtyTag = /\sdirty="[^"]*"/.test(tag) ? tag.replace(/(\sdirty=")[^"]*"/, (_, head) => head + '1"') : tag.replace(/(\/?>)$/, ' dirty="1"$1');
22742
+ splices.push({ start: region.beginTagStart, end: region.beginTagEnd, replacement: dirtyTag });
22743
+ }
22744
+ }
22145
22745
  if (current === replacement) continue;
22146
22746
  splices.push({ start: region.start, end: region.end, replacement });
22147
22747
  }
@@ -24687,8 +25287,8 @@ function resolveLevels(levels, bodyHeight) {
24687
25287
  }
24688
25288
  return Object.keys(out).length > 0 ? out : null;
24689
25289
  }
24690
- function levelMarkerHeight(g, depth) {
24691
- return g.levels?.[depth]?.height ?? g.bodyHeight;
25290
+ function levelMarkerHeight(g2, depth) {
25291
+ return g2.levels?.[depth]?.height ?? g2.bodyHeight;
24692
25292
  }
24693
25293
  function incompatibleGongmunWarnings(opts) {
24694
25294
  const warns = [];
@@ -25112,8 +25712,8 @@ function parseMarkdownToBlocks(md2) {
25112
25712
  if (!l.trim() || /^\s*(`{3,}|~{3,})/.test(l)) break;
25113
25713
  const end = findMathDelim(l, 0);
25114
25714
  if (end >= 0) {
25115
- const before = l.slice(0, end);
25116
- if (before.trim()) mathLines.push(before);
25715
+ const before2 = l.slice(0, end);
25716
+ if (before2.trim()) mathLines.push(before2);
25117
25717
  trailing = l.slice(end + 2).trim();
25118
25718
  closed = true;
25119
25719
  j++;
@@ -25254,7 +25854,7 @@ function parseMarkdownToBlocks(md2) {
25254
25854
  }
25255
25855
  function parseInlineMarkdown(text) {
25256
25856
  const literals = [];
25257
- text = text.replace(/\x00/g, "").replace(/\\([\\`*_{}[\]()#+\-.!|>~])/g, (_, c) => {
25857
+ text = text.replace(/\x00/g, "").replace(/\\([\\`*_{}[\]()#+\-.!|<>~$])/g, (_, c) => {
25258
25858
  literals.push(c);
25259
25859
  return `\0${literals.length - 1}\0`;
25260
25860
  });
@@ -26209,16 +26809,16 @@ function table(rows, w, h, cols, tblBf = 1, bodyWidth = GAEJOSIK_BASE_WIDTH) {
26209
26809
  return `<hp:tbl id="${++gjTableId}" zOrder="0" numberingType="TABLE" textWrap="TOP_AND_BOTTOM" textFlow="BOTH_SIDES" lock="0" dropcapstyle="None" pageBreak="CELL" repeatHeader="0" rowCnt="${rows.length}" colCnt="${cols}" cellSpacing="0" borderFillIDRef="${tblBf}" noAdjust="1"><hp:sz width="${w}" widthRelTo="ABSOLUTE" height="${h}" heightRelTo="ABSOLUTE" protect="0"/><hp:pos treatAsChar="1" affectLSpacing="0" flowWithText="1" allowOverlap="0" holdAnchorAndSO="0" vertRelTo="PARA" horzRelTo="PARA" vertAlign="TOP" horzAlign="LEFT" vertOffset="0" horzOffset="0"/><hp:outMargin left="${outLR}" right="${outLR}" top="283" bottom="283"/><hp:inMargin left="141" right="141" top="141" bottom="141"/>` + rows.map((r) => `<hp:tr>${r}</hp:tr>`).join("") + `</hp:tbl>`;
26210
26810
  }
26211
26811
  function buildGaejosikCover(title, gongmun, bodyWidth = GAEJOSIK_BASE_WIDTH) {
26212
- const g = coverGeom(gongmun.bodyHeight, gongmun.sizes, bodyWidth);
26812
+ const g2 = coverGeom(gongmun.bodyHeight, gongmun.sizes, bodyWidth);
26213
26813
  const sz = gaejosikSizes(gongmun.bodyHeight, gongmun.sizes);
26214
- const innerW = g.totalW - 282;
26814
+ const innerW = g2.totalW - 282;
26215
26815
  const titleCharPr = simulateWrap(title, innerW, innerW, sz.coverTitle, 100, "keep").lines > 2 ? GJ_CHAR_COVER_SUB : GJ_CHAR_COVER_TITLE;
26216
26816
  const titlePara = `<hp:p paraPrIDRef="${GJ_PARA_COVER}" styleIDRef="0"><hp:run charPrIDRef="${titleCharPr}"><hp:t>${escapeTextXml(title)}</hp:t></hp:run></hp:p>`;
26217
26817
  const barEmpty = emptyPara(GJ_PARA_BAR, false, GJ_CHAR_BAR);
26218
- const topRow = cell({ bf: GJ_BF_BAR_DARK, col: 0, colSpan: 2, row: 0, w: g.topDarkW, h: g.barH, paras: barEmpty }) + cell({ bf: GJ_BF_BAR_LIGHT, col: 2, row: 0, w: g.topLightW, h: g.barH, paras: barEmpty });
26219
- const titleRow = cell({ bf: 1, col: 0, colSpan: 3, row: 1, w: g.totalW, h: g.titleH, paras: titlePara, name: "__kordoc_h1" });
26220
- const botRow = cell({ bf: GJ_BF_BAR_DARK, col: 0, row: 2, w: g.botDarkW, h: g.botBarH, paras: barEmpty }) + cell({ bf: GJ_BF_BAR_LIGHT, col: 1, colSpan: 2, row: 2, w: g.botLightW, h: g.botBarH, paras: barEmpty });
26221
- const tbl2 = table([topRow, titleRow, botRow], g.totalW, g.barH + g.titleH + g.botBarH, 3, 1, bodyWidth);
26818
+ const topRow = cell({ bf: GJ_BF_BAR_DARK, col: 0, colSpan: 2, row: 0, w: g2.topDarkW, h: g2.barH, paras: barEmpty }) + cell({ bf: GJ_BF_BAR_LIGHT, col: 2, row: 0, w: g2.topLightW, h: g2.barH, paras: barEmpty });
26819
+ const titleRow = cell({ bf: 1, col: 0, colSpan: 3, row: 1, w: g2.totalW, h: g2.titleH, paras: titlePara, name: "__kordoc_h1" });
26820
+ const botRow = cell({ bf: GJ_BF_BAR_DARK, col: 0, row: 2, w: g2.botDarkW, h: g2.botBarH, paras: barEmpty }) + cell({ bf: GJ_BF_BAR_LIGHT, col: 1, colSpan: 2, row: 2, w: g2.botLightW, h: g2.botBarH, paras: barEmpty });
26821
+ const tbl2 = table([topRow, titleRow, botRow], g2.totalW, g2.barH + g2.titleH + g2.botBarH, 3, 1, bodyWidth);
26222
26822
  const host2 = `<hp:p paraPrIDRef="${PARA_NORMAL}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_NORMAL}">${tbl2}</hp:run></hp:p>`;
26223
26823
  const date = gongmun.cover?.date ?? formatGaejosikDate(/* @__PURE__ */ new Date());
26224
26824
  const org = gongmun.cover?.org ?? "";
@@ -26233,14 +26833,14 @@ function buildGaejosikCover(title, gongmun, bodyWidth = GAEJOSIK_BASE_WIDTH) {
26233
26833
  ];
26234
26834
  }
26235
26835
  function buildTocBanner(gongmun, bodyWidth = GAEJOSIK_BASE_WIDTH) {
26236
- const g = tocBannerGeom(gongmun.bodyHeight, gongmun.sizes);
26836
+ const g2 = tocBannerGeom(gongmun.bodyHeight, gongmun.sizes);
26237
26837
  const bfs = [GJ_BF_BAR_DARK, 1, GJ_BF_TOC_STRIPE, 1, GJ_BF_TOC_STRIPE, 1, GJ_BF_BAR_DARK];
26238
26838
  const filler = emptyPara(GJ_PARA_BAR, false, GJ_CHAR_BAR);
26239
26839
  const label = `<hp:p paraPrIDRef="${GONGMUN_CENTER}" styleIDRef="0"><hp:run charPrIDRef="${GJ_CHAR_TOC_LABEL}"><hp:t>\uBAA9 \uCC28</hp:t></hp:run></hp:p>`;
26240
- const row = g.colWs.map(
26241
- (w, c) => cell({ bf: bfs[c], col: c, w, h: g.h, paras: c === 3 ? label : filler, name: c === 3 ? "__kordoc_toc" : void 0 })
26840
+ const row = g2.colWs.map(
26841
+ (w, c) => cell({ bf: bfs[c], col: c, w, h: g2.h, paras: c === 3 ? label : filler, name: c === 3 ? "__kordoc_toc" : void 0 })
26242
26842
  ).join("");
26243
- const banner = table([row], g.colWs.reduce((a, b) => a + b, 0), g.h, 7, 2, bodyWidth);
26843
+ const banner = table([row], g2.colWs.reduce((a, b) => a + b, 0), g2.h, 7, 2, bodyWidth);
26244
26844
  return `<hp:p paraPrIDRef="${GJ_PARA_BAR}" styleIDRef="0" pageBreak="1"><hp:run charPrIDRef="${CHAR_NORMAL}">${banner}</hp:run></hp:p>`;
26245
26845
  }
26246
26846
  function buildGaejosikToc(chapters, gongmun, bodyWidth = GAEJOSIK_BASE_WIDTH) {
@@ -26261,22 +26861,22 @@ function buildGaejosikToc(chapters, gongmun, bodyWidth = GAEJOSIK_BASE_WIDTH) {
26261
26861
  return [label, emptyPara(GJ_PARA_BAR, false, GJ_CHAR_BAR), host2];
26262
26862
  }
26263
26863
  function buildGaejosikBodyTitle(title, gongmun, bodyWidth = GAEJOSIK_BASE_WIDTH) {
26264
- const g = coverGeom(gongmun.bodyHeight, gongmun.sizes, bodyWidth);
26864
+ const g2 = coverGeom(gongmun.bodyHeight, gongmun.sizes, bodyWidth);
26265
26865
  const bt = bodyTitleGeom(gongmun.bodyHeight, gongmun.sizes);
26266
26866
  const barEmpty = emptyPara(GJ_PARA_BAR, false, GJ_CHAR_BAR);
26267
26867
  const titlePara = `<hp:p paraPrIDRef="${GONGMUN_CENTER}" styleIDRef="0"><hp:run charPrIDRef="${GJ_CHAR_BODY_TITLE}"><hp:t>${escapeTextXml(title)}</hp:t></hp:run></hp:p>`;
26268
- const topRow = cell({ bf: GJ_BF_BAR_DARK, col: 0, colSpan: 2, row: 0, w: g.topDarkW, h: bt.barH, paras: barEmpty }) + cell({ bf: GJ_BF_BAR_LIGHT, col: 2, row: 0, w: g.topLightW, h: bt.barH, paras: barEmpty });
26269
- const titleRow = cell({ bf: 1, col: 0, colSpan: 3, row: 1, w: g.totalW, h: bt.titleH, paras: titlePara, name: "__kordoc_skip" });
26270
- const botRow = cell({ bf: GJ_BF_BAR_DARK, col: 0, row: 2, w: g.botDarkW, h: bt.barH, paras: barEmpty }) + cell({ bf: GJ_BF_BAR_LIGHT, col: 1, colSpan: 2, row: 2, w: g.botLightW, h: bt.barH, paras: barEmpty });
26271
- const box = table([topRow, titleRow, botRow], g.totalW, bt.barH * 2 + bt.titleH, 3, 1, bodyWidth);
26868
+ const topRow = cell({ bf: GJ_BF_BAR_DARK, col: 0, colSpan: 2, row: 0, w: g2.topDarkW, h: bt.barH, paras: barEmpty }) + cell({ bf: GJ_BF_BAR_LIGHT, col: 2, row: 0, w: g2.topLightW, h: bt.barH, paras: barEmpty });
26869
+ const titleRow = cell({ bf: 1, col: 0, colSpan: 3, row: 1, w: g2.totalW, h: bt.titleH, paras: titlePara, name: "__kordoc_skip" });
26870
+ const botRow = cell({ bf: GJ_BF_BAR_DARK, col: 0, row: 2, w: g2.botDarkW, h: bt.barH, paras: barEmpty }) + cell({ bf: GJ_BF_BAR_LIGHT, col: 1, colSpan: 2, row: 2, w: g2.botLightW, h: bt.barH, paras: barEmpty });
26871
+ const box = table([topRow, titleRow, botRow], g2.totalW, bt.barH * 2 + bt.titleH, 3, 1, bodyWidth);
26272
26872
  return `<hp:p paraPrIDRef="${PARA_NORMAL}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_NORMAL}">${box}</hp:run></hp:p>`;
26273
26873
  }
26274
26874
  function buildGaejosikChapter(n, title, gongmun, bodyWidth = GAEJOSIK_BASE_WIDTH, srcLevel = 2) {
26275
- const g = gongmun ? chapterGeom(gongmun.bodyHeight, gongmun.sizes, bodyWidth) : chapterGeom(1500, {}, bodyWidth);
26875
+ const g2 = gongmun ? chapterGeom(gongmun.bodyHeight, gongmun.sizes, bodyWidth) : chapterGeom(1500, {}, bodyWidth);
26276
26876
  const numPara = `<hp:p paraPrIDRef="${GONGMUN_CENTER}" styleIDRef="0"><hp:run charPrIDRef="${GJ_CHAR_CHAPTER_NUM}"><hp:t>${chapterRoman(n)}</hp:t></hp:run></hp:p>`;
26277
26877
  const titlePara = `<hp:p paraPrIDRef="${PARA_NORMAL}" styleIDRef="0"><hp:run charPrIDRef="${GJ_CHAR_CHAPTER_TITLE}"><hp:t> ${escapeTextXml(title)}</hp:t></hp:run></hp:p>`;
26278
- const row = cell({ bf: GJ_BF_CHAPTER_NUM, col: 0, w: g.numW, h: g.rowH, paras: numPara }) + cell({ bf: GJ_BF_CHAPTER_GAP, col: 1, w: g.gapW, h: g.rowH }) + cell({ bf: GJ_BF_CHAPTER_TITLE, col: 2, w: g.titleW, h: g.rowH, paras: titlePara, name: `__kordoc_h${Math.min(Math.max(srcLevel, 1), 6)}` });
26279
- const tbl2 = table([row], g.numW + g.gapW + g.titleW, g.rowH, 3, 1, bodyWidth);
26878
+ const row = cell({ bf: GJ_BF_CHAPTER_NUM, col: 0, w: g2.numW, h: g2.rowH, paras: numPara }) + cell({ bf: GJ_BF_CHAPTER_GAP, col: 1, w: g2.gapW, h: g2.rowH }) + cell({ bf: GJ_BF_CHAPTER_TITLE, col: 2, w: g2.titleW, h: g2.rowH, paras: titlePara, name: `__kordoc_h${Math.min(Math.max(srcLevel, 1), 6)}` });
26879
+ const tbl2 = table([row], g2.numW + g2.gapW + g2.titleW, g2.rowH, 3, 1, bodyWidth);
26280
26880
  return `<hp:p paraPrIDRef="${GJ_PARA_CHAPTER}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_NORMAL}">${tbl2}</hp:run></hp:p>`;
26281
26881
  }
26282
26882
 
@@ -26299,15 +26899,15 @@ function approxDistance(a, b) {
26299
26899
  const bigramCounts = (s) => {
26300
26900
  const m = /* @__PURE__ */ new Map();
26301
26901
  for (let i = 0; i < s.length - 1; i++) {
26302
- const g = s.slice(i, i + 2);
26303
- m.set(g, (m.get(g) ?? 0) + 1);
26902
+ const g2 = s.slice(i, i + 2);
26903
+ m.set(g2, (m.get(g2) ?? 0) + 1);
26304
26904
  }
26305
26905
  return m;
26306
26906
  };
26307
26907
  const ca = bigramCounts(a);
26308
26908
  const cb = bigramCounts(b);
26309
26909
  let inter = 0;
26310
- for (const [g, n] of ca) inter += Math.min(n, cb.get(g) ?? 0);
26910
+ for (const [g2, n] of ca) inter += Math.min(n, cb.get(g2) ?? 0);
26311
26911
  const total = Math.max(a.length - 1, 0) + Math.max(b.length - 1, 0);
26312
26912
  const dice = total > 0 ? 2 * inter / total : 1;
26313
26913
  return Math.round(Math.max(a.length, b.length) * (1 - dice));
@@ -26465,16 +27065,6 @@ function bestSimInRange(arr, from, to, target) {
26465
27065
  }
26466
27066
  return best;
26467
27067
  }
26468
- function escapeGfm(text) {
26469
- const NUL = String.fromCharCode(0);
26470
- const spans = [];
26471
- const masked = text.replace(/!\[[^\]]*\]\([^)\n]*\)|\]\((?:https?:|mailto:|tel:|#)[^)\n]*\)/gi, (m) => {
26472
- spans.push(m);
26473
- return NUL + (spans.length - 1) + NUL;
26474
- });
26475
- const escaped = masked.replace(/([~*_`])/g, "\\$1");
26476
- return escaped.replace(new RegExp(NUL + "(\\d+)" + NUL, "g"), (_, n) => spans[Number(n)]);
26477
- }
26478
27068
  var HWP_SHAPE_ALT_TEXT_RE = /^(?:모서리가 둥근 |둥근 )?(?:사각형|직사각형|정사각형|원|타원|삼각형|이등변 삼각형|직각 삼각형|선|직선|곡선|화살표|굵은 화살표|이중 화살표|오각형|육각형|팔각형|별|[4-8]점별|십자|십자형|구름|구름형|마름모|도넛|평행사변형|사다리꼴|부채꼴|호|반원|물결|번개|하트|빗금|블록 화살표|수식|표|그림|개체|그리기\s?개체|묶음\s?개체|글상자|수식\s?개체|OLE\s?개체)\s?입니다\.?$/gm;
26479
27069
  function sanitizeText(text) {
26480
27070
  let result = mapPuaText(text).replace(/[\u{F0000}-\u{FFFFD}]/gu, "").replace(HWP_SHAPE_ALT_TEXT_RE, "").replace(/ +/g, " ").trim();
@@ -26488,10 +27078,10 @@ function sanitizeText(text) {
26488
27078
  return result;
26489
27079
  }
26490
27080
  function normForMatch(text) {
26491
- return sanitizeText(text).replace(/\s+/g, " ").trim();
27081
+ return sanitizeText(text.replace(/\\\$/g, "$")).replace(/\s+/g, " ").trim();
26492
27082
  }
26493
27083
  function unescapeGfm(text) {
26494
- return text.replace(/\\([~*_`])/g, "$1");
27084
+ return text.replace(/\\([~*_`|#<$\\])/g, "$1");
26495
27085
  }
26496
27086
  function summarize(text) {
26497
27087
  const t = text.replace(/\s+/g, " ").trim();
@@ -26510,7 +27100,7 @@ function replicateGfmTable(table2) {
26510
27100
  const cell2 = cells[r]?.[c];
26511
27101
  if (!cell2) continue;
26512
27102
  display[r][c] = {
26513
- text: escapeGfm(sanitizeText(cell2.text)).replace(/\|/g, "\\|").replace(/\n/g, "<br>"),
27103
+ text: escapeGfm(sanitizeText(cell2.text)).replace(/(?<!\\)\|/g, "\\|").replace(/\n/g, "<br>"),
26514
27104
  gridR: r,
26515
27105
  gridC: c
26516
27106
  };
@@ -26558,7 +27148,7 @@ function parseGfmTable(lines) {
26558
27148
  return rows;
26559
27149
  }
26560
27150
  function unescapeGfmCell(text) {
26561
- return text.replace(/<br\s*\/?>/gi, "\n").replace(/\\\|/g, "|").replace(/\\([~*_`])/g, "$1");
27151
+ return text.replace(/(?<!\\)<br\s*\/?>/gi, "\n").replace(/\\\|/g, "|").replace(/\\([~*_`#<$\\])/g, "$1");
26562
27152
  }
26563
27153
  function replicateCellInnerHtml(cell2) {
26564
27154
  if (cell2.blocks?.length) {
@@ -26569,7 +27159,7 @@ function replicateCellInnerHtml(cell2) {
26569
27159
  }
26570
27160
  if (b.type === "image" && b.text) return `<img src="${b.text}" alt="image">`;
26571
27161
  const t = sanitizeText(b.text ?? "");
26572
- return t ? t.replace(/\n/g, "<br>") : "";
27162
+ return t ? (t + noteSuffix(b)).replace(/\n/g, "<br>") : "";
26573
27163
  }).filter(Boolean).join("<br>");
26574
27164
  }
26575
27165
  return sanitizeText(cell2.text).replace(/\n/g, "<br>");
@@ -26586,7 +27176,7 @@ function replicateTableToHtml(table2) {
26586
27176
  const attrStr = attrs.length ? " " + attrs.join(" ") : "";
26587
27177
  return `<${tag}${attrStr}>${cell2.inner}</${tag}>`;
26588
27178
  });
26589
- if (rowHtml.length) lines.push(`<tr>${rowHtml.join("")}</tr>`);
27179
+ lines.push(`<tr>${rowHtml.join("")}</tr>`);
26590
27180
  }
26591
27181
  lines.push("</table>");
26592
27182
  return lines.join("\n");
@@ -26616,7 +27206,7 @@ function replicateHtmlTable(table2) {
26616
27206
  gridC: c
26617
27207
  });
26618
27208
  }
26619
- if (rowCells.length) result.push({ tag, cells: rowCells });
27209
+ result.push({ tag, cells: rowCells });
26620
27210
  }
26621
27211
  return result;
26622
27212
  }
@@ -27663,8 +28253,8 @@ function buildTitleBox(title, titleCharPr, barCharPr, bodyWidth, reg) {
27663
28253
  function docframeIds(base) {
27664
28254
  return { org: base, sender: base + 1, foot: base + 2, small: base + 3, pressLabel: base + 4, pressHead: base + 5, pressTitle: base + 6, pressSub: base + 7, pressContact: base + 8 };
27665
28255
  }
27666
- function docframeActive(g) {
27667
- return !!(g.docHead || g.docFoot || g.reportInfo || g.noticeHead || g.press);
28256
+ function docframeActive(g2) {
28257
+ return !!(g2.docHead || g2.docFoot || g2.reportInfo || g2.noticeHead || g2.press);
27668
28258
  }
27669
28259
  function docframeCharPrXmls(base, richAssets) {
27670
28260
  const body = richAssets ? 4 : 0;
@@ -27687,15 +28277,15 @@ function blank() {
27687
28277
  function isInternalApproval(to) {
27688
28278
  return !!to && /^\s*내\s*부\s*결\s*재/.test(to);
27689
28279
  }
27690
- function buildReportInfo(g, ids) {
27691
- return para(g.reportInfo, GONGMUN_RIGHT, ids.small);
28280
+ function buildReportInfo(g2, ids) {
28281
+ return para(g2.reportInfo, GONGMUN_RIGHT, ids.small);
27692
28282
  }
27693
- function buildNoticeHead(g) {
27694
- const n = g.noticeHead;
28283
+ function buildNoticeHead(g2) {
28284
+ const n = g2.noticeHead;
27695
28285
  return n.no ? [para(n.no, PARA_NORMAL, CHAR_BOLD), blank()] : [];
27696
28286
  }
27697
- function buildNoticeFoot(g) {
27698
- const n = g.noticeHead;
28287
+ function buildNoticeFoot(g2) {
28288
+ const n = g2.noticeHead;
27699
28289
  const out = [];
27700
28290
  if (n.date) out.push(blank(), para(n.date, GONGMUN_RIGHT, CHAR_NORMAL));
27701
28291
  if (n.sender) out.push(blank(), para(n.sender, GONGMUN_RIGHT, CHAR_BOLD));
@@ -27703,8 +28293,8 @@ function buildNoticeFoot(g) {
27703
28293
  }
27704
28294
  var PRESS_LABEL_H = 3200;
27705
28295
  var PRESS_HEAD_H = 900;
27706
- function buildPressHead(g, ids, bodyWidth, reg) {
27707
- const p = g.press;
28296
+ function buildPressHead(g2, ids, bodyWidth, reg) {
28297
+ const p = g2.press;
27708
28298
  const w = bodyWidth - 280;
27709
28299
  const thick = { t: "thick", b: "thick", l: "thick", r: "thick" };
27710
28300
  const rows = [tc({ bf: reg.get({ ...thick, b: "thin" }), row: 0, col: 0, w, h: PRESS_LABEL_H, paras: para("\uBCF4\uB3C4\uC790\uB8CC", GONGMUN_TBL_CENTER, ids.pressLabel) })];
@@ -27716,8 +28306,8 @@ function buildPressHead(g, ids, bodyWidth, reg) {
27716
28306
  const table2 = tbl(rows, w, PRESS_LABEL_H + (headLine ? PRESS_HEAD_H : 0), 1);
27717
28307
  return [`<hp:p paraPrIDRef="${PARA_NORMAL}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_NORMAL}">${table2}</hp:run></hp:p>`, blank()];
27718
28308
  }
27719
- function buildPressContact(g, ids, bodyWidth, reg) {
27720
- const c = g.press?.contact;
28309
+ function buildPressContact(g2, ids, bodyWidth, reg) {
28310
+ const c = g2.press?.contact;
27721
28311
  if (!c || !c.dept && !c.manager && !c.phone) return [];
27722
28312
  const w = bodyWidth - 280;
27723
28313
  const cells = [["\uB2F4\uB2F9 \uBD80\uC11C", c.dept ?? ""], ["\uB2F4\uB2F9\uC790", c.manager ?? ""], ["\uC5F0\uB77D\uCC98", c.phone ?? ""]];
@@ -28518,19 +29108,19 @@ function blocksToSectionXml(blocks, theme, gongmun, gongmunList = gongmun ? prec
28518
29108
  }
28519
29109
 
28520
29110
  // src/hwpx/gen-levels.ts
28521
- function levelDepths(g) {
28522
- return g.levels ? Object.keys(g.levels).map(Number).sort((a, b) => a - b) : [];
29111
+ function levelDepths(g2) {
29112
+ return g2.levels ? Object.keys(g2.levels).map(Number).sort((a, b) => a - b) : [];
28523
29113
  }
28524
- function levelFontFaces(g) {
29114
+ function levelFontFaces(g2) {
28525
29115
  const out = [];
28526
- for (const d of levelDepths(g)) {
28527
- const f = g.levels[d].font;
29116
+ for (const d of levelDepths(g2)) {
29117
+ const f = g2.levels[d].font;
28528
29118
  if (f && !out.includes(f)) out.push(f);
28529
29119
  }
28530
29120
  return out;
28531
29121
  }
28532
- function levelCharIds(g, base) {
28533
- const depths = levelDepths(g);
29122
+ function levelCharIds(g2, base) {
29123
+ const depths = levelDepths(g2);
28534
29124
  if (depths.length === 0) return null;
28535
29125
  const ids = {};
28536
29126
  depths.forEach((d, i) => {
@@ -28538,13 +29128,13 @@ function levelCharIds(g, base) {
28538
29128
  });
28539
29129
  return ids;
28540
29130
  }
28541
- function levelCharPrXmls(g, base, fontBase, bodyFontId) {
28542
- const ids = levelCharIds(g, base);
29131
+ function levelCharPrXmls(g2, base, fontBase, bodyFontId) {
29132
+ const ids = levelCharIds(g2, base);
28543
29133
  if (!ids) return [];
28544
- const faces = levelFontFaces(g);
29134
+ const faces = levelFontFaces(g2);
28545
29135
  const out = [];
28546
- for (const d of levelDepths(g)) {
28547
- const st = g.levels[d];
29136
+ for (const d of levelDepths(g2)) {
29137
+ const st = g2.levels[d];
28548
29138
  const fontId = st.font ? fontBase + faces.indexOf(st.font) : bodyFontId;
28549
29139
  out.push(charPr(ids[d].normal, st.height, st.bold, false, fontId, void 0, GONGMUN_BODY_RATIO, fontId));
28550
29140
  out.push(charPr(ids[d].bold, st.height, true, false, fontId, void 0, GONGMUN_BODY_RATIO, fontId));
@@ -28738,25 +29328,25 @@ function seoulLegalScheme(bodyFont = "\uAD74\uB9BC\uCCB4", bodyPt = 12, lineSp =
28738
29328
  blankBetweenTop: false
28739
29329
  };
28740
29330
  }
28741
- function applySchemeOverrides(s, g) {
29331
+ function applySchemeOverrides(s, g2) {
28742
29332
  const out = { ...s, levels: s.levels.map((l) => ({ ...l })), body: { ...s.body }, ref: { ...s.ref }, table: { ...s.table } };
28743
- if (g.fonts.heading) {
28744
- out.levels[0].font = g.fonts.heading;
28745
- out.chapter = { ...out.chapter, font: g.fonts.heading };
28746
- }
28747
- if (g.fonts.body) {
28748
- out.body.font = g.fonts.body;
28749
- if (s.kind === "legal") for (const l of out.levels) l.font = g.fonts.body;
28750
- else out.levels[1].font = g.fonts.body;
28751
- }
28752
- if (g.fonts.ref) out.ref.font = g.fonts.ref;
28753
- if (g.fonts.table) out.table.font = g.fonts.table;
28754
- if (g.levels) {
28755
- for (const [k, st] of Object.entries(g.levels)) {
29333
+ if (g2.fonts.heading) {
29334
+ out.levels[0].font = g2.fonts.heading;
29335
+ out.chapter = { ...out.chapter, font: g2.fonts.heading };
29336
+ }
29337
+ if (g2.fonts.body) {
29338
+ out.body.font = g2.fonts.body;
29339
+ if (s.kind === "legal") for (const l of out.levels) l.font = g2.fonts.body;
29340
+ else out.levels[1].font = g2.fonts.body;
29341
+ }
29342
+ if (g2.fonts.ref) out.ref.font = g2.fonts.ref;
29343
+ if (g2.fonts.table) out.table.font = g2.fonts.table;
29344
+ if (g2.levels) {
29345
+ for (const [k, st] of Object.entries(g2.levels)) {
28756
29346
  const l = out.levels[Number(k)];
28757
29347
  if (!l) continue;
28758
29348
  if (st.font) l.font = st.font;
28759
- if (st.height !== g.bodyHeight) l.pt = st.height / 100;
29349
+ if (st.height !== g2.bodyHeight) l.pt = st.height / 100;
28760
29350
  l.bold = st.bold;
28761
29351
  }
28762
29352
  }
@@ -28765,13 +29355,13 @@ function applySchemeOverrides(s, g) {
28765
29355
  function taHu(pt2) {
28766
29356
  return Math.round(pt2 * 50);
28767
29357
  }
28768
- function pickScheme(g, bodyHasBoxMarkers) {
28769
- if (g.preset === "ministry") {
28770
- return applySchemeOverrides(ministryScheme(g.bodyPtExplicit ? g.bodyHeight / 100 : 15, g.lineSpacingExplicit ? g.lineSpacing : 145), g);
29358
+ function pickScheme(g2, bodyHasBoxMarkers) {
29359
+ if (g2.preset === "ministry") {
29360
+ return applySchemeOverrides(ministryScheme(g2.bodyPtExplicit ? g2.bodyHeight / 100 : 15, g2.lineSpacingExplicit ? g2.lineSpacing : 145), g2);
28771
29361
  }
28772
- const gaejosik = g.numbering !== "standard" || bodyHasBoxMarkers;
28773
- const base = gaejosik ? seoulGaejosikScheme(g.bodyPtExplicit ? g.bodyHeight / 100 : 15, g.lineSpacingExplicit ? g.lineSpacing : 180, g.bullet2) : seoulLegalScheme(g.fonts.body ?? (g.bodyFontExplicit ? g.bodyFont === "gothic" ? "\uB9D1\uC740 \uACE0\uB515" : "\uD568\uCD08\uB86C\uBC14\uD0D5" : "\uAD74\uB9BC\uCCB4"), g.bodyPtExplicit ? g.bodyHeight / 100 : 12, g.lineSpacingExplicit ? g.lineSpacing : 160);
28774
- return applySchemeOverrides(base, g);
29362
+ const gaejosik = g2.numbering !== "standard" || bodyHasBoxMarkers;
29363
+ const base = gaejosik ? seoulGaejosikScheme(g2.bodyPtExplicit ? g2.bodyHeight / 100 : 15, g2.lineSpacingExplicit ? g2.lineSpacing : 180, g2.bullet2) : seoulLegalScheme(g2.fonts.body ?? (g2.bodyFontExplicit ? g2.bodyFont === "gothic" ? "\uB9D1\uC740 \uACE0\uB515" : "\uD568\uCD08\uB86C\uBC14\uD0D5" : "\uAD74\uB9BC\uCCB4"), g2.bodyPtExplicit ? g2.bodyHeight / 100 : 12, g2.lineSpacingExplicit ? g2.lineSpacing : 160);
29364
+ return applySchemeOverrides(base, g2);
28775
29365
  }
28776
29366
 
28777
29367
  // src/hwpx/outline.ts
@@ -29104,7 +29694,7 @@ function neighbor(s, i, dir) {
29104
29694
  for (let k = i + dir; k >= 0 && k < s.length; k += dir) if (!/[*_~]/.test(s[k])) return s[k];
29105
29695
  return "";
29106
29696
  }
29107
- function curlQuotes(s, before = "") {
29697
+ function curlQuotes(s, before2 = "") {
29108
29698
  let out = "";
29109
29699
  for (let i = 0; i < s.length; i++) {
29110
29700
  const c = s[i];
@@ -29112,7 +29702,7 @@ function curlQuotes(s, before = "") {
29112
29702
  out += c;
29113
29703
  continue;
29114
29704
  }
29115
- const prev = neighbor(s, i, -1) || before;
29705
+ const prev = neighbor(s, i, -1) || before2;
29116
29706
  const opening = prev === "" || OPENER_BEFORE.test(prev);
29117
29707
  if (c === "'") {
29118
29708
  if (opening && /^\d{2}(?![\d'])/.test(s.slice(i + 1))) {
@@ -29333,7 +29923,7 @@ function buildReportTitleTable(title, contact, ctx) {
29333
29923
  return { xml: host(ftbl(rows, w, h, 1, { bottomGap: 600 }), reg.para({ align: "CENTER", lineSp: 100 }), cTitle), overflow: fit.overflow };
29334
29924
  }
29335
29925
  var CHAPTER_BAND_DEFAULT = { color: "#003366", textColor: "#FFFFFF", numFont: "HY\uD5E4\uB4DC\uB77C\uC778M", numPt: 17 };
29336
- function buildChapterBand(roman, title, ctx, spec, before) {
29926
+ function buildChapterBand(roman, title, ctx, spec, before2) {
29337
29927
  const { reg, bf } = ctx;
29338
29928
  const w = ctx.W;
29339
29929
  const numW = 3100, gap = 400, h = 2600;
@@ -29347,7 +29937,7 @@ function buildChapterBand(roman, title, ctx, spec, before) {
29347
29937
  const center = reg.para({ align: "CENTER", lineSp: 130 });
29348
29938
  const left = reg.para({ align: "LEFT", lineSp: 130, left: 300 });
29349
29939
  const row = tc({ bf: numBf, row: 0, col: 0, w: numW, h, paras: para(roman, center, cNum), vAlign: "CENTER" }) + tc({ bf: gapBf, row: 0, col: 1, w: gap, h, paras: para("", center, cNum), vAlign: "CENTER" }) + tc({ bf: titleBf, row: 0, col: 2, w: titleW, h, paras: para(title, left, cTitle), name: "__kordoc_h2", vAlign: "CENTER" });
29350
- return { xml: host(ftbl([row], w, h, 3, { bottomGap: 1e3 }), reg.para({ align: "LEFT", lineSp: 100, before, keepWithNext: true }), cTitle), overflow: fit.overflow };
29940
+ return { xml: host(ftbl([row], w, h, 3, { bottomGap: 1e3 }), reg.para({ align: "LEFT", lineSp: 100, before: before2, keepWithNext: true }), cTitle), overflow: fit.overflow };
29351
29941
  }
29352
29942
  var SUMMARY_PAD = 1e3;
29353
29943
  function buildSummaryBox(text, ctx) {
@@ -29552,7 +30142,7 @@ function romanOf(n) {
29552
30142
  }
29553
30143
  return s;
29554
30144
  }
29555
- function buildMinistryChapterBand(roman, title, ctx, before) {
30145
+ function buildMinistryChapterBand(roman, title, ctx, before2) {
29556
30146
  const { reg, bf } = ctx;
29557
30147
  const c = MINISTRY.chapter;
29558
30148
  const w = ctx.W;
@@ -29562,9 +30152,9 @@ function buildMinistryChapterBand(roman, title, ctx, before) {
29562
30152
  const cT = reg.char({ font: MINISTRY.headFont, pt: fit.pt, ratio: fit.ratio, spacing: fit.spacing });
29563
30153
  const p = reg.para({ align: "LEFT", lineSp: 130, left: 500 });
29564
30154
  const cell2 = tc({ bf: band, row: 0, col: 0, w, h: c.h, paras: para(text, p, cT), name: "__kordoc_h2" });
29565
- return { xml: host(ftbl([cell2], w, c.h, 1, { bottomGap: 800 }), reg.para({ align: "LEFT", lineSp: 100, before, keepWithNext: true }), cT), overflow: fit.overflow };
30155
+ return { xml: host(ftbl([cell2], w, c.h, 1, { bottomGap: 800 }), reg.para({ align: "LEFT", lineSp: 100, before: before2, keepWithNext: true }), cT), overflow: fit.overflow };
29566
30156
  }
29567
- function buildMinistrySectionBand(n, title, ctx, before) {
30157
+ function buildMinistrySectionBand(n, title, ctx, before2) {
29568
30158
  const { reg, bf } = ctx;
29569
30159
  const s = MINISTRY.section;
29570
30160
  const t = plain(title);
@@ -29576,9 +30166,9 @@ function buildMinistrySectionBand(n, title, ctx, before) {
29576
30166
  const center = reg.para({ align: "CENTER", lineSp: 130 });
29577
30167
  const left = reg.para({ align: "LEFT", lineSp: 130, left: 300 });
29578
30168
  const row = tc({ bf: numBf, row: 0, col: 0, w: s.numW, h: s.h, paras: para(String(n), center, cNum) }) + tc({ bf: titleBf, row: 0, col: 1, w: titleW, h: s.h, paras: para(t, left, cT), name: "__kordoc_h3" });
29579
- return host(ftbl([row], s.numW + titleW, s.h, 2, { bottomGap: 900 }), reg.para({ align: "LEFT", lineSp: 100, before, keepWithNext: true }), cT);
30169
+ return host(ftbl([row], s.numW + titleW, s.h, 2, { bottomGap: 900 }), reg.para({ align: "LEFT", lineSp: 100, before: before2, keepWithNext: true }), cT);
29580
30170
  }
29581
- function buildMinistrySubheadBox(n, title, ctx, before) {
30171
+ function buildMinistrySubheadBox(n, title, ctx, before2) {
29582
30172
  const { reg, bf } = ctx;
29583
30173
  const s = MINISTRY.subhead;
29584
30174
  const text = `${n}. ${plain(title)}`;
@@ -29587,9 +30177,9 @@ function buildMinistrySubheadBox(n, title, ctx, before) {
29587
30177
  const cT = reg.char({ font: MINISTRY.headFont, pt: s.pt, color: "#FFFFFF" });
29588
30178
  const p = reg.para({ align: "LEFT", lineSp: 130, left: 400 });
29589
30179
  const cell2 = tc({ bf: box, row: 0, col: 0, w, h: s.h, paras: para(text, p, cT), name: "__kordoc_h4" });
29590
- return host(ftbl([cell2], w, s.h, 1, { bottomGap: 900 }), reg.para({ align: "LEFT", lineSp: 100, before, keepWithNext: true }), cT);
30180
+ return host(ftbl([cell2], w, s.h, 1, { bottomGap: 900 }), reg.para({ align: "LEFT", lineSp: 100, before: before2, keepWithNext: true }), cT);
29591
30181
  }
29592
- function buildMinistryItemBand(n, text, ctx, before) {
30182
+ function buildMinistryItemBand(n, text, ctx, before2) {
29593
30183
  const { reg, bf } = ctx;
29594
30184
  const s = MINISTRY.item;
29595
30185
  const w = ctx.W;
@@ -29600,7 +30190,7 @@ function buildMinistryItemBand(n, text, ctx, before) {
29600
30190
  const runs = ministryRuns(circledNumber(n - 1), text, ctx, styled, MINISTRY.blue, MINISTRY.blue);
29601
30191
  const p = reg.para({ align: "LEFT", lineSp: 130, left: 300 });
29602
30192
  const cell2 = tc({ bf: band, row: 0, col: 0, w, h: s.h, paras: `<hp:p paraPrIDRef="${p}" styleIDRef="0">${runs}</hp:p>`, name: "__kordoc_h5" });
29603
- return host(ftbl([cell2], w, s.h, 1, { bottomGap: 700 }), reg.para({ align: "LEFT", lineSp: 100, before, keepWithNext: true }), reg.char(base));
30193
+ return host(ftbl([cell2], w, s.h, 1, { bottomGap: 700 }), reg.para({ align: "LEFT", lineSp: 100, before: before2, keepWithNext: true }), reg.char(base));
29604
30194
  }
29605
30195
  function buildMinistrySummaryBox(text, ctx) {
29606
30196
  const { reg, bf } = ctx;
@@ -29628,7 +30218,7 @@ function buildMinistrySummaryBox(text, ctx) {
29628
30218
  const cell2 = tc({ bf: box, row: 0, col: 0, w, h, paras: paras.join(""), vAlign: "TOP", name: "__kordoc_summary" });
29629
30219
  return host(ftbl([cell2], w, h, 1, { bottomGap: 900 }), reg.para({ align: "CENTER", lineSp: 100, before: 400 }), reg.char(baseItem));
29630
30220
  }
29631
- function buildMinistryAttachBand(title, ctx, before) {
30221
+ function buildMinistryAttachBand(title, ctx, before2) {
29632
30222
  const { reg, bf } = ctx;
29633
30223
  const a = MINISTRY.attach;
29634
30224
  const w = ctx.W;
@@ -29642,7 +30232,7 @@ function buildMinistryAttachBand(title, ctx, before) {
29642
30232
  const cT = reg.char({ font: MINISTRY.headFont, pt: fit.pt, ratio: fit.ratio, spacing: fit.spacing });
29643
30233
  const center = reg.para({ align: "CENTER", lineSp: 130 });
29644
30234
  const row = tc({ bf: labelBf, row: 0, col: 0, w: a.labelW, h: a.h, paras: para("\uBCC4 \uCCA8", center, cLabel) }) + tc({ bf: gapBf, row: 0, col: 1, w: a.gap, h: a.h, paras: para("", center, cT) }) + tc({ bf: titleBf, row: 0, col: 2, w: titleW, h: a.h, paras: para(t, center, cT), name: "__kordoc_h2" });
29645
- return host(ftbl([row], w, a.h, 3, { bottomGap: 900 }), reg.para({ align: "LEFT", lineSp: 100, before, keepWithNext: true }), cT);
30235
+ return host(ftbl([row], w, a.h, 3, { bottomGap: 900 }), reg.para({ align: "LEFT", lineSp: 100, before: before2, keepWithNext: true }), cT);
29646
30236
  }
29647
30237
  function isAttachHeading(text) {
29648
30238
  return /^\[?\s*별\s*첨\s*\]?/u.test(text.trim());
@@ -29665,13 +30255,13 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
29665
30255
  resetFrameTableIds();
29666
30256
  const { reg, bfReg, remap, images, page } = deps;
29667
30257
  const warnings = [];
29668
- const g = gongmun;
29669
- const preset = g.preset;
30258
+ const g2 = gongmun;
30259
+ const preset = g2.preset;
29670
30260
  const isReport = preset === "report" || preset === "plan";
29671
30261
  const isMinistry = preset === "ministry";
29672
- const W = mmToHwpunit(210 - g.margins.left - g.margins.right);
30262
+ const W = mmToHwpunit(210 - g2.margins.left - g2.margins.right);
29673
30263
  const pre = buildOutline(blocks, isMinistry ? { gaejosik: true, consumeTitle: true, summaryFromQuote: true, quoteBox: true, headingFrames: true, keepMarkers: true } : { gaejosik: true, consumeTitle: true, summaryFromQuote: isReport });
29674
- const scheme = pickScheme(g, pre.hasBoxMarkers);
30264
+ const scheme = pickScheme(g2, pre.hasBoxMarkers);
29675
30265
  const gaejosik = scheme.kind === "gaejosik";
29676
30266
  const raw = gaejosik ? pre : buildOutline(blocks, { gaejosik: false, consumeTitle: true, summaryFromQuote: false });
29677
30267
  const outline = { ...raw, title: raw.title && polishGongmunText(raw.title), nodes: raw.nodes.map(polishNode) };
@@ -29702,48 +30292,48 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
29702
30292
  if (box.lines > 3) warnings.push(`\uC694\uC57D\uBC15\uC2A4\uAC00 ${box.lines}\uC904\uC785\uB2C8\uB2E4 \u2014 \uBCF4\uACE0 \uBAA9\uC801\uC744 \uD55C \uBB38\uC7A5(\uC27C\uD45C \uD5C8\uC6A9) 3\uC904 \uC774\uB0B4 "\u2026\uD558\uACE0\uC790 \uD568"\uC73C\uB85C \uC904\uC774\uC138\uC694`);
29703
30293
  paras.push(box.xml);
29704
30294
  };
29705
- const docTitle = polishGongmunText(g.docHead?.title ?? outline.title ?? "");
29706
- const reportInfo = g.reportInfo && polishGongmunText(g.reportInfo);
30295
+ const docTitle = polishGongmunText(g2.docHead?.title ?? outline.title ?? "");
30296
+ const reportInfo = g2.reportInfo && polishGongmunText(g2.reportInfo);
29707
30297
  if (preset === "official") {
29708
- if (g.docHead) paras.push(buildDocHeadTable({ ...g.docHead, title: docTitle }, frame));
30298
+ if (g2.docHead) paras.push(buildDocHeadTable({ ...g2.docHead, title: docTitle }, frame));
29709
30299
  else if (outline.title) paras.push(generateParagraph(outline.title, reg.para({ align: "CENTER", lineSp: scheme.lineSp, after: lineHu(scheme.body) }), reg.char({ font: scheme.body.font, pt: scheme.body.pt + 2, bold: true }), void 0, 1));
29710
30300
  if (reportInfo) paras.push(generateParagraph(reportInfo, reg.para({ align: "RIGHT", lineSp: scheme.lineSp }), reg.char({ font: scheme.body.font, pt: 12 })));
29711
- if (g.approval) paras.push(buildApprovalSeoul(g.approval, null, frame));
30301
+ if (g2.approval) paras.push(buildApprovalSeoul(g2.approval, null, frame));
29712
30302
  } else if (isMinistry) {
29713
- if (g.cover) {
29714
- const cover = buildMinistryCover({ title: docTitle, date: g.cover.date ?? formatGaejosikDate(/* @__PURE__ */ new Date()), org: g.cover.org || void 0, label: g.cover.label }, frame);
29715
- if (g.pageNumbers) cover[0] = cover[0].replace(/<hp:run charPrIDRef="(\d+)">/, `<hp:run charPrIDRef="$1">${pageHidingCtrl()}`);
30303
+ if (g2.cover) {
30304
+ const cover = buildMinistryCover({ title: docTitle, date: g2.cover.date ?? formatGaejosikDate(/* @__PURE__ */ new Date()), org: g2.cover.org || void 0, label: g2.cover.label }, frame);
30305
+ if (g2.pageNumbers) cover[0] = cover[0].replace(/<hp:run charPrIDRef="(\d+)">/, `<hp:run charPrIDRef="$1">${pageHidingCtrl()}`);
29716
30306
  paras.push(...cover);
29717
30307
  pendingPageBreak = true;
29718
30308
  }
29719
- if (g.toc && outline.chapters > 0) {
30309
+ if (g2.toc && outline.chapters > 0) {
29720
30310
  const tocChapters = [];
29721
30311
  for (const n of outline.nodes) {
29722
30312
  if (n.kind === "chapter") tocChapters.push({ title: plain2(n.text), subs: [], attach: isAttachHeading(n.text) });
29723
30313
  else if (n.kind === "heading" && n.level === 3 && tocChapters.length) tocChapters[tocChapters.length - 1].subs.push(plain2(n.text));
29724
30314
  }
29725
30315
  const toc = buildMinistryToc(tocChapters, frame);
29726
- if (g.pageNumbers) toc[0] = toc[0].replace(/<hp:run charPrIDRef="(\d+)">/, `<hp:run charPrIDRef="$1">${pageHidingCtrl()}`);
30316
+ if (g2.pageNumbers) toc[0] = toc[0].replace(/<hp:run charPrIDRef="(\d+)">/, `<hp:run charPrIDRef="$1">${pageHidingCtrl()}`);
29727
30317
  paras.push(...toc);
29728
30318
  pendingPageBreak = true;
29729
30319
  }
29730
- if (!g.cover && !g.toc && docTitle) {
30320
+ if (!g2.cover && !g2.toc && docTitle) {
29731
30321
  paras.push(generateParagraph(docTitle, reg.para({ align: "CENTER", lineSp: scheme.lineSp, after: lineHu(scheme.body) }), reg.char({ font: scheme.frame.titleFont, pt: 20 }), void 0, 1));
29732
30322
  frontKind = "title";
29733
30323
  }
29734
30324
  } else if (isReport) {
29735
- if (g.cover) {
30325
+ if (g2.cover) {
29736
30326
  paras.push(...buildReportCover({
29737
30327
  title: docTitle,
29738
- date: g.cover.date ?? formatGaejosikDate(/* @__PURE__ */ new Date()).replace(/\s\d+\.$/, "."),
29739
- org: g.cover.org || void 0,
29740
- dept: g.cover.dept,
29741
- docInfo: g.docInfo ?? void 0,
29742
- approval: g.approval ?? void 0
30328
+ date: g2.cover.date ?? formatGaejosikDate(/* @__PURE__ */ new Date()).replace(/\s\d+\.$/, "."),
30329
+ org: g2.cover.org || void 0,
30330
+ dept: g2.cover.dept,
30331
+ docInfo: g2.docInfo ?? void 0,
30332
+ approval: g2.approval ?? void 0
29743
30333
  }, frame));
29744
30334
  pendingPageBreak = true;
29745
- } else if (g.approval) {
29746
- paras.push(buildApprovalSeoul(g.approval, null, frame));
30335
+ } else if (g2.approval) {
30336
+ paras.push(buildApprovalSeoul(g2.approval, null, frame));
29747
30337
  }
29748
30338
  if (docTitle) {
29749
30339
  const t = buildReportTitleTable(docTitle, reportInfo, frame);
@@ -29755,16 +30345,16 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
29755
30345
  frontKind = "title";
29756
30346
  }
29757
30347
  if (!docTitle && reportInfo) paras.push(generateParagraph(reportInfo, reg.para({ align: "RIGHT", lineSp: scheme.lineSp }), reg.char({ font: scheme.body.font, pt: 12 })));
29758
- if (g.summary) pushSummary(g.summary);
30348
+ if (g2.summary) pushSummary(g2.summary);
29759
30349
  else if (!outline.nodes.some((n) => n.kind === "summary")) warnings.push('\uBCF4\uACE0\uC11C \uC694\uC57D\uBC15\uC2A4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uC81C\uBAA9 \uC9C1\uD6C4 \uC778\uC6A9\uBB38(>)\uC5D0 \uBCF4\uACE0 \uBAA9\uC801\uC744 \uD55C \uBB38\uC7A5(\uC27C\uD45C \uD5C8\uC6A9) 3\uC904 \uC774\uB0B4 "\u2026\uD558\uACE0\uC790 \uD568"\uC73C\uB85C \uB123\uAC70\uB098 summary \uC635\uC158\uC744 \uC9C0\uC815\uD558\uC138\uC694');
29760
30350
  } else {
29761
- if (g.noticeHead) paras.push(...buildNoticeHead(g));
29762
- if (g.approval) paras.push(buildApprovalSeoul(g.approval, null, frame));
30351
+ if (g2.noticeHead) paras.push(...buildNoticeHead(g2));
30352
+ if (g2.approval) paras.push(buildApprovalSeoul(g2.approval, null, frame));
29763
30353
  if (outline.title) paras.push(generateParagraph(outline.title, reg.para({ align: "CENTER", lineSp: scheme.lineSp, before: 400, after: lineHu(scheme.body) }), reg.char({ font: scheme.frame.titleFont, pt: 20, bold: true }), void 0, 1));
29764
30354
  }
29765
- const numberer = new GongmunNumberer("standard", g.bullet2);
30355
+ const numberer = new GongmunNumberer("standard", g2.bullet2);
29766
30356
  const itemDepths = outline.nodes.filter((n) => n.kind === "item" && !n.legalMarker).map((n) => n.depth);
29767
- const suppress = !gaejosik && g.suppressSingle ? computeSuppression(itemDepths) : null;
30357
+ const suppress = !gaejosik && g2.suppressSingle ? computeSuppression(itemDepths) : null;
29768
30358
  let itemSeq = 0;
29769
30359
  const BOX_BLANK_HU = 1e3;
29770
30360
  const BOX_GAP_AFTER_BAND = 1200;
@@ -29774,8 +30364,8 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
29774
30364
  let nextNode;
29775
30365
  let tableSeq = 0;
29776
30366
  let sectionSeq = 0, subheadSeq = 0, itemBandSeq = 0;
29777
- let pendingNewPageNum = isMinistry && (!!g.cover || !!g.toc);
29778
- let chapterStyle = g.h2Marker === "box" || g.h2Marker === "number" || g.h2Marker === "none" || g.h2Marker === "band" ? g.h2Marker : "roman";
30367
+ let pendingNewPageNum = isMinistry && (!!g2.cover || !!g2.toc);
30368
+ let chapterStyle = g2.h2Marker === "box" || g2.h2Marker === "number" || g2.h2Marker === "none" || g2.h2Marker === "band" ? g2.h2Marker : "roman";
29779
30369
  if (!gaejosik) chapterStyle = "number";
29780
30370
  const leadLeft = (depth, pt2) => (scheme.levels[Math.min(depth, 7)]?.leadTa ?? 0) * taHu(pt2);
29781
30371
  const renderItem = (node, styleId = 0) => {
@@ -29806,8 +30396,8 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
29806
30396
  }
29807
30397
  } else {
29808
30398
  const t = plain2(node.text);
29809
- const cb = fitCharBreaks(t, st.font, st.pt, textW2, textW2, g.autoFitMinRatio ?? 101);
29810
- const f = cb ?? (g.autoFitMinRatio !== null ? fitParagraph(t, st.font, st.pt, textW2, textW2, g.autoFitMinRatio) : null);
30399
+ const cb = fitCharBreaks(t, st.font, st.pt, textW2, textW2, g2.autoFitMinRatio ?? 101);
30400
+ const f = cb ?? (g2.autoFitMinRatio !== null ? fitParagraph(t, st.font, st.pt, textW2, textW2, g2.autoFitMinRatio) : null);
29811
30401
  if (f) {
29812
30402
  ratio = f.ratio;
29813
30403
  spacing = f.spacing;
@@ -29817,9 +30407,9 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
29817
30407
  const base = { font: st.font, pt: st.pt, bold: st.bold, ratio, spacing };
29818
30408
  const consecutiveBox = prevKind === "item" && prevItemDepth === 0 && depth === 0;
29819
30409
  const afterBand = depth === 0 && (prevKind === "chapter" && chapterStyle === "band" || prevKind === "title" && !!st.blankBefore);
29820
- const before = afterBand ? BOX_GAP_AFTER_BAND : st.blankBefore && !consecutiveBox && prevKind !== "chapter" && prevKind !== "start" && prevKind !== "summary" ? BOX_BLANK_HU : 0;
30410
+ const before2 = afterBand ? BOX_GAP_AFTER_BAND : st.blankBefore && !consecutiveBox && prevKind !== "chapter" && prevKind !== "start" && prevKind !== "summary" ? BOX_BLANK_HU : 0;
29821
30411
  const keepNext = !!st.keepWithNext && !!nextNode && (nextNode.kind === "item" && nextNode.depth > depth || nextNode.kind === "ref");
29822
- const paraSpec = { align: "JUSTIFY", left: lay.left, indent: -lay.hang, before, lineSp: st.lineSp ?? scheme.lineSp, keepWithNext: keepNext, keepWord: !charBreaks, autoTab: lay.hang > 0, widowOrphan: true };
30412
+ const paraSpec = { align: "JUSTIFY", left: lay.left, indent: -lay.hang, before: before2, lineSp: st.lineSp ?? scheme.lineSp, keepWithNext: keepNext, keepWord: !charBreaks, autoTab: lay.hang > 0, widowOrphan: true };
29823
30413
  const markerRun = mk ? markerRunXml(mk, reg.char({ font: st.font, pt: st.pt, bold: st.bold }), lay) : "";
29824
30414
  if (isMinistry) {
29825
30415
  const paraId2 = reg.para({ ...paraSpec, after: 600 });
@@ -29836,8 +30426,8 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
29836
30426
  const left = isMinistry ? st.leadTa * taHu(st.pt) : leadLeft(node.depth, st.pt);
29837
30427
  const textW2 = W - left - lay.hang;
29838
30428
  const t = plain2(node.text);
29839
- const cb = fitCharBreaks(t, st.font, st.pt, textW2, textW2, g.autoFitMinRatio ?? 101);
29840
- const f = cb ?? (g.autoFitMinRatio !== null ? fitParagraph(t, st.font, st.pt, textW2, textW2, g.autoFitMinRatio) : null);
30429
+ const cb = fitCharBreaks(t, st.font, st.pt, textW2, textW2, g2.autoFitMinRatio ?? 101);
30430
+ const f = cb ?? (g2.autoFitMinRatio !== null ? fitParagraph(t, st.font, st.pt, textW2, textW2, g2.autoFitMinRatio) : null);
29841
30431
  const base = { font: st.font, pt: st.pt, bold: st.bold, ratio: f?.ratio ?? 100, spacing: f?.spacing ?? 0 };
29842
30432
  const paraId = reg.para({ align: "JUSTIFY", left, indent: -lay.hang, after: isMinistry ? 400 : 0, lineSp: isMinistry ? 130 : st.lineSp ?? scheme.lineSp, keepWord: !cb, autoTab: true, widowOrphan: true });
29843
30433
  const markerRun = markerRunXml(marker, reg.char({ font: st.font, pt: st.pt, bold: st.bold }), lay);
@@ -29858,8 +30448,8 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
29858
30448
  let ratio = 100, spacing = 0, charBreaks = false;
29859
30449
  if (!node.align) {
29860
30450
  const t = plain2(node.text);
29861
- const cb = fitCharBreaks(t, st.font, st.pt, W, W, g.autoFitMinRatio ?? 101);
29862
- const f = cb ?? (g.autoFitMinRatio !== null ? fitParagraph(t, st.font, st.pt, W, W, g.autoFitMinRatio) : null);
30451
+ const cb = fitCharBreaks(t, st.font, st.pt, W, W, g2.autoFitMinRatio ?? 101);
30452
+ const f = cb ?? (g2.autoFitMinRatio !== null ? fitParagraph(t, st.font, st.pt, W, W, g2.autoFitMinRatio) : null);
29863
30453
  if (f) {
29864
30454
  ratio = f.ratio;
29865
30455
  spacing = f.spacing;
@@ -29885,8 +30475,8 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
29885
30475
  return first ? xml : xml.replace(/^<hp:p /, `<hp:p pageBreak="1" `);
29886
30476
  }
29887
30477
  if (chapterStyle === "band") {
29888
- const before2 = prevKind === "start" || prevKind === "title" ? 0 : prevKind === "summary" ? BOX_GAP_AFTER_BAND : BAND_BEFORE_HU;
29889
- const band = buildChapterBand(chapterLabel(node.index, "roman").replace(/\.$/, ""), plain2(node.text), frame, { ...CHAPTER_BAND_DEFAULT, color: g.bandColor, textColor: g.bandTextColor, titleFont: st.font, titlePt: st.pt }, before2);
30478
+ const before3 = prevKind === "start" || prevKind === "title" ? 0 : prevKind === "summary" ? BOX_GAP_AFTER_BAND : BAND_BEFORE_HU;
30479
+ const band = buildChapterBand(chapterLabel(node.index, "roman").replace(/\.$/, ""), plain2(node.text), frame, { ...CHAPTER_BAND_DEFAULT, color: g2.bandColor, textColor: g2.bandTextColor, titleFont: st.font, titlePt: st.pt }, before3);
29890
30480
  if (band.overflow) warnings.push(`\uC7A5 \uC81C\uBAA9\uC774 \uB760 \uD45C \uD55C \uC904\uC5D0 \uB2F4\uAE30\uC9C0 \uC54A\uC544 \uCD95\uC18C\uD588\uC2B5\uB2C8\uB2E4 \u2014 \uC81C\uBAA9\uC744 \uC904\uC774\uC138\uC694: "${node.text.slice(0, 30)}\u2026"`);
29891
30481
  return band.xml;
29892
30482
  }
@@ -29894,8 +30484,8 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
29894
30484
  const text = label ? `${label} ${node.text}` : node.text;
29895
30485
  const fit = fitOneLine(plain2(text), st.font, st.pt, W, st.pt - 2);
29896
30486
  const base = { font: st.font, pt: fit.pt, bold: st.bold, ratio: fit.ratio, spacing: fit.spacing };
29897
- const before = prevKind === "start" || prevKind === "title" || prevKind === "summary" ? 0 : lineHu(scheme.body);
29898
- const paraId = reg.para({ align: "LEFT", before, after: Math.round(lineHu(scheme.body) * 0.3), lineSp: st.lineSp ?? scheme.lineSp, keepWithNext: true });
30487
+ const before2 = prevKind === "start" || prevKind === "title" || prevKind === "summary" ? 0 : lineHu(scheme.body);
30488
+ const paraId = reg.para({ align: "LEFT", before: before2, after: Math.round(lineHu(scheme.body) * 0.3), lineSp: st.lineSp ?? scheme.lineSp, keepWithNext: true });
29899
30489
  return generateParagraph(text, paraId, reg.char(base), inlineMapper(reg, base), 2);
29900
30490
  };
29901
30491
  const renderAttach = (node, first) => {
@@ -29964,7 +30554,7 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
29964
30554
  xml = buildMinistrySummaryBox(node.text, frame);
29965
30555
  break;
29966
30556
  }
29967
- if (isReport && !g.summary) {
30557
+ if (isReport && !g2.summary) {
29968
30558
  pushSummary(node.text);
29969
30559
  prevKind = node.kind;
29970
30560
  return;
@@ -29972,15 +30562,15 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
29972
30562
  xml = renderRef({ kind: "ref", depth: 0, text: node.text });
29973
30563
  break;
29974
30564
  case "heading": {
29975
- const before = prevKind === "chapter" || prevKind === "heading" || prevKind === "start" ? 0 : BAND_BEFORE_HU;
30565
+ const before2 = prevKind === "chapter" || prevKind === "heading" || prevKind === "start" ? 0 : BAND_BEFORE_HU;
29976
30566
  if (node.level <= 3) {
29977
30567
  subheadSeq = 0;
29978
30568
  itemBandSeq = 0;
29979
- xml = buildMinistrySectionBand(++sectionSeq, node.text, frame, before);
30569
+ xml = buildMinistrySectionBand(++sectionSeq, node.text, frame, before2);
29980
30570
  } else if (node.level === 4) {
29981
30571
  itemBandSeq = 0;
29982
- xml = buildMinistrySubheadBox(++subheadSeq, node.text, frame, before);
29983
- } else xml = buildMinistryItemBand(++itemBandSeq, node.text, frame, before);
30572
+ xml = buildMinistrySubheadBox(++subheadSeq, node.text, frame, before2);
30573
+ } else xml = buildMinistryItemBand(++itemBandSeq, node.text, frame, before2);
29984
30574
  lastItemFromHeading = true;
29985
30575
  lastTextNode = node;
29986
30576
  break;
@@ -30034,16 +30624,16 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
30034
30624
  if (node.kind === "item") prevItemDepth = node.depth;
30035
30625
  else if (node.kind === "chapter" && gaejosik && chapterStyle === "box") prevItemDepth = 0;
30036
30626
  });
30037
- if (g.noticeHead) paras.push(...buildNoticeFoot(g));
30038
- if (g.endMark && paras.length > 0) {
30627
+ if (g2.noticeHead) paras.push(...buildNoticeFoot(g2));
30628
+ if (g2.endMark && paras.length > 0) {
30039
30629
  const lastText = lastTextNode && (lastTextNode.kind === "item" || lastTextNode.kind === "para" || lastTextNode.kind === "attach" || lastTextNode.kind === "ref") ? lastTextNode.text : "";
30040
30630
  if (!hasEndMark(lastText)) {
30041
30631
  const st = scheme.body;
30042
30632
  paras.push(generateParagraph(" \uB05D.", reg.para({ align: "LEFT", lineSp: scheme.lineSp }), reg.char({ font: st.font, pt: st.pt })));
30043
30633
  }
30044
30634
  }
30045
- if (preset === "official" && g.docFoot) {
30046
- const f = g.docFoot;
30635
+ if (preset === "official" && g2.docFoot) {
30636
+ const f = g2.docFoot;
30047
30637
  const approvers = (f.approvers ?? [f.drafter, f.reviewer, f.approver].filter((x) => !!x)).map(splitTitleName);
30048
30638
  const cooperators = (f.cooperator ? f.cooperator.split(/[,、·]/).map((s) => s.trim()).filter(Boolean) : []).map(splitTitleName);
30049
30639
  paras.push(buildDocFootTable({
@@ -30060,10 +30650,10 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
30060
30650
  fax: f.fax,
30061
30651
  email: f.email,
30062
30652
  disclosure: f.disclosure
30063
- }, frame, isInternalApproval(g.docHead?.to)));
30653
+ }, frame, isInternalApproval(g2.docHead?.to)));
30064
30654
  }
30065
30655
  if (paras.length === 0) paras.push(`<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="0"><hp:t></hp:t></hp:run></hp:p>`);
30066
- paras[0] = paras[0].replace(/<hp:run charPrIDRef="(\d+)">/, `<hp:run charPrIDRef="$1">${generateSecPr(g, page)}`);
30656
+ paras[0] = paras[0].replace(/<hp:run charPrIDRef="(\d+)">/, `<hp:run charPrIDRef="$1">${generateSecPr(g2, page)}`);
30067
30657
  return {
30068
30658
  xml: `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
30069
30659
  <hs:sec xmlns:hs="${NS_SECTION}" xmlns:hp="${NS_PARA}">
@@ -30484,11 +31074,11 @@ function findDeep(el, name, depth = 0) {
30484
31074
  return null;
30485
31075
  }
30486
31076
  function parseParaGeom(el) {
30487
- const g = { ...DEFAULT_PARA_GEOM };
31077
+ const g2 = { ...DEFAULT_PARA_GEOM };
30488
31078
  const ls = findDeep(el, "lineSpacing");
30489
31079
  if (ls) {
30490
- g.lineSpacingType = ls.getAttribute("type") ?? "PERCENT";
30491
- g.lineSpacingValue = Number(ls.getAttribute("value")) || 160;
31080
+ g2.lineSpacingType = ls.getAttribute("type") ?? "PERCENT";
31081
+ g2.lineSpacingValue = Number(ls.getAttribute("value")) || 160;
30492
31082
  }
30493
31083
  const margin = findDeep(el, "margin");
30494
31084
  if (margin) {
@@ -30496,19 +31086,19 @@ function parseParaGeom(el) {
30496
31086
  const c = findDeep(margin, n);
30497
31087
  return c ? Number(c.getAttribute("value")) || 0 : 0;
30498
31088
  };
30499
- g.marginLeft = v("left");
30500
- g.marginRight = v("right");
30501
- g.marginIntent = v("intent");
30502
- g.spaceBefore = v("prev");
30503
- g.spaceAfter = v("next");
31089
+ g2.marginLeft = v("left");
31090
+ g2.marginRight = v("right");
31091
+ g2.marginIntent = v("intent");
31092
+ g2.spaceBefore = v("prev");
31093
+ g2.spaceAfter = v("next");
30504
31094
  }
30505
31095
  const bs = findDeep(el, "breakSetting");
30506
31096
  if (bs) {
30507
31097
  const nl = bs.getAttribute("breakNonLatinWord");
30508
- if (nl === "BREAK_WORD") g.wrapMode = "keep";
30509
- else if (nl === "KEEP_WORD") g.wrapMode = "charAll";
31098
+ if (nl === "BREAK_WORD") g2.wrapMode = "keep";
31099
+ else if (nl === "KEEP_WORD") g2.wrapMode = "charAll";
30510
31100
  }
30511
- return g;
31101
+ return g2;
30512
31102
  }
30513
31103
  function parseRenderStyles(headXml) {
30514
31104
  const styles = { charPr: /* @__PURE__ */ new Map(), paraAlign: /* @__PURE__ */ new Map(), paraGeom: /* @__PURE__ */ new Map(), borderFill: /* @__PURE__ */ new Map() };
@@ -30578,8 +31168,8 @@ function parseRenderStyles(headXml) {
30578
31168
  };
30579
31169
  walk(root);
30580
31170
  for (const [id, tab] of paraTabRef) {
30581
- const g = styles.paraGeom.get(id);
30582
- if (g && autoTabPr.has(tab)) g.autoTabLeft = true;
31171
+ const g2 = styles.paraGeom.get(id);
31172
+ if (g2 && autoTabPr.has(tab)) g2.autoTabLeft = true;
30583
31173
  }
30584
31174
  return styles;
30585
31175
  }
@@ -30654,8 +31244,8 @@ function anchorRunInfo(xml, para2, anchor) {
30654
31244
  tr = para2.tRanges.find((r) => !r.selfClosing && xml.slice(r.contentStart, r.contentEnd).includes(head)) ?? para2.tRanges[para2.tRanges.length - 1];
30655
31245
  }
30656
31246
  if (!tr) return null;
30657
- const before = xml.slice(Math.max(0, para2.start), tr.contentStart);
30658
- const runOpen = [...before.matchAll(/<([A-Za-z0-9]+):run\b[^>]*>/g)].pop();
31247
+ const before2 = xml.slice(Math.max(0, para2.start), tr.contentStart);
31248
+ const runOpen = [...before2.matchAll(/<([A-Za-z0-9]+):run\b[^>]*>/g)].pop();
30659
31249
  const prefix = runOpen?.[1] ?? tr.prefix ?? "hp";
30660
31250
  const charPr2 = runOpen && attrOf3(runOpen[0], "charPrIDRef") || "0";
30661
31251
  const close = xml.indexOf(`</${prefix}:run>`, tr.contentEnd);
@@ -32273,9 +32863,9 @@ function patchTextChunkTable(table2, scanTable, orig, edited, ctx, skip) {
32273
32863
  }
32274
32864
  return skip("\uD45C \uB80C\uB354 \uACBD\uB85C \uC2DD\uBCC4 \uC2E4\uD328");
32275
32865
  }
32276
- function applyCellEdit(table2, scanTable, gridR, gridC, newLines, ctx, before, after, origLineCount) {
32866
+ function applyCellEdit(table2, scanTable, gridR, gridC, newLines, ctx, before2, after2, origLineCount) {
32277
32867
  const skip = (reason) => {
32278
- ctx.skipped.push({ reason, before: summarize(before), after: summarize(after) });
32868
+ ctx.skipped.push({ reason, before: summarize(before2), after: summarize(after2) });
32279
32869
  return 0;
32280
32870
  };
32281
32871
  const cell2 = scanTable.cellByAnchor.get(`${gridR},${gridC}`);
@@ -32320,7 +32910,7 @@ function applyCellEdit(table2, scanTable, gridR, gridC, newLines, ctx, before, a
32320
32910
  splices.push(...sp);
32321
32911
  sectionIndex = target.sectionIndex;
32322
32912
  if (newLines.length > 1) {
32323
- ctx.skipped.push({ reason: "\uC140 \uB0B4 \uC904 \uCD94\uAC00\uB294 \uBB38\uB2E8 \uC0DD\uC131 \uBBF8\uC9C0\uC6D0 \u2014 \uD55C \uBB38\uB2E8\uC73C\uB85C \uBCD1\uD569 \uC801\uC6A9", after: summarize(after), partial: true });
32913
+ ctx.skipped.push({ reason: "\uC140 \uB0B4 \uC904 \uCD94\uAC00\uB294 \uBB38\uB2E8 \uC0DD\uC131 \uBBF8\uC9C0\uC6D0 \u2014 \uD55C \uBB38\uB2E8\uC73C\uB85C \uBCD1\uD569 \uC801\uC6A9", after: summarize(after2), partial: true });
32324
32914
  }
32325
32915
  } else {
32326
32916
  const assigned = [];
@@ -32332,9 +32922,9 @@ function applyCellEdit(table2, scanTable, gridR, gridC, newLines, ctx, before, a
32332
32922
  }
32333
32923
  }
32334
32924
  if (newLines.length > nonEmpty.length) {
32335
- ctx.skipped.push({ reason: "\uC140 \uB0B4 \uC904 \uCD94\uAC00\uB294 \uBB38\uB2E8 \uC0DD\uC131 \uBBF8\uC9C0\uC6D0 \u2014 \uB9C8\uC9C0\uB9C9 \uBB38\uB2E8\uC5D0 \uBCD1\uD569 \uC801\uC6A9", after: summarize(after), partial: true });
32925
+ ctx.skipped.push({ reason: "\uC140 \uB0B4 \uC904 \uCD94\uAC00\uB294 \uBB38\uB2E8 \uC0DD\uC131 \uBBF8\uC9C0\uC6D0 \u2014 \uB9C8\uC9C0\uB9C9 \uBB38\uB2E8\uC5D0 \uBCD1\uD569 \uC801\uC6A9", after: summarize(after2), partial: true });
32336
32926
  } else if (newLines.length < nonEmpty.length && nonEmpty.length > 1) {
32337
- ctx.skipped.push({ reason: "\uC140 \uB0B4 \uC904 \uC0AD\uC81C\uB294 \uBB38\uB2E8 \uC81C\uAC70 \uBBF8\uC9C0\uC6D0 \u2014 \uBE48 \uBB38\uB2E8 \uC794\uC874(\uBDF0\uC5B4\uC5D0 \uBE48 \uC904 \uD45C\uC2DC \uAC00\uB2A5)", before: summarize(before), after: summarize(after), partial: true });
32927
+ ctx.skipped.push({ reason: "\uC140 \uB0B4 \uC904 \uC0AD\uC81C\uB294 \uBB38\uB2E8 \uC81C\uAC70 \uBBF8\uC9C0\uC6D0 \u2014 \uBE48 \uBB38\uB2E8 \uC794\uC874(\uBDF0\uC5B4\uC5D0 \uBE48 \uC904 \uD45C\uC2DC \uAC00\uB2A5)", before: summarize(before2), after: summarize(after2), partial: true });
32338
32928
  }
32339
32929
  for (let i = 0; i < nonEmpty.length; i++) {
32340
32930
  if (assigned[i] === nonEmpty[i].text || normForMatch(assigned[i]) === normForMatch(nonEmpty[i].text)) continue;
@@ -32600,6 +33190,11 @@ function resolveParagraphMappings(blocks, scans) {
32600
33190
  if (b.type !== "paragraph" && b.type !== "heading" || !b.text || !footerNorms.has(normForMatch(b.text))) break;
32601
33191
  pageText.add(i);
32602
33192
  }
33193
+ const noted = scans.flatMap((scan) => {
33194
+ if (!/<(?:\w+:)?(?:footNote|endNote)\b/.test(scan.xml)) return [];
33195
+ const fmts = sectionNoteFormats(scan.xml);
33196
+ return scan.bodyParagraphs.map((p) => ({ key: normForMatch(p.text), marks: paraNoteMarks(scan.xml, p, fmts) })).filter((x) => x.key && x.marks.length > 0);
33197
+ });
32603
33198
  const counters = /* @__PURE__ */ new Map();
32604
33199
  const result = /* @__PURE__ */ new Map();
32605
33200
  for (let i = 0; i < blocks.length; i++) {
@@ -32611,13 +33206,26 @@ function resolveParagraphMappings(blocks, scans) {
32611
33206
  }
32612
33207
  let key = normForMatch(b.text);
32613
33208
  let prefixStripped = false;
33209
+ let noteMarks;
32614
33210
  if (!buckets.has(key)) {
32615
33211
  const sp = b.text.indexOf(" ");
32616
- if (sp > 0) {
32617
- const alt = normForMatch(b.text.slice(sp + 1));
32618
- if (alt && buckets.has(alt)) {
32619
- key = alt;
32620
- prefixStripped = true;
33212
+ const alt = sp > 0 ? normForMatch(b.text.slice(sp + 1)) : "";
33213
+ if (alt && buckets.has(alt)) {
33214
+ key = alt;
33215
+ prefixStripped = true;
33216
+ } else {
33217
+ for (const n of noted) {
33218
+ if (textWithoutMarks(key, n.key, n.marks)) {
33219
+ key = n.key;
33220
+ noteMarks = n.marks;
33221
+ break;
33222
+ }
33223
+ if (alt && textWithoutMarks(alt, n.key, n.marks)) {
33224
+ key = n.key;
33225
+ noteMarks = n.marks;
33226
+ prefixStripped = true;
33227
+ break;
33228
+ }
32621
33229
  }
32622
33230
  }
32623
33231
  }
@@ -32628,10 +33236,90 @@ function resolveParagraphMappings(blocks, scans) {
32628
33236
  }
32629
33237
  const occ = counters.get(key) ?? 0;
32630
33238
  counters.set(key, occ + 1);
32631
- result.set(i, occ < list.length ? { para: list[occ], prefixStripped } : {});
33239
+ result.set(i, occ < list.length ? { para: list[occ], prefixStripped, noteMarks } : {});
32632
33240
  }
32633
33241
  return result;
32634
33242
  }
33243
+ function sectionNoteFormats(xml) {
33244
+ const pick2 = (tag) => {
33245
+ const m = new RegExp(`<(?:\\w+:)?${tag}\\b[^>]*>\\s*<(?:\\w+:)?autoNumFormat\\b([^>]*?)/?>`).exec(xml);
33246
+ return m ? noteFormatFrom((n) => xmlAttr(m[1], n)) : void 0;
33247
+ };
33248
+ return { footnote: pick2("footNotePr"), endnote: pick2("endNotePr") };
33249
+ }
33250
+ function xmlAttr(attrs, name) {
33251
+ const m = new RegExp(`(?:^|\\s)${name}\\s*=\\s*"([^"]*)"`).exec(attrs);
33252
+ return m ? decodeXmlEntities(m[1]) : void 0;
33253
+ }
33254
+ function paraNoteMarks(xml, para2, fmts) {
33255
+ const end = findElementEnd(xml, para2.start);
33256
+ if (end < 0) return [];
33257
+ const seg = xml.slice(para2.start, end);
33258
+ if (!/<(?:\w+:)?(?:footNote|endNote)\b/.test(seg)) return [];
33259
+ const marks = [];
33260
+ let depth = 0;
33261
+ for (const m of seg.matchAll(/<(\/?)(?:\w+:)?(tbl|drawText|caption|footNote|endNote|header|footer|memo|hiddenComment)\b([^>]*?)(\/?)>/g)) {
33262
+ const [, close, tag, attrs, selfClose] = m;
33263
+ if (selfClose) continue;
33264
+ if (close) {
33265
+ depth = Math.max(0, depth - 1);
33266
+ continue;
33267
+ }
33268
+ if (depth === 0 && (tag === "footNote" || tag === "endNote")) {
33269
+ const get = (n) => xmlAttr(attrs, n);
33270
+ marks.push(noteRefMark(
33271
+ { number: get("number"), prefixChar: get("prefixChar"), suffixChar: get("suffixChar"), userChar: get("userChar") },
33272
+ tag === "endNote" ? fmts.endnote : fmts.footnote
33273
+ ));
33274
+ }
33275
+ depth++;
33276
+ }
33277
+ return marks;
33278
+ }
33279
+ function textWithoutMarks(irKey, scanKey, marks) {
33280
+ let i = 0, j = 0, k = 0;
33281
+ while (i < irKey.length) {
33282
+ if (j < scanKey.length && irKey[i] === scanKey[j]) {
33283
+ i++;
33284
+ j++;
33285
+ continue;
33286
+ }
33287
+ if (k < marks.length && marks[k] && irKey.startsWith(marks[k], i)) {
33288
+ i += marks[k].length;
33289
+ k++;
33290
+ continue;
33291
+ }
33292
+ if (irKey[i] === " " && (j === 0 || scanKey[j - 1] === " ")) {
33293
+ i++;
33294
+ continue;
33295
+ }
33296
+ return false;
33297
+ }
33298
+ return j === scanKey.length && k === marks.length;
33299
+ }
33300
+ function removeNoteMarks(text, marks) {
33301
+ let out = text;
33302
+ let cursor = 0;
33303
+ const missing = [];
33304
+ for (const mark of marks) {
33305
+ if (!mark) continue;
33306
+ let at = -1;
33307
+ for (let p = out.indexOf(mark, cursor); p !== -1; p = out.indexOf(mark, p + 1)) {
33308
+ if (p === 0 || !/\s/.test(out[p - 1])) {
33309
+ at = p;
33310
+ break;
33311
+ }
33312
+ if (at === -1) at = p;
33313
+ }
33314
+ if (at === -1) {
33315
+ missing.push(mark);
33316
+ continue;
33317
+ }
33318
+ out = out.slice(0, at) + out.slice(at + mark.length);
33319
+ cursor = at;
33320
+ }
33321
+ return { text: out, missing };
33322
+ }
32635
33323
  function handleModifiedUnit(orig, edited, ctx) {
32636
33324
  const block = ctx.origBlocks[orig.blockIdx];
32637
33325
  const skip = (reason) => {
@@ -32714,6 +33402,11 @@ function patchParagraphUnit(block, orig, edited, ctx, skip) {
32714
33402
  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})` });
32715
33403
  }
32716
33404
  }
33405
+ if (mapping.noteMarks?.length) {
33406
+ const r = removeNoteMarks(newPlain, mapping.noteMarks);
33407
+ newPlain = r.text;
33408
+ 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(" ") });
33409
+ }
32717
33410
  if (mapping.prefixStripped) {
32718
33411
  const origPrefix = block.text.split(" ", 1)[0];
32719
33412
  const sp = newPlain.indexOf(" ");
@@ -33102,6 +33795,47 @@ var Surgeon = class {
33102
33795
  entry.size = newData.length;
33103
33796
  this.writeDirEntry(entry);
33104
33797
  }
33798
+ /**
33799
+ * wipeFreeSectors 본체 — 어느 체인에도 닿지 않는 섹터·미니섹터와 스트림 꼬리 슬랙을 0으로.
33800
+ * FREESECT 표시를 믿지 않고 도달성으로 판정한다(쓰는 쪽에 따라 빈 FAT 칸을 ENDOFCHAIN 으로 채우기도 한다).
33801
+ */
33802
+ wipeUnallocated() {
33803
+ const used = /* @__PURE__ */ new Set([...this.fatSectors, ...this.miniFatSectors, ...this.dirSectors]);
33804
+ let d = this.buf.readUInt32LE(68);
33805
+ for (let guard = 0; d !== ENDOFCHAIN && d !== FREESECT && guard < 1e6; guard++) {
33806
+ used.add(d);
33807
+ d = this.buf.readUInt32LE(this.sectorOffset(d) + 127 * 4);
33808
+ }
33809
+ const root = this.rootEntry();
33810
+ const rootChain = root.start === ENDOFCHAIN || root.size === 0 ? [] : this.chain(root.start);
33811
+ for (const s of rootChain) used.add(s);
33812
+ const usedMini = /* @__PURE__ */ new Set();
33813
+ for (const e of this.entries) {
33814
+ if (e.type !== 2 || e.size === 0 || e.start === ENDOFCHAIN) continue;
33815
+ if (e.size < MINI_CUTOFF) {
33816
+ const chain = this.miniChain(e.start);
33817
+ for (const s of chain) usedMini.add(s);
33818
+ const tail = e.size - (chain.length - 1) * MINI_SECTOR;
33819
+ const off = this.miniOffset(chain[chain.length - 1], rootChain);
33820
+ if (tail > 0 && tail < MINI_SECTOR) this.buf.fill(0, off + tail, off + MINI_SECTOR);
33821
+ } else {
33822
+ const chain = this.chain(e.start);
33823
+ for (const s of chain) used.add(s);
33824
+ const tail = e.size - (chain.length - 1) * SECTOR;
33825
+ const off = this.sectorOffset(chain[chain.length - 1]);
33826
+ if (tail > 0 && tail < SECTOR) this.buf.fill(0, off + tail, off + SECTOR);
33827
+ }
33828
+ }
33829
+ for (let i = 0; SECTOR + (i + 1) * SECTOR <= this.len; i++) {
33830
+ if (!used.has(i)) this.buf.fill(0, SECTOR + i * SECTOR, SECTOR + (i + 1) * SECTOR);
33831
+ }
33832
+ const capacity = rootChain.length * (SECTOR / MINI_SECTOR);
33833
+ for (let i = 0; i < capacity; i++) {
33834
+ if (usedMini.has(i)) continue;
33835
+ const off = this.miniOffset(i, rootChain);
33836
+ this.buf.fill(0, off, off + MINI_SECTOR);
33837
+ }
33838
+ }
33105
33839
  finish() {
33106
33840
  this.wipeFreedSectors();
33107
33841
  this.flushFat();
@@ -33434,6 +34168,11 @@ function resolveTableMappings(blocks, scanTables) {
33434
34168
  for (let k = si; k < scanTables.length; k++) {
33435
34169
  if (scanTables[k].rows === table2.rows && scanTables[k].cols === table2.cols) cands.push(k);
33436
34170
  }
34171
+ if (cands.length === 0) {
34172
+ for (let k = si; k < scanTables.length; k++) {
34173
+ if (scanTables[k].rows === table2.rows && scanTables[k].cols > table2.cols) cands.push(k);
34174
+ }
34175
+ }
33437
34176
  if (cands.length === 0) continue;
33438
34177
  let pick2 = cands[0];
33439
34178
  if (cands.length > 1) {
@@ -33554,11 +34293,11 @@ function patchGfmCells(scanTable, orig, edited, ctx, skip) {
33554
34293
  cellSkip("\uC11C\uC2DD/\uB9C1\uD06C/\uC774\uBBF8\uC9C0 \uD3EC\uD568 \uC140 \uC218\uC815\uC740 \uBBF8\uC9C0\uC6D0 (v1)");
33555
34294
  continue;
33556
34295
  }
33557
- const before = beforeEach.join("\n");
33558
- const after = afterEach.join("\n");
33559
- if (before === after) continue;
34296
+ const before2 = beforeEach.join("\n");
34297
+ const after2 = afterEach.join("\n");
34298
+ if (before2 === after2) continue;
33560
34299
  const para2 = cell2.paras[0];
33561
- if (normForMatch(para2.rawText) !== normForMatch(before)) {
34300
+ if (normForMatch(para2.rawText) !== normForMatch(before2)) {
33562
34301
  cellSkip("\uC140 \uD14D\uC2A4\uD2B8 \uBD88\uC77C\uCE58 \u2014 \uC18C\uC2A4\uB9F5 \uC2E0\uB8B0 \uBD88\uAC00");
33563
34302
  continue;
33564
34303
  }
@@ -33566,7 +34305,7 @@ function patchGfmCells(scanTable, orig, edited, ctx, skip) {
33566
34305
  cellSkip("\uACF5\uBC31 \uC815\uADDC\uD654 \uBD88\uC548\uC815 \uD14D\uC2A4\uD2B8 \u2014 \uBBF8\uC9C0\uC6D0");
33567
34306
  continue;
33568
34307
  }
33569
- applied += stageParaPatch(ctx.scans[para2.sectionIndex], para2, after, cellSkip);
34308
+ applied += stageParaPatch(ctx.scans[para2.sectionIndex], para2, after2, cellSkip);
33570
34309
  continue;
33571
34310
  }
33572
34311
  if (beforeParts.length !== cell2.paras.length || afterParts.length !== cell2.paras.length) {
@@ -33574,27 +34313,27 @@ function patchGfmCells(scanTable, orig, edited, ctx, skip) {
33574
34313
  continue;
33575
34314
  }
33576
34315
  for (let k = 0; k < cell2.paras.length; k++) {
33577
- const before = gfmCellToPlain(beforeParts[k]);
33578
- const after = gfmCellToPlain(afterParts[k]);
33579
- if (before === null || after === null) {
34316
+ const before2 = gfmCellToPlain(beforeParts[k]);
34317
+ const after2 = gfmCellToPlain(afterParts[k]);
34318
+ if (before2 === null || after2 === null) {
33580
34319
  cellSkip("\uC11C\uC2DD/\uB9C1\uD06C/\uC774\uBBF8\uC9C0 \uD3EC\uD568 \uC140 \uC218\uC815\uC740 \uBBF8\uC9C0\uC6D0 (v1)");
33581
34320
  break;
33582
34321
  }
33583
- if (before === after) continue;
33584
- if (after.includes("\n")) {
34322
+ if (before2 === after2) continue;
34323
+ if (after2.includes("\n")) {
33585
34324
  cellSkip("\uC140 \uB0B4 \uC904\uBC14\uAFC8 \uCD94\uAC00\uB294 \uBBF8\uC9C0\uC6D0 (v1)");
33586
34325
  break;
33587
34326
  }
33588
34327
  const para2 = cell2.paras[k];
33589
- if (normForMatch(para2.rawText) !== normForMatch(before)) {
34328
+ if (normForMatch(para2.rawText) !== normForMatch(before2)) {
33590
34329
  cellSkip("\uC140 \uD14D\uC2A4\uD2B8 \uBD88\uC77C\uCE58 \u2014 \uC18C\uC2A4\uB9F5 \uC2E0\uB8B0 \uBD88\uAC00");
33591
34330
  break;
33592
34331
  }
33593
- if (sanitizeText(after) !== after) {
34332
+ if (sanitizeText(after2) !== after2) {
33594
34333
  cellSkip("\uACF5\uBC31 \uC815\uADDC\uD654 \uBD88\uC548\uC815 \uD14D\uC2A4\uD2B8 \u2014 \uBBF8\uC9C0\uC6D0");
33595
34334
  break;
33596
34335
  }
33597
- applied += stageParaPatch(ctx.scans[para2.sectionIndex], para2, after, cellSkip);
34336
+ applied += stageParaPatch(ctx.scans[para2.sectionIndex], para2, after2, cellSkip);
33598
34337
  }
33599
34338
  }
33600
34339
  }
@@ -33684,9 +34423,9 @@ function patchTextChunk5(table2, scanTable, orig, edited, ctx, skip) {
33684
34423
  }
33685
34424
  return skip("\uD45C \uB80C\uB354 \uACBD\uB85C \uC2DD\uBCC4 \uC2E4\uD328");
33686
34425
  }
33687
- function applyCellEdit5(table2, scanTable, gridR, gridC, newLines, ctx, before, after, origLineCount) {
34426
+ function applyCellEdit5(table2, scanTable, gridR, gridC, newLines, ctx, before2, after2, origLineCount) {
33688
34427
  const skip = (reason) => {
33689
- ctx.skipped.push({ reason, before: summarize(before), after: summarize(after) });
34428
+ ctx.skipped.push({ reason, before: summarize(before2), after: summarize(after2) });
33690
34429
  return 0;
33691
34430
  };
33692
34431
  const cell2 = scanTable.cells.get(`${gridR},${gridC}`);
@@ -33719,9 +34458,9 @@ function applyCellEdit5(table2, scanTable, gridR, gridC, newLines, ctx, before,
33719
34458
  }
33720
34459
  }
33721
34460
  if (newLines.length > targets.length) {
33722
- ctx.skipped.push({ reason: "\uC140 \uB0B4 \uCD94\uAC00 \uC904\uC744 \uB9C8\uC9C0\uB9C9 \uBB38\uB2E8\uC5D0 \uAC15\uC81C \uC904\uBC14\uAFC8\uC73C\uB85C \uBCD1\uD569(\uBB38\uB2E8 \uC0DD\uC131 \uB300\uC2E0)", after: summarize(after), partial: true });
34461
+ ctx.skipped.push({ reason: "\uC140 \uB0B4 \uCD94\uAC00 \uC904\uC744 \uB9C8\uC9C0\uB9C9 \uBB38\uB2E8\uC5D0 \uAC15\uC81C \uC904\uBC14\uAFC8\uC73C\uB85C \uBCD1\uD569(\uBB38\uB2E8 \uC0DD\uC131 \uB300\uC2E0)", after: summarize(after2), partial: true });
33723
34462
  } else if (newLines.length < nonEmpty.length && nonEmpty.length > 1) {
33724
- ctx.skipped.push({ reason: "\uC140 \uB0B4 \uC904 \uC0AD\uC81C\uB294 \uBB38\uB2E8 \uC81C\uAC70 \uBBF8\uC9C0\uC6D0 \u2014 \uBE48 \uBB38\uB2E8 \uC794\uC874(\uBDF0\uC5B4\uC5D0 \uBE48 \uC904 \uD45C\uC2DC \uAC00\uB2A5)", before: summarize(before), after: summarize(after), partial: true });
34463
+ ctx.skipped.push({ reason: "\uC140 \uB0B4 \uC904 \uC0AD\uC81C\uB294 \uBB38\uB2E8 \uC81C\uAC70 \uBBF8\uC9C0\uC6D0 \u2014 \uBE48 \uBB38\uB2E8 \uC794\uC874(\uBDF0\uC5B4\uC5D0 \uBE48 \uC904 \uD45C\uC2DC \uAC00\uB2A5)", before: summarize(before2), after: summarize(after2), partial: true });
33725
34464
  }
33726
34465
  let staged = 0;
33727
34466
  for (let i = 0; i < targets.length; i++) {
@@ -34037,22 +34776,18 @@ async function validateHwpx(buffer) {
34037
34776
  return { ok: issues.length === 0, issues, entryCount: names.length };
34038
34777
  }
34039
34778
 
34040
- // src/redact.ts
34041
- var DEFAULT_REDACT_RULES = [
34042
- "rrn",
34043
- "phone",
34044
- "email",
34045
- "card",
34046
- "account"
34047
- ];
34779
+ // src/redact-rules.ts
34048
34780
  var RULE_PRIORITY = [
34781
+ "crn",
34049
34782
  "rrn",
34050
34783
  "email",
34051
34784
  "card",
34785
+ "brn",
34052
34786
  "phone",
34053
34787
  "driver",
34788
+ "passport",
34054
34789
  "account",
34055
- "passport"
34790
+ "ip"
34056
34791
  ];
34057
34792
  function luhnValid(digits) {
34058
34793
  let sum = 0;
@@ -34066,110 +34801,586 @@ function luhnValid(digits) {
34066
34801
  }
34067
34802
  return sum % 10 === 0;
34068
34803
  }
34069
- function birthdateValid(front6) {
34804
+ var DAYS_IN_MONTH = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
34805
+ function birthdateValid(front6, genderDigit) {
34806
+ const yy = Number(front6.slice(0, 2));
34070
34807
  const mm = Number(front6.slice(2, 4));
34071
34808
  const dd = Number(front6.slice(4, 6));
34072
- return mm >= 1 && mm <= 12 && dd >= 1 && dd <= 31;
34809
+ if (mm < 1 || mm > 12 || dd < 1) return false;
34810
+ const g2 = Number(genderDigit);
34811
+ const year = (g2 === 3 || g2 === 4 || g2 === 7 || g2 === 8 ? 2e3 : 1900) + yy;
34812
+ if (year > (/* @__PURE__ */ new Date()).getFullYear()) return false;
34813
+ const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
34814
+ const max = mm === 2 ? leap ? 29 : 28 : DAYS_IN_MONTH[mm - 1];
34815
+ return dd <= max;
34816
+ }
34817
+ function brnChecksumValid(d) {
34818
+ const w = [1, 3, 7, 1, 3, 7, 1, 3, 5];
34819
+ let sum = 0;
34820
+ for (let i = 0; i < 9; i++) sum += w[i] * (d.charCodeAt(i) - 48);
34821
+ sum += Math.floor((d.charCodeAt(8) - 48) * 5 / 10);
34822
+ return (10 - sum % 10) % 10 === d.charCodeAt(9) - 48;
34073
34823
  }
34824
+ function crnChecksumValid(d) {
34825
+ let sum = 0;
34826
+ for (let i = 0; i < 12; i++) sum += (i % 2 === 0 ? 1 : 2) * (d.charCodeAt(i) - 48);
34827
+ return (10 - sum % 10) % 10 === d.charCodeAt(12) - 48;
34828
+ }
34829
+ var digitsOf = (s) => s.replace(/\D/g, "");
34830
+ var allSameDigit = (d) => d.length > 0 && /^(\d)\1*$/.test(d);
34831
+ var LABELS = [
34832
+ ["crn", /법\s*인\s*(?:등\s*록\s*)?번\s*호/g],
34833
+ ["rrn", /주\s*민\s*(?:등\s*록\s*)?번\s*호|주\s*민\s*등\s*록|외\s*국\s*인\s*(?:등\s*록\s*)?번\s*호|거\s*소\s*신\s*고\s*번\s*호|생\s*년\s*월\s*일|resident\s*registration|\bRRN\b/gi],
34834
+ ["brn", /사\s*업\s*자\s*(?:등\s*록\s*)?번\s*호|고\s*유\s*번\s*호|business\s*registration/gi],
34835
+ ["account", /계\s*좌|예\s*금\s*주|입\s*금|은\s*행|뱅\s*크|농\s*협|신\s*협|수\s*협|우\s*체\s*국|금\s*고|bank\s*account|account\s*(?:no|number)/gi],
34836
+ ["card", /카\s*드|credit\s*card|card\s*(?:no|number)/gi],
34837
+ ["phone", /전\s*화|연\s*락\s*처|휴\s*대|핸\s*드\s*폰|팩\s*스|☎|☏|콜\s*센\s*터|고\s*객\s*센\s*터|대\s*표\s*번\s*호|상\s*담|문\s*의|\b(?:tel|fax|mobile|phone|h\.?p|ARS)\b/gi],
34838
+ ["passport", /여\s*권|passport/gi],
34839
+ ["driver", /운\s*전\s*면\s*허|면\s*허\s*번\s*호|driver'?s?\s*licen[cs]e/gi],
34840
+ ["ip", /\bIP\b|아\s*이\s*피/g],
34841
+ ["other", /출\s*원\s*번\s*호|과\s*제\s*번\s*호|사\s*업\s*번\s*호|접\s*수\s*번\s*호|민\s*원\s*번\s*호|신\s*청\s*번\s*호|문\s*서\s*번\s*호|관\s*리\s*번\s*호|주\s*문\s*번\s*호|일\s*련\s*번\s*호|발\s*간\s*등\s*록|등\s*록\s*번\s*호\s*\(\s*기\s*록|ISBN|ISSN|예\s*산|과\s*목|코\s*드|모\s*델|제\s*품|부\s*품|시\s*리\s*얼|serial|model|\bS\/N\b|바\s*코\s*드|기\s*록\s*물|사\s*건\s*번\s*호/gi]
34842
+ ];
34843
+ var CELL_SEP_RE = /(?<!\\)\||<\/t[dh]>/gi;
34844
+ var PREV_VALUE_RE = /\d[\d\-.\s]{4,}\d/g;
34845
+ function labelsIn(s) {
34846
+ const out = /* @__PURE__ */ new Set();
34847
+ for (const [ctx, re] of LABELS) {
34848
+ re.lastIndex = 0;
34849
+ if (re.test(s)) out.add(ctx);
34850
+ }
34851
+ return out;
34852
+ }
34853
+ function labelsBefore(norm2, start) {
34854
+ let win = norm2.slice(Math.max(0, start - 120), start);
34855
+ const seps = [...win.matchAll(CELL_SEP_RE)];
34856
+ if (seps.length >= 2) {
34857
+ const s = seps[seps.length - 2];
34858
+ win = win.slice(s.index + s[0].length);
34859
+ }
34860
+ win = win.replace(/<[^>]*>/g, " ").slice(-40);
34861
+ const prev = [...win.matchAll(PREV_VALUE_RE)].pop();
34862
+ if (prev) win = win.slice(prev.index + prev[0].length);
34863
+ return labelsIn(win);
34864
+ }
34865
+ var OTHER_AFTER_RE = /^\s{0,2}[(\[]?\s{0,2}(?:접\s*수|신\s*청|주\s*문|사\s*건|과\s*제|문\s*서|관\s*리|부\s*품|제\s*품|모\s*델|코\s*드|시\s*리\s*얼|serial|model)/i;
34866
+ var g = (x, name) => x.m.groups?.[name] ?? "";
34867
+ var has = (x, c) => x.ctx().has(c);
34868
+ var crnOnly = (x) => has(x, "crn") && !has(x, "rrn");
34869
+ var after = (x, n = 16) => {
34870
+ const end = x.m.index + x.m[0].length;
34871
+ return x.m.input.slice(end, end + n);
34872
+ };
34873
+ var before = (x, n = 2) => {
34874
+ const start = x.m.index;
34875
+ return x.m.input.slice(Math.max(0, start - n), start);
34876
+ };
34877
+ var otherCtx = (x, own) => has(x, "other") && !has(x, own) || OTHER_AFTER_RE.test(after(x));
34878
+ var SERIAL_BEFORE_RE = /(?:\bS\/N|\bserial(?:\s*(?:no|number))?|시\s*리\s*얼(?:\s*번\s*호)?|일\s*련\s*번\s*호|바\s*코\s*드)\s*[:.]?\s*$/i;
34879
+ var serialBefore = (x) => SERIAL_BEFORE_RE.test(before(x, 20)) && !has(x, "phone");
34880
+ var PHONE_PREFIX = "0(?:1[016789]|2|3[1-3]|4[1-4]|5[1-5]|6[1-4]|70|80|60|50[2-8])";
34881
+ var PHONE_AREA = "0(?:1[016789]|2|3[1-3]|4[1-4]|5[1-5]|6[1-4]|70|80|50[2-8])";
34882
+ var PHONE_SEP = "(?: {0,2}- {0,2}| {1,2}|\\.)";
34883
+ var PHONE_END = "(?![\\w@-])";
34884
+ function phoneSepsOk(x, s1, s2) {
34885
+ if (s1 === "" !== (s2 === "")) return false;
34886
+ if (s1 === "." !== (s2 === ".")) return false;
34887
+ return s1 !== "." || !/^\.\d/.test(after(x, 2));
34888
+ }
34889
+ function phoneDigitsOk(pre, mid, last, bare) {
34890
+ if (/^0+$/.test(mid + last)) return false;
34891
+ return !bare || !/^0(?:2|3[1-3]|4[1-4]|5[1-5]|6[1-4])$/.test(pre) || mid[0] !== "0";
34892
+ }
34893
+ var EMAIL_START = "(?<![\\w%+])(?<!\\w[.\\-])(?<![A-Za-z0-9][*\u25CF\u25CB\u25EF\xD7#]+)";
34894
+ var EMAIL_DOMAIN = "(?<domain>[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?(?:\\.[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?)*\\.(?<tld>[A-Za-z]{2,24}))(?![\\w-])";
34895
+ var FILE_EXT_TLD = /^(?:png|jpe?g|gif|bmp|svg|webp|tiff?|hwpx?|hwp|pdf|docx?|xlsx?|pptx?|txt|zip|csv|json|xml|html?|js|css|mp[34]|wav|avi|exe)$/i;
34074
34896
  var RULES2 = {
34075
- // 주민/외국인등록번호 — 뒷자리 첫 숫자 1-8 + 생년월일 유효성으로 오탐 축소.
34076
- // 유니코드 대시 변형(‐ ‑ – —)은 rrn만 허용. 앞 6자리 유지, 뒤 7자리 전부 마스크.
34077
- rrn: {
34078
- pattern: /(?<!\d)(\d{6})([-‐‑–—])([1-8]\d{6})(?!\d)/g,
34079
- validate: (m) => birthdateValid(m[1]),
34080
- mask: (m, mc) => m[1] + m[2] + mc.repeat(7)
34081
- },
34082
- // 이메일 — 로컬파트 첫 글자만 남기고 마스크, 도메인 유지
34083
- email: {
34084
- pattern: /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g,
34085
- mask: (m, mc) => {
34086
- const at = m[0].indexOf("@");
34087
- return m[0][0] + mc.repeat(at - 1) + m[0].slice(at);
34088
- }
34089
- },
34090
- // 카드번호 — 구분자 필수(무구분 16자리는 오탐 높아 제외), 동일 구분자 강제(\2),
34091
- // Luhn 체크. 가운데 8자리 마스크.
34092
- card: {
34093
- pattern: /(?<!\d)(\d{4})([- ])(\d{4})\2(\d{4})\2(\d{4})(?!\d)/g,
34094
- validate: (m) => luhnValid(m[1] + m[3] + m[4] + m[5]),
34095
- mask: (m, mc) => m[1] + m[2] + mc.repeat(4) + m[2] + mc.repeat(4) + m[2] + m[5]
34096
- },
34097
- // 전화번호 — 휴대폰(01[016789])·서울(02)·지역(0[3-6]\d)·인터넷(070)은 구분자
34098
- // -·.·공백 또는 무구분(동일 구분자 강제), 대표번호(15xx/16xx/18xx)는 구분자 필수.
34099
- // 가운데 자리만 마스크 (대표번호는 뒤 4자리). 선행 [\d-] 금지 — 계좌 부분매치 방지.
34100
- phone: {
34101
- pattern: /(?<![\d-])(?:(01[016789]|070|02|0[3-6]\d)([-. ]?)(\d{3,4})\2(\d{4})|(1[568]\d{2})([-. ])(\d{4}))(?!\d)/g,
34102
- mask: (m, mc) => m[1] !== void 0 ? m[1] + m[2] + mc.repeat(m[3].length) + m[2] + m[4] : m[5] + m[6] + mc.repeat(4)
34103
- },
34104
- // 운전면허 (기본 OFF) — 신형 12자리만 (지역명 2글자 선행 구버전은 스킵). 뒷 8자리 마스크.
34105
- driver: {
34106
- pattern: /(?<![\d-])(\d{2})-(\d{2})-\d{6}-\d{2}(?!-?\d)/g,
34107
- mask: (m, mc) => m[1] + "-" + m[2] + "-" + mc.repeat(6) + "-" + mc.repeat(2)
34108
- },
34109
- // 계좌번호 — 3~4그룹 + 총 자릿수 10~16. rrn·card·phone과 겹치면 그쪽 우선.
34110
- // 마지막 그룹 빼고 전부 마스크. 사업자등록번호(3-2-5, 10자리)도 걸린다 — 계약상 의도
34111
- // (테스트로 명시). 날짜(2026-07-16)는 8자리라 총자릿수 검증에서 탈락.
34112
- account: {
34113
- pattern: /(?<!\d)(?<!\d-)\d{2,6}(?:-\d{2,6}){1,2}-\d{2,8}(?!-?\d)/g,
34114
- validate: (m) => {
34115
- const digits = m[0].replace(/-/g, "").length;
34116
- return digits >= 10 && digits <= 16;
34897
+ // 주민·외국인등록번호 — 앞 6자리(생년월일) 유지, 뒤 7자리 마스크
34898
+ rrn: [
34899
+ {
34900
+ // 하이픈형 (앞뒤 공백 0~2칸, 유니코드 대시는 정규화로 흡수). 뒤에 "-글자"는 허용, "-숫자"는 더 긴 코드.
34901
+ // 법인 라벨만 있으면 제외
34902
+ re: /(?<![\w-])(?<front>\d{6})(?<sep> {0,2}- {0,2})(?<back>[1-8]\d{6})(?![\w])(?!-\d)/dg,
34903
+ ok: (x) => birthdateValid(g(x, "front"), g(x, "back")[0]) && !crnOnly(x),
34904
+ mask: ["back"]
34117
34905
  },
34118
- mask: (m, mc) => {
34119
- const parts = m[0].split("-");
34120
- return parts.map((p, i) => i === parts.length - 1 ? p : mc.repeat(p.length)).join("-");
34121
- }
34122
- },
34123
- // 여권번호 (기본 OFF) — 단어 경계, 첫 글자만 남기고 전부 마스크
34124
- passport: {
34125
- pattern: /\b([MSRODG])\d{8}(?![0-9A-Za-z])/g,
34126
- mask: (m, mc) => m[1] + mc.repeat(8)
34127
- }
34906
+ {
34907
+ // 뒷자리 일부만 가린 형태 (900101-1******, 900101-1694***) — 남은 숫자를 가린다
34908
+ re: /(?<![\w-])(?<front>\d{6})(?<sep> {0,2}- {0,2})(?<back>[1-8][0-9*●○◯×xX#]{6})(?![\w*●○◯×#])(?!-\d)/dg,
34909
+ ok: (x) => /[*●○◯×xX#]/.test(g(x, "back")) && birthdateValid(g(x, "front"), g(x, "back")[0]) && !crnOnly(x),
34910
+ mask: ["back"]
34911
+ },
34912
+ {
34913
+ // 구분자 없음·공백/온점/가운뎃점/빗금/물결 구분 — 주민/외국인등록번호(생년월일) 라벨이 있을 때만
34914
+ re: /(?<![\w@.-])(?<front>\d{6})(?<sep>[ .·/~]?)(?<back>[1-8]\d{6})(?![\w@-])/dg,
34915
+ needs: "rrn",
34916
+ ok: (x) => birthdateValid(g(x, "front"), g(x, "back")[0]),
34917
+ mask: ["back"]
34918
+ }
34919
+ ],
34920
+ // 법인등록번호 (opt-in) — 법인 라벨이 있거나, 뒷자리가 0으로 시작(주민번호 불가)하고 체크섬 유효
34921
+ crn: [
34922
+ {
34923
+ re: /(?<![\w-])(?<front>\d{6})(?<sep> {0,2}- {0,2})(?<back>\d{7})(?![\w-])/dg,
34924
+ ok: (x) => !allSameDigit(g(x, "front") + g(x, "back")) && (crnOnly(x) || g(x, "back")[0] === "0" && crnChecksumValid(g(x, "front") + g(x, "back"))),
34925
+ mask: ["back"]
34926
+ },
34927
+ {
34928
+ re: /(?<![\w@.-])(?<front>\d{6})(?<back>\d{7})(?![\w@-])/dg,
34929
+ needs: "crn",
34930
+ ok: (x) => !allSameDigit(g(x, "front") + g(x, "back")) && !has(x, "rrn"),
34931
+ mask: ["back"]
34932
+ }
34933
+ ],
34934
+ // 이메일 — 로컬파트 첫 글자만 남기고 마스크, 도메인 유지. 파일명(image@2x.png)은 제외
34935
+ // 이미 가린 로컬파트(j*********5@…) 뒤꼬리에서 시작하지 않는다 — 가림표 바로 뒤 매치 금지
34936
+ email: [
34937
+ {
34938
+ re: new RegExp(`${EMAIL_START}(?<l1>[A-Za-z0-9])(?<lrest>[A-Za-z0-9._%+-]+)@${EMAIL_DOMAIN}`, "dg"),
34939
+ ok: (x) => !FILE_EXT_TLD.test(g(x, "tld")),
34940
+ mask: ["lrest"],
34941
+ maskAll: true
34942
+ },
34943
+ {
34944
+ // 한 글자 로컬파트 — 첫 글자를 남기면 가린 게 없으므로 전부 가린다
34945
+ re: new RegExp(`${EMAIL_START}(?<l1>[A-Za-z0-9])@${EMAIL_DOMAIN}`, "dg"),
34946
+ ok: (x) => !FILE_EXT_TLD.test(g(x, "tld")),
34947
+ mask: ["l1"]
34948
+ }
34949
+ ],
34950
+ // 카드번호 — 가운데 마스크 + Luhn. 구분자 없는 15~16자리는 카드 라벨이 있을 때만
34951
+ card: [
34952
+ {
34953
+ // 4-4-4-4 (같은 구분자 강제)
34954
+ re: /(?<![\w-])(?<g1>\d{4})(?<sep>[- ])(?<g2>\d{4})\k<sep>(?<g3>\d{4})\k<sep>(?<g4>\d{4})(?![\w-])(?![- ]\d)/dg,
34955
+ ok: (x) => {
34956
+ const d = digitsOf(x.m[0]);
34957
+ return luhnValid(d) && !allSameDigit(d);
34958
+ },
34959
+ mask: ["g2", "g3"]
34960
+ },
34961
+ {
34962
+ // AMEX 4-6-5
34963
+ re: /(?<![\w-])(?<g1>3[47]\d{2})(?<sep>[- ])(?<g2>\d{6})\k<sep>(?<g3a>\d)(?<g3b>\d{4})(?![\w-])(?![- ]\d)/dg,
34964
+ ok: (x) => luhnValid(digitsOf(x.m[0])),
34965
+ mask: ["g2", "g3a"]
34966
+ },
34967
+ {
34968
+ // 온점 구분 4.4.4.4 — 카드 라벨 필요 (버전·좌표 모양과 겹침)
34969
+ re: /(?<![\w.-])(?<g1>\d{4})\.(?<g2>\d{4})\.(?<g3>\d{4})\.(?<g4>\d{4})(?![\w-])(?!\.\d)/dg,
34970
+ needs: "card",
34971
+ ok: (x) => {
34972
+ const d = digitsOf(x.m[0]);
34973
+ return luhnValid(d) && !allSameDigit(d);
34974
+ },
34975
+ mask: ["g2", "g3"]
34976
+ },
34977
+ {
34978
+ // 무구분 15~16자리 — 카드 라벨 필요
34979
+ re: /(?<![\w@.-])(?<g1>\d{4})(?<mid>\d{7,8})(?<g4>\d{4})(?![\w@-])/dg,
34980
+ needs: "card",
34981
+ ok: (x) => {
34982
+ const d = digitsOf(x.m[0]);
34983
+ return luhnValid(d) && !allSameDigit(d);
34984
+ },
34985
+ mask: ["mid"]
34986
+ }
34987
+ ],
34988
+ // 사업자등록번호 3-2-5 — 체크섬 유효 또는 사업자 라벨. 뒤 5자리(일련·검증번호) 마스크
34989
+ brn: [
34990
+ {
34991
+ re: /(?<![\w-])(?<a>\d{3})-(?<b>\d{2})-(?<c>\d{5})(?![\w-])/dg,
34992
+ ok: (x) => {
34993
+ const d = digitsOf(x.m[0]);
34994
+ if (allSameDigit(d)) return false;
34995
+ return has(x, "brn") || brnChecksumValid(d) && !has(x, "account") && !otherCtx(x, "brn");
34996
+ },
34997
+ mask: ["c"]
34998
+ },
34999
+ {
35000
+ re: /(?<![\w@.-])(?<a>\d{3})(?<b>\d{2})(?<c>\d{5})(?![\w@-])/dg,
35001
+ needs: "brn",
35002
+ ok: (x) => !allSameDigit(digitsOf(x.m[0])),
35003
+ mask: ["c"]
35004
+ }
35005
+ ],
35006
+ // 전화번호 — 가운데 자리만 마스크 (대표번호는 뒤 4자리).
35007
+ // 뒤에 "-"가 붙으면 더 긴 코드(예산과목 064-1100-1131-301-210)의 일부라 제외.
35008
+ phone: [
35009
+ {
35010
+ // 국내 일반: 휴대폰·지역·070/080/060·050x 안심번호. 구분자 -·공백·온점(PHONE_SEP) 또는 무구분
35011
+ re: new RegExp(`(?<![\\w@+-])(?<pre>${PHONE_PREFIX})(?<s1>${PHONE_SEP}?)(?<mid>\\d{3,4})(?<s2>${PHONE_SEP}?)(?<last>\\d{4})${PHONE_END}`, "dg"),
35012
+ ok: (x) => {
35013
+ const pre = g(x, "pre");
35014
+ const bare = g(x, "s1") === "";
35015
+ if (!phoneSepsOk(x, g(x, "s1"), g(x, "s2"))) return false;
35016
+ if (!phoneDigitsOk(pre, g(x, "mid"), g(x, "last"), bare)) return false;
35017
+ if (serialBefore(x)) return false;
35018
+ if (!bare) return true;
35019
+ if (/\d\.$/.test(before(x))) return false;
35020
+ if (pre.startsWith("01")) return true;
35021
+ return !/\d,$/.test(before(x)) && !/^,\d/.test(after(x, 2));
35022
+ },
35023
+ mask: ["mid"]
35024
+ },
35025
+ {
35026
+ // 괄호 지역번호: (02) 123-4567, (042)481-6300
35027
+ re: new RegExp(`(?<![\\w@])\\((?<pre>${PHONE_AREA})\\) ?(?<mid>\\d{3,4})(?<s2>${PHONE_SEP}?)(?<last>\\d{4})${PHONE_END}`, "dg"),
35028
+ ok: (x) => phoneDigitsOk(g(x, "pre"), g(x, "mid"), g(x, "last"), false) && (g(x, "s2") !== "." || !/^\.\d/.test(after(x, 2))),
35029
+ mask: ["mid"]
35030
+ },
35031
+ {
35032
+ // 지역번호 뒤 닫는 괄호: 02)450-1234
35033
+ re: new RegExp(`(?<![\\w@(+-])(?<!\\d\\.)(?<pre>${PHONE_AREA})\\) ?(?<mid>\\d{3,4})(?<s2>${PHONE_SEP})(?<last>\\d{4})${PHONE_END}`, "dg"),
35034
+ ok: (x) => phoneDigitsOk(g(x, "pre"), g(x, "mid"), g(x, "last"), false) && (g(x, "s2") !== "." || !/^\.\d/.test(after(x, 2))),
35035
+ mask: ["mid"]
35036
+ },
35037
+ {
35038
+ // 국제 표기: +82 10-1234-5678, +82-2-123-4567, +82 (0)10 1234 5678, +821012345678
35039
+ re: new RegExp(`(?<![\\w@+])(?<cc>\\+82)(?<s0>[-. ]?)(?:\\(0\\) ?)?(?<pre>1[016789]|2|3[1-3]|4[1-4]|5[1-5]|6[1-4]|70|80)(?<s1>${PHONE_SEP}?)(?<mid>\\d{3,4})(?<s2>${PHONE_SEP}?)(?<last>\\d{4})${PHONE_END}`, "dg"),
35040
+ ok: (x) => phoneSepsOk(x, g(x, "s1"), g(x, "s2")) && phoneDigitsOk("0" + g(x, "pre"), g(x, "mid"), g(x, "last"), g(x, "s1") === ""),
35041
+ mask: ["mid"]
35042
+ },
35043
+ {
35044
+ // 대표번호 15xx·16xx·18xx — 구분자 필수. 연도 범위 모양(1592-1598)·온점/공백 구분은 전화 라벨이 있을 때만.
35045
+ // 뒤 4자리 0000 은 자리표시자
35046
+ re: new RegExp(`(?<![\\w@+-])(?<!\\d\\.)(?<pre>1(?:5[2-9]\\d|6\\d\\d|8\\d\\d))(?<sep> {0,2}- {0,2}| {1,2}|\\.)(?<last>\\d{4})${PHONE_END}`, "dg"),
35047
+ ok: (x) => {
35048
+ if (g(x, "last") === "0000" || serialBefore(x)) return false;
35049
+ if (g(x, "sep") === "." && /^\.\d/.test(after(x, 2))) return false;
35050
+ const a = Number(g(x, "pre"));
35051
+ const b = Number(g(x, "last"));
35052
+ const yearRange = b >= a && b <= (/* @__PURE__ */ new Date()).getFullYear();
35053
+ return g(x, "sep").trim() === "-" && !yearRange || has(x, "phone");
35054
+ },
35055
+ mask: ["last"]
35056
+ }
35057
+ ],
35058
+ // 운전면허번호 — 지역코드(11~28)-연도-일련6-검증2 / 구형 지역명 + 2-6-2. 뒤 8자리 마스크
35059
+ driver: [
35060
+ {
35061
+ // 신형 — 무문맥이지만 비개인 식별번호 문맥("(접수)" 등)이면 아님
35062
+ re: /(?<![\w-])(?<r>1[1-9]|2[0-8])-(?<y>\d{2})-(?<s>\d{6})-(?<c>\d{2})(?![\w-])/dg,
35063
+ ok: (x) => has(x, "driver") || !otherCtx(x, "driver"),
35064
+ mask: ["s", "c"]
35065
+ },
35066
+ {
35067
+ re: /(?<rn>서울|부산|경기|강원|충북|충남|전북|전남|경북|경남|제주|대구|인천|광주|대전|울산) ?(?<y>\d{2})-(?<s>\d{6})-(?<c>\d{2})(?![\w-])/dg,
35068
+ mask: ["s", "c"]
35069
+ },
35070
+ {
35071
+ re: /(?<![\w@.-])(?<r>1[1-9]|2[0-8])(?<y>\d{2})(?<s>\d{6})(?<c>\d{2})(?![\w@-])/dg,
35072
+ needs: "driver",
35073
+ mask: ["s", "c"]
35074
+ }
35075
+ ],
35076
+ // 여권번호 — 신형 복수여권(M123A4567)은 무문맥, 그 밖의 종류 글자(S·R·O·D·G)와 구형(M12345678)은 여권 라벨
35077
+ // 필요 ("S123A4567 부품" 같은 제품 코드). 첫 글자만 남김
35078
+ passport: [
35079
+ {
35080
+ re: /(?<![A-Za-z0-9])(?<l>[MSRODG])(?<num>\d{3}[A-Z]\d{4})(?![A-Za-z0-9])/dg,
35081
+ ok: (x) => has(x, "passport") || g(x, "l") === "M" && !otherCtx(x, "passport"),
35082
+ mask: ["num"]
35083
+ },
35084
+ {
35085
+ re: /(?<![A-Za-z0-9])(?<l>[MSRODG])(?<num>\d{8})(?![A-Za-z0-9])/dg,
35086
+ needs: "passport",
35087
+ mask: ["num"]
35088
+ },
35089
+ {
35090
+ // 소문자로 친 여권번호(m123a4567) — 여권 라벨이 있을 때만
35091
+ re: /(?<![A-Za-z0-9])(?<l>[msrodg])(?<num>\d{3}[a-z]\d{4}|\d{8})(?![A-Za-z0-9])/dg,
35092
+ needs: "passport",
35093
+ mask: ["num"]
35094
+ }
35095
+ ],
35096
+ // 계좌번호 — 하이픈 3~4그룹, 총 11~16자리 (10자리는 계좌 라벨 필요). 마지막 그룹 빼고 마스크.
35097
+ // 무구분 10~14자리는 계좌·은행 라벨이 있을 때만 (뒤 4자리 유지)
35098
+ account: [
35099
+ {
35100
+ // 마지막 그룹 1자리 허용 — 새마을금고 9002-1234-5678-9, 3-2-6-1·3-3-6-1 형식
35101
+ re: /(?<![\w-])(?<!\d\.)(?<head>\d{2,6}(?:-\d{2,6}){1,2})-(?<last>\d{1,8})(?![\w-])(?!\.\d)/dg,
35102
+ ok: (x) => {
35103
+ const s = x.m[0];
35104
+ const d = digitsOf(s);
35105
+ const acct = has(x, "account");
35106
+ if (allSameDigit(d) || !acct && otherCtx(x, "account")) return false;
35107
+ if (!acct && (/\d,$/.test(before(x)) || /^,\d/.test(after(x, 2)))) return false;
35108
+ if (/^\d+(?:-0+)+$/.test(s)) return false;
35109
+ if (d.length < 10 || d.length > 16 || d.length === 10 && !acct) return false;
35110
+ if (/^\s*원/.test(after(x, 3))) return false;
35111
+ if (acct) return true;
35112
+ if (/^(?:10|20|30|40)-(?:19|20)\d\d-\d{6,7}$/.test(s)) return false;
35113
+ if (/^(?:19|20)\d\d-(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])?-/.test(s)) return false;
35114
+ if (s.split("-").filter((p) => /^(?:19|20)\d\d$/.test(p)).length >= 2) return false;
35115
+ if (/^\d{3}-\d{2}-\d{5}$/.test(s) && brnChecksumValid(d)) return false;
35116
+ return true;
35117
+ },
35118
+ mask: ["head"]
35119
+ },
35120
+ {
35121
+ re: /(?<![\w@.-])(?<!\d,)(?<head>\d{6,10})(?<last>\d{4})(?![\w@-])(?!,\d)/dg,
35122
+ needs: "account",
35123
+ ok: (x) => !allSameDigit(x.m[0]),
35124
+ mask: ["head"]
35125
+ }
35126
+ ],
35127
+ // IPv4 (opt-in) — 옥텟 0~255, 첫 옥텟 10 이상(1.2.3.4 절 번호·버전 제외) 또는 IP 라벨. 뒤 두 옥텟 마스크
35128
+ ip: [
35129
+ {
35130
+ re: /(?<![\w.])(?<a>\d{1,3})\.(?<b>\d{1,3})\.(?<c>\d{1,3})\.(?<d>\d{1,3})(?![\w])(?!\.\d)/dg,
35131
+ ok: (x) => {
35132
+ const oct = [g(x, "a"), g(x, "b"), g(x, "c"), g(x, "d")];
35133
+ if (oct.some((o) => Number(o) > 255 || o.length > 1 && o[0] === "0")) return false;
35134
+ return Number(oct[0]) >= 10 || has(x, "ip");
35135
+ },
35136
+ mask: ["c", "d"]
35137
+ }
35138
+ ]
34128
35139
  };
34129
- function redactText(text, options) {
34130
- const maskChar = options?.maskChar ?? "\u25CF";
34131
- if (maskChar.length !== 1 || /[0-9A-Za-z]/.test(maskChar)) {
34132
- throw new Error(`maskChar\uB294 \uC601\uC22B\uC790\uAC00 \uC544\uB2CC 1\uAE00\uC790\uC5EC\uC57C \uD568: ${JSON.stringify(maskChar)}`);
34133
- }
34134
- const enabled = options?.rules ?? DEFAULT_REDACT_RULES;
35140
+
35141
+ // src/redact.ts
35142
+ var DEFAULT_REDACT_RULES = [
35143
+ "rrn",
35144
+ "phone",
35145
+ "email",
35146
+ "card",
35147
+ "account",
35148
+ "brn",
35149
+ "passport",
35150
+ "driver"
35151
+ ];
35152
+ var ALL_REDACT_RULES = [...DEFAULT_REDACT_RULES, "crn", "ip"];
35153
+ var DASHES = /* @__PURE__ */ new Set([8208, 8209, 8210, 8211, 8212, 8213, 8722, 65112, 65123, 65293, 9472, 9473, 12641, 12540]);
35154
+ var SPACES = /* @__PURE__ */ new Set([160, 8194, 8195, 8196, 8197, 8198, 8199, 8200, 8201, 8202, 8239, 12288]);
35155
+ function normalizeForDetect(text) {
35156
+ let out = "";
35157
+ let changed = false;
35158
+ for (let i = 0; i < text.length; i++) {
35159
+ const c = text.charCodeAt(i);
35160
+ let r = c;
35161
+ if (c >= 65281 && c <= 65374) r = c - 65248;
35162
+ if (DASHES.has(c)) r = 45;
35163
+ else if (SPACES.has(c)) r = 32;
35164
+ if (r !== c) changed = true;
35165
+ out += r === c ? text[i] : String.fromCharCode(r);
35166
+ }
35167
+ return changed ? out : text;
35168
+ }
35169
+ function maskMatch(orig, x, v, maskChar) {
35170
+ const base = x.m.index;
35171
+ const norm2 = x.m[0];
35172
+ const chars = orig.split("");
35173
+ const idx = x.m.indices?.groups;
35174
+ for (const name of v.mask) {
35175
+ const r = idx?.[name];
35176
+ if (!r) continue;
35177
+ for (let i = r[0] - base; i < r[1] - base; i++) {
35178
+ if (v.maskAll || /[0-9A-Za-z]/.test(norm2[i])) chars[i] = maskChar;
35179
+ }
35180
+ }
35181
+ return chars.join("");
35182
+ }
35183
+ function assertMaskChar(maskChar) {
35184
+ const n = normalizeForDetect(maskChar);
35185
+ if (maskChar.length !== 1 || /[0-9A-Za-z|\\<>&"'._\-@+:/,;]/.test(n) || /[\p{Z}\p{C}\p{M}]/u.test(maskChar) || /[\uFDD0-\uFDEF\uFFFE\uFFFF]/.test(maskChar)) {
35186
+ throw new Error(`maskChar\uB294 \uC601\uC22B\uC790\xB7\uACF5\uBC31\xB7\uC81C\uC5B4/\uACB0\uD569 \uBB38\uC790\xB7\uAD6C\uBD84 \uAE30\uD638(|\\<>&"'._-@+:/,;)\uAC00 \uC544\uB2CC \uBCF4\uC774\uB294 1\uAE00\uC790\uC5EC\uC57C \uD568: ${JSON.stringify(maskChar)}`);
35187
+ }
35188
+ }
35189
+ var LIST_SEP_RE = /^\s{0,2}(?:[,;/·、]|및|또는)?\s{0,2}$/;
35190
+ function detect(text, rules, maskChar, headerAt) {
35191
+ const norm2 = normalizeForDetect(text);
34135
35192
  const hits = [];
34136
- if (text === "" || enabled.length === 0) return { text, hits };
34137
35193
  const occupied = [];
34138
35194
  for (const rule of RULE_PRIORITY) {
34139
- if (!enabled.includes(rule)) continue;
34140
- const def = RULES2[rule];
34141
- for (const m of text.matchAll(def.pattern)) {
34142
- if (def.validate && !def.validate(m)) continue;
34143
- const start = m.index;
34144
- const end = start + m[0].length;
34145
- if (occupied.some((o) => start < o.end && end > o.start)) continue;
34146
- occupied.push({ start, end });
34147
- hits.push({ rule, masked: def.mask(m, maskChar), index: start, length: m[0].length });
34148
- }
34149
- }
34150
- hits.sort((a, b) => a.index - b.index);
35195
+ if (!rules.includes(rule)) continue;
35196
+ for (const v of RULES2[rule]) {
35197
+ for (const m of norm2.matchAll(v.re)) {
35198
+ const start = m.index;
35199
+ const end = start + m[0].length;
35200
+ if (occupied.some((o) => start < o.end && end > o.start)) continue;
35201
+ let cached;
35202
+ const ctx = () => {
35203
+ if (!cached) {
35204
+ let c = labelsBefore(norm2, start);
35205
+ if (c.size === 0) {
35206
+ const prev = occupied.find((o) => o.end < start && start - o.end <= 6 && LIST_SEP_RE.test(norm2.slice(o.end, start)));
35207
+ if (prev) c = prev.rule === "email" ? prev.ctx() : /* @__PURE__ */ new Set([...prev.ctx(), prev.rule]);
35208
+ }
35209
+ if (c.size === 0 && headerAt) {
35210
+ const h = headerAt(start);
35211
+ if (h) c = labelsIn(normalizeForDetect(h));
35212
+ }
35213
+ cached = c;
35214
+ }
35215
+ return cached;
35216
+ };
35217
+ const x = { m, ctx };
35218
+ if (v.needs && !ctx().has(v.needs)) continue;
35219
+ if (v.ok && !v.ok(x)) continue;
35220
+ occupied.push({ start, end, ctx, rule });
35221
+ hits.push({ rule, masked: maskMatch(text.slice(start, end), x, v, maskChar), index: start, length: end - start });
35222
+ }
35223
+ }
35224
+ }
35225
+ return hits.sort((a, b) => a.index - b.index);
35226
+ }
35227
+ function applyHits(text, hits) {
34151
35228
  let out = "";
34152
35229
  let cursor = 0;
34153
35230
  for (const h of hits) {
34154
35231
  out += text.slice(cursor, h.index) + h.masked;
34155
35232
  cursor = h.index + h.length;
34156
35233
  }
34157
- out += text.slice(cursor);
34158
- return { text: out, hits };
35234
+ return out + text.slice(cursor);
35235
+ }
35236
+ function redactText(text, options) {
35237
+ const maskChar = options?.maskChar ?? "\u25CF";
35238
+ assertMaskChar(maskChar);
35239
+ const rules = options?.rules ?? DEFAULT_REDACT_RULES;
35240
+ if (text === "" || rules.length === 0) return { text, hits: [] };
35241
+ const hits = detect(text, rules, maskChar);
35242
+ return { text: applyHits(text, hits), hits };
35243
+ }
35244
+ function pipePositions(line) {
35245
+ const out = [];
35246
+ for (let i = 0; i < line.length; i++) {
35247
+ if (line[i] === "|" && line[i - 1] !== "\\") out.push(i);
35248
+ }
35249
+ return out;
35250
+ }
35251
+ var isGfmRow = (l) => /^\s*\|.*\|\s*$/.test(l);
35252
+ var isGfmSep = (l) => /^\s*\|(?:\s*:?-{3,}:?\s*\|)+\s*$/.test(l);
35253
+ function htmlCells(line) {
35254
+ const out = [];
35255
+ let col = 0;
35256
+ for (const m of line.matchAll(/<t([dh])\b([^>]*)>([\s\S]*?)<\/t\1>/gi)) {
35257
+ const span = Math.max(1, Number(m[2].match(/colspan="?(\d+)/i)?.[1] ?? 1));
35258
+ out.push({ start: m.index, end: m.index + m[0].length, col, span, text: m[3].replace(/<[^>]*>/g, " ") });
35259
+ col += span;
35260
+ }
35261
+ return out;
35262
+ }
35263
+ function tableHeaderContext(lines) {
35264
+ const out = /* @__PURE__ */ new Map();
35265
+ for (let i = 0; i < lines.length; i++) {
35266
+ if (isGfmRow(lines[i]) && i + 1 < lines.length && isGfmSep(lines[i + 1])) {
35267
+ const head = lines[i];
35268
+ const hp = pipePositions(head);
35269
+ const headers = hp.slice(0, -1).map((p, k) => head.slice(p + 1, hp[k + 1]));
35270
+ let j = i + 2;
35271
+ for (; j < lines.length && isGfmRow(lines[j]); j++) {
35272
+ const rp = pipePositions(lines[j]);
35273
+ out.set(j, (off) => {
35274
+ let k = -1;
35275
+ for (const p of rp) {
35276
+ if (p < off) k++;
35277
+ else break;
35278
+ }
35279
+ return k >= 0 ? headers[k] : void 0;
35280
+ });
35281
+ }
35282
+ i = j - 1;
35283
+ continue;
35284
+ }
35285
+ if (/<table\b/i.test(lines[i])) {
35286
+ let headers = null;
35287
+ let depth = 0;
35288
+ for (let j = i; j < lines.length; j++) {
35289
+ depth += (lines[j].match(/<table\b/gi) ?? []).length;
35290
+ if (/<tr\b/i.test(lines[j]) && depth === 1) {
35291
+ const cells = htmlCells(lines[j]);
35292
+ if (!headers) headers = cells;
35293
+ else if (cells.length > 0) {
35294
+ const hdr = headers;
35295
+ out.set(j, (off) => {
35296
+ const c = cells.find((x) => off >= x.start && off < x.end);
35297
+ if (!c) return void 0;
35298
+ const h = hdr.find((x) => c.col >= x.col && c.col < x.col + x.span);
35299
+ return h?.text;
35300
+ });
35301
+ }
35302
+ }
35303
+ depth -= (lines[j].match(/<\/table>/gi) ?? []).length;
35304
+ if (depth <= 0) {
35305
+ i = j;
35306
+ break;
35307
+ }
35308
+ }
35309
+ }
35310
+ }
35311
+ return out;
35312
+ }
35313
+ var MD_ALWAYS_MARKER_RE = new RegExp([
35314
+ "(?<!\\*)\\*\\*(?!\\*)",
35315
+ "(?<!_)__(?!_)",
35316
+ "(?<!~)~~(?!~)",
35317
+ "<\\/?(?:u|b|i|s|em|strong|sup|sub|del|span|mark|small|big|ins|strike|tt|code|kbd|samp|var|q|cite|abbr|dfn)>",
35318
+ "\\\\(?=[\\\\`*_{}[\\]()#+\\-.!|~>])"
35319
+ ].join("|"), "g");
35320
+ var MD_OPEN_RE = /(?<![\w*\\])(\*\*\*|\*|_)(?=[0-9A-Za-z0-9])/g;
35321
+ var MD_CLOSE_RE = /(?<=[0-9A-Za-z0-9])(\*\*\*|\*|_)(?![\w*])/g;
35322
+ function mdMarkerRanges(line) {
35323
+ const out = [];
35324
+ for (const m of line.matchAll(MD_ALWAYS_MARKER_RE)) out.push([m.index, m.index + m[0].length]);
35325
+ const taken = (a, b) => out.some(([s, e]) => a < e && b > s);
35326
+ const closes = [...line.matchAll(MD_CLOSE_RE)].filter((m) => !taken(m.index, m.index + m[0].length));
35327
+ let from = 0;
35328
+ for (const o of line.matchAll(MD_OPEN_RE)) {
35329
+ const os = o.index;
35330
+ const oe = os + o[0].length;
35331
+ if (os < from || taken(os, oe)) continue;
35332
+ const c = closes.find((m) => m.index >= oe && m[0] === o[0]);
35333
+ if (!c) continue;
35334
+ out.push([os, oe], [c.index, c.index + c[0].length]);
35335
+ from = c.index + c[0].length;
35336
+ }
35337
+ return out.sort((a, b) => a[0] - b[0]);
35338
+ }
35339
+ function detectMarkdownLine(line, rules, maskChar, headerAt) {
35340
+ const markers = mdMarkerRanges(line);
35341
+ if (markers.length === 0) return detect(line, rules, maskChar, headerAt);
35342
+ let clean = "";
35343
+ const map = [];
35344
+ let cursor = 0;
35345
+ const keep = (to) => {
35346
+ for (let i = cursor; i < to; i++) {
35347
+ clean += line[i];
35348
+ map.push(i);
35349
+ }
35350
+ };
35351
+ for (const [ms, me] of markers) {
35352
+ keep(ms);
35353
+ cursor = me;
35354
+ }
35355
+ keep(line.length);
35356
+ const hits = detect(clean, rules, maskChar, headerAt ? (i) => headerAt(map[i] ?? i) : void 0);
35357
+ return hits.map((h) => {
35358
+ const start = map[h.index];
35359
+ const end = map[h.index + h.length - 1] + 1;
35360
+ const chars = line.slice(start, end).split("");
35361
+ for (let k = 0; k < h.length; k++) {
35362
+ if (h.masked[k] !== clean[h.index + k]) chars[map[h.index + k] - start] = maskChar;
35363
+ }
35364
+ return { rule: h.rule, masked: chars.join(""), index: start, length: end - start };
35365
+ });
34159
35366
  }
34160
35367
  function redactMarkdown(markdown, options) {
35368
+ const maskChar = options?.maskChar ?? "\u25CF";
35369
+ assertMaskChar(maskChar);
35370
+ const rules = options?.rules ?? DEFAULT_REDACT_RULES;
34161
35371
  const lines = markdown.split("\n");
35372
+ const headers = tableHeaderContext(lines);
34162
35373
  const hits = [];
34163
35374
  let offset = 0;
34164
- const outLines = lines.map((line) => {
34165
- if (line.includes("data:image/")) {
35375
+ const outLines = lines.map((line, i) => {
35376
+ if (line.includes("data:image/") || line === "" || rules.length === 0) {
34166
35377
  offset += line.length + 1;
34167
35378
  return line;
34168
35379
  }
34169
- const r = redactText(line, options);
34170
- for (const h of r.hits) hits.push({ ...h, index: h.index + offset });
35380
+ const lineHits = detectMarkdownLine(line, rules, maskChar, headers.get(i));
35381
+ for (const h of lineHits) hits.push({ ...h, index: h.index + offset });
34171
35382
  offset += line.length + 1;
34172
- return r.text;
35383
+ return applyHits(line, lineHits);
34173
35384
  });
34174
35385
  return { text: outLines.join("\n"), hits };
34175
35386
  }
@@ -34534,6 +35745,11 @@ var HwpxSession = class _HwpxSession {
34534
35745
  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)");
34535
35746
  }
34536
35747
  let newPlain = newTextRaw.split("\n").map((l) => l.trim()).filter(Boolean).join(" ");
35748
+ if (mapping.noteMarks?.length) {
35749
+ const r = removeNoteMarks(newPlain, mapping.noteMarks);
35750
+ newPlain = r.text;
35751
+ 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(" ") });
35752
+ }
34537
35753
  if (mapping.prefixStripped) {
34538
35754
  const origPrefix = block.text.split(" ", 1)[0];
34539
35755
  const sp = newPlain.indexOf(" ");
@@ -35180,8 +36396,8 @@ function reflowBlockFlow(container, doc, styles, areaW, mode, counter, bodyH) {
35180
36396
  for (const p of elements2(container)) {
35181
36397
  if (ln2(p) !== "p") continue;
35182
36398
  reflowTablesIn(p, doc, styles, mode, counter);
35183
- const g = styles.paraGeom.get(p.getAttribute("paraPrIDRef") ?? "");
35184
- const startV = cursorV + prevSpaceAfter + (g?.spaceBefore ?? 0);
36399
+ const g2 = styles.paraGeom.get(p.getAttribute("paraPrIDRef") ?? "");
36400
+ const startV = cursorV + prevSpaceAfter + (g2?.spaceBefore ?? 0);
35185
36401
  const res = reflowPara(p, doc, styles, areaW, startV, mode);
35186
36402
  if (!res) {
35187
36403
  const lsa = findChildByLocalName(p, "linesegarray");
@@ -35194,7 +36410,7 @@ function reflowBlockFlow(container, doc, styles, areaW, mode, counter, bodyH) {
35194
36410
  }
35195
36411
  if (Number.isFinite(bottom)) {
35196
36412
  cursorV = bottom;
35197
- prevSpaceAfter = g?.spaceAfter ?? 0;
36413
+ prevSpaceAfter = g2?.spaceAfter ?? 0;
35198
36414
  }
35199
36415
  }
35200
36416
  continue;
@@ -35626,14 +36842,14 @@ function drawTable(tbl2, tx, ty, ctx, depth) {
35626
36842
  w: colX[Math.min(c.ca + c.cs, nCols)] - colX[c.ca],
35627
36843
  h: rowY[Math.min(c.ra + c.rs, nRows)] - rowY[c.ra]
35628
36844
  }));
35629
- for (const g of geom) {
35630
- const bf = g.c.bfId != null ? ctx.styles.borderFill.get(g.c.bfId) : void 0;
35631
- if (bf?.fill) emit(ctx, `<rect x="${pt(g.x)}" y="${pt(g.y)}" width="${pt(g.w)}" height="${pt(g.h)}" fill="${escapeXml7(bf.fill)}"/>`);
36845
+ for (const g2 of geom) {
36846
+ const bf = g2.c.bfId != null ? ctx.styles.borderFill.get(g2.c.bfId) : void 0;
36847
+ if (bf?.fill) emit(ctx, `<rect x="${pt(g2.x)}" y="${pt(g2.y)}" width="${pt(g2.w)}" height="${pt(g2.h)}" fill="${escapeXml7(bf.fill)}"/>`);
35632
36848
  }
35633
- for (const g of geom) {
35634
- const { c } = g;
36849
+ for (const g2 of geom) {
36850
+ const { c } = g2;
35635
36851
  if (!c.sub) continue;
35636
- const innerH = g.h - c.marginT - c.marginB;
36852
+ const innerH = g2.h - c.marginT - c.marginB;
35637
36853
  const extent = cellContentExtent(c, ctx.extentMemo);
35638
36854
  const va = c.sub.getAttribute("vertAlign") ?? "TOP";
35639
36855
  let yoff = 0;
@@ -35641,16 +36857,16 @@ function drawTable(tbl2, tx, ty, ctx, depth) {
35641
36857
  else if (va === "BOTTOM") yoff = Math.max(0, innerH - extent);
35642
36858
  for (const p of elements(c.sub)) {
35643
36859
  if (ln(p) !== "p") continue;
35644
- drawPara(p, g.x + c.marginL, g.y + c.marginT + yoff, g.w - c.marginL - c.marginR, ctx, depth + 1);
36860
+ drawPara(p, g2.x + c.marginL, g2.y + c.marginT + yoff, g2.w - c.marginL - c.marginR, ctx, depth + 1);
35645
36861
  }
35646
36862
  }
35647
- for (const g of geom) {
35648
- const bf = g.c.bfId != null ? ctx.styles.borderFill.get(g.c.bfId) : void 0;
36863
+ for (const g2 of geom) {
36864
+ const bf = g2.c.bfId != null ? ctx.styles.borderFill.get(g2.c.bfId) : void 0;
35649
36865
  if (!bf) continue;
35650
- if (bf.top) emit(ctx, edgeLine(g.x, g.y, g.x + g.w, g.y, bf.top));
35651
- if (bf.bottom) emit(ctx, edgeLine(g.x, g.y + g.h, g.x + g.w, g.y + g.h, bf.bottom));
35652
- if (bf.left) emit(ctx, edgeLine(g.x, g.y, g.x, g.y + g.h, bf.left));
35653
- if (bf.right) emit(ctx, edgeLine(g.x + g.w, g.y, g.x + g.w, g.y + g.h, bf.right));
36866
+ if (bf.top) emit(ctx, edgeLine(g2.x, g2.y, g2.x + g2.w, g2.y, bf.top));
36867
+ if (bf.bottom) emit(ctx, edgeLine(g2.x, g2.y + g2.h, g2.x + g2.w, g2.y + g2.h, bf.bottom));
36868
+ if (bf.left) emit(ctx, edgeLine(g2.x, g2.y, g2.x, g2.y + g2.h, bf.left));
36869
+ if (bf.right) emit(ctx, edgeLine(g2.x + g2.w, g2.y, g2.x + g2.w, g2.y + g2.h, bf.right));
35654
36870
  }
35655
36871
  ctx.parentStack.pop();
35656
36872
  emit(ctx, "</g>");
@@ -36414,11 +37630,11 @@ function shapeElement(records, sc, scIdx, end, posXml, drawText, ctx, wrap) {
36414
37630
  return `<hp:line${wa}>${posXml}${sizes}${line}${pts}</hp:line>`;
36415
37631
  }
36416
37632
  if (sc.chid === CHID_POL || sc.chid === CHID_CUR) {
36417
- const g = find(sc.chid === CHID_POL ? TAG_SHAPE_COMPONENT_POLYGON : TAG_SHAPE_COMPONENT_CURVE);
37633
+ const g2 = find(sc.chid === CHID_POL ? TAG_SHAPE_COMPONENT_POLYGON : TAG_SHAPE_COMPONENT_CURVE);
36418
37634
  let pts = "";
36419
- if (g && g.length >= 2) {
36420
- const n = Math.min(g.readInt16LE(0), 4096);
36421
- for (let k = 0; k < n && 2 + k * 8 + 8 <= g.length; k++) pts += `<hp:pt x="${g.readInt32LE(2 + k * 8)}" y="${g.readInt32LE(6 + k * 8)}"/>`;
37635
+ if (g2 && g2.length >= 2) {
37636
+ const n = Math.min(g2.readInt16LE(0), 4096);
37637
+ for (let k = 0; k < n && 2 + k * 8 + 8 <= g2.length; k++) pts += `<hp:pt x="${g2.readInt32LE(2 + k * 8)}" y="${g2.readInt32LE(6 + k * 8)}"/>`;
36422
37638
  }
36423
37639
  const tag2 = sc.chid === CHID_POL ? "polygon" : "curv";
36424
37640
  return `<hp:${tag2}${wa}>${posXml}${sizes}${line}${pts}${drawText}</hp:${tag2}>`;
@@ -36809,6 +38025,10 @@ async function parse(input, options) {
36809
38025
  }
36810
38026
  const format = detectFormat(buffer);
36811
38027
  const result = await dispatch(format, buffer, opts);
38028
+ if (result.success && opts?.images === false) {
38029
+ delete result.images;
38030
+ dropImageBytes(result.blocks);
38031
+ }
36812
38032
  if (result.success && opts?.classifyTables) classifyTableTree(result.blocks);
36813
38033
  if (result.success && !result.pages) {
36814
38034
  const pages = blocksToPages(result.blocks);
@@ -36816,6 +38036,17 @@ async function parse(input, options) {
36816
38036
  }
36817
38037
  return result;
36818
38038
  }
38039
+ function dropImageBytes(blocks, depth = 0) {
38040
+ if (!blocks || depth > 64) return;
38041
+ for (const b of blocks) {
38042
+ if (b.imageData) delete b.imageData;
38043
+ dropImageBytes(b.children, depth + 1);
38044
+ if (b.table) {
38045
+ for (const row of b.table.cells) for (const cell2 of row) dropImageBytes(cell2.blocks, depth + 1);
38046
+ dropImageBytes(b.table.captionBlocks, depth + 1);
38047
+ }
38048
+ }
38049
+ }
36819
38050
  async function dispatch(format, buffer, opts) {
36820
38051
  switch (format) {
36821
38052
  case "hwpx": {
@@ -36846,7 +38077,7 @@ async function dispatch(format, buffer, opts) {
36846
38077
  }
36847
38078
  async function parseImage(buffer, options) {
36848
38079
  try {
36849
- const { parseImageDocument } = await import("./image-ocr-FLIL7MGN.js");
38080
+ const { parseImageDocument } = await import("./image-ocr-R6W6A75P.js");
36850
38081
  const { blocks, warnings } = await parseImageDocument(buffer, options);
36851
38082
  return {
36852
38083
  success: true,
@@ -36905,7 +38136,7 @@ async function parseHwp(buffer, options) {
36905
38136
  async function parsePdf(buffer, options) {
36906
38137
  let parsePdfDocument;
36907
38138
  try {
36908
- const mod = await import("./parser-A4TMPJAM.js");
38139
+ const mod = await import("./parser-HWFQRSUQ.js");
36909
38140
  parsePdfDocument = mod.parsePdfDocument;
36910
38141
  } catch {
36911
38142
  return {
@@ -37030,6 +38261,7 @@ export {
37030
38261
  fillHwpx,
37031
38262
  fillWithUniqueGuard,
37032
38263
  fitRatioForFewerLines,
38264
+ flattenLayoutTables,
37033
38265
  formatFillValue,
37034
38266
  gongmunLintWarnings,
37035
38267
  hasStructuredCellContent,