kordoc 4.16.2 → 4.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/README.md +14 -13
  2. package/dist/{-4G6MA4X6.js → -PRAKO6M4.js} +11 -11
  3. package/dist/{chunk-AUWGDP4G.js → chunk-2KF5WZCR.js} +2 -2
  4. package/dist/{chunk-UTP2WCHS.js → chunk-4GSF2R7T.js} +4 -4
  5. package/dist/{chunk-TXUKTOG2.js → chunk-4XVNQAK5.js} +219 -23
  6. package/dist/chunk-4XVNQAK5.js.map +1 -0
  7. package/dist/chunk-5CDUL6PH.js +250 -0
  8. package/dist/chunk-5CDUL6PH.js.map +1 -0
  9. package/dist/{chunk-4VODJKFL.js → chunk-6RGGYQQC.js} +3 -3
  10. package/dist/{chunk-DES2LGFZ.js → chunk-A55JGNKD.js} +2 -2
  11. package/dist/chunk-AKB72TEH.cjs +248 -0
  12. package/dist/chunk-AKB72TEH.cjs.map +1 -0
  13. package/dist/{chunk-LEIAEWZ7.js → chunk-BFT5IM2R.js} +2 -2
  14. package/dist/{chunk-KIOQ4N37.js → chunk-BLPR4HRF.js} +2 -2
  15. package/dist/{chunk-JELRWAZC.js → chunk-BZG7K44Z.js} +2 -2
  16. package/dist/{chunk-F4NRLLY4.js → chunk-EFO6JZY6.js} +2 -2
  17. package/dist/chunk-GS7T56RP.cjs.map +1 -1
  18. package/dist/{chunk-JFLAM3MM.js → chunk-I5DJOP62.js} +2 -2
  19. package/dist/chunk-I5DJOP62.js.map +1 -0
  20. package/dist/chunk-J55UQC5H.cjs.map +1 -1
  21. package/dist/{chunk-QOCS7W7R.js → chunk-K2VYABZ3.js} +86 -43
  22. package/dist/chunk-K2VYABZ3.js.map +1 -0
  23. package/dist/chunk-MJBCXW5R.js +248 -0
  24. package/dist/chunk-MJBCXW5R.js.map +1 -0
  25. package/dist/{chunk-MLIS253L.js → chunk-ML4NHUR7.js} +2 -2
  26. package/dist/{chunk-GO5IFOUF.js → chunk-N6V7LQBL.js} +2 -2
  27. package/dist/{chunk-FKUDAIJI.cjs → chunk-O7OUX6NU.cjs} +12 -12
  28. package/dist/chunk-O7OUX6NU.cjs.map +1 -0
  29. package/dist/{chunk-S2PL227Q.js → chunk-PHID6WNG.js} +2 -2
  30. package/dist/{chunk-IQGIT2VX.js → chunk-QJWVMPB6.js} +34 -17
  31. package/dist/chunk-QJWVMPB6.js.map +1 -0
  32. package/dist/{chunk-STM3ORY7.cjs → chunk-QNJLLF4J.cjs} +2 -2
  33. package/dist/chunk-QNJLLF4J.cjs.map +1 -0
  34. package/dist/{chunk-FSDJLAOW.js → chunk-QPJXYJIK.js} +55 -1
  35. package/dist/chunk-QPJXYJIK.js.map +1 -0
  36. package/dist/{chunk-XTFN5WKS.cjs → chunk-R2BUANHQ.cjs} +6 -6
  37. package/dist/chunk-R2BUANHQ.cjs.map +1 -0
  38. package/dist/{chunk-OXLPVQYK.js → chunk-TDQ6XDB3.js} +3 -3
  39. package/dist/{chunk-SFEQYKYJ.js → chunk-WN6OD2CQ.js} +218 -22
  40. package/dist/chunk-WN6OD2CQ.js.map +1 -0
  41. package/dist/{chunk-5KS5PWJR.js → chunk-WY5JAR4O.js} +2 -2
  42. package/dist/{chunk-CEIATRDW.cjs → chunk-WYON5ANU.cjs} +3 -3
  43. package/dist/chunk-WYON5ANU.cjs.map +1 -0
  44. package/dist/{chunk-WDQTFAXY.cjs → chunk-XEPKOC2O.cjs} +240 -44
  45. package/dist/chunk-XEPKOC2O.cjs.map +1 -0
  46. package/dist/{chunk-7KNGSQU7.js → chunk-YZ5HHS6A.js} +2 -2
  47. package/dist/{chunk-P6ZDXTV6.js → chunk-ZF6KHPB7.js} +2 -2
  48. package/dist/chunks-YBFBLJ4G.js +10 -0
  49. package/dist/cli.js +36 -54
  50. package/dist/cli.js.map +1 -1
  51. package/dist/{formula-NARUOXRX.js → formula-7MMFBRGJ.js} +4 -4
  52. package/dist/{formula-WIMV5TUX.cjs → formula-ECNOZT36.cjs} +9 -9
  53. package/dist/formula-ECNOZT36.cjs.map +1 -0
  54. package/dist/{formula-EPKLPDU7.js → formula-SHBXRXOJ.js} +3 -3
  55. package/dist/{image-ocr-3WJ4QH3T.js → image-ocr-P6ECJCT2.js} +8 -8
  56. package/dist/{image-ocr-5BUJQ4TK.js → image-ocr-QCT3A7X5.js} +6 -6
  57. package/dist/{image-ocr-AXWXR6JV.cjs → image-ocr-VUDSP2B4.cjs} +15 -15
  58. package/dist/image-ocr-VUDSP2B4.cjs.map +1 -0
  59. package/dist/index.cjs +768 -710
  60. package/dist/index.cjs.map +1 -1
  61. package/dist/index.d.cts +4 -0
  62. package/dist/index.d.ts +4 -0
  63. package/dist/index.js +106 -48
  64. package/dist/index.js.map +1 -1
  65. package/dist/mcp.js +38 -43
  66. package/dist/mcp.js.map +1 -1
  67. package/dist/{model-bundle-IMBW7D2Z.js → model-bundle-OUHGCEZY.js} +5 -5
  68. package/dist/{models-SY75IBOE.js → models-6D6LMOTH.js} +5 -5
  69. package/dist/page-range-QHHFTL7X.cjs.map +1 -1
  70. package/dist/{parser-ZQM3664Z.js → parser-CTRB7PKD.js} +257 -46
  71. package/dist/parser-CTRB7PKD.js.map +1 -0
  72. package/dist/{parser-2LU4EVU3.cjs → parser-RQ4PJRTJ.cjs} +411 -200
  73. package/dist/parser-RQ4PJRTJ.cjs.map +1 -0
  74. package/dist/{parser-N6476XAC.js → parser-ZPRQ3SSU.js} +260 -49
  75. package/dist/parser-ZPRQ3SSU.js.map +1 -0
  76. package/dist/pdf-ocr-462ZFSHX.js +15 -0
  77. package/dist/pdf-ocr-L35FQYGP.js +18 -0
  78. package/dist/pdf-ocr-STK5EQFF.cjs +16 -0
  79. package/dist/pdf-ocr-STK5EQFF.cjs.map +1 -0
  80. package/dist/{profile-io-GJPKCYPT.js → profile-io-YZUW6MAN.js} +3 -3
  81. package/dist/{rasterize-UOALJQDS.js → rasterize-OUR2QJXX.js} +3 -3
  82. package/dist/{redact-doc-22SYW3KH.js → redact-doc-4OQKG4SM.js} +11 -11
  83. package/dist/{render-MJ4H3R23.js → render-XNO6PZJO.js} +7 -7
  84. package/dist/seal-CJ7C44JN.js +10 -0
  85. package/dist/{setup-2URYVUEO.js → setup-UU3EIHNI.js} +3 -3
  86. package/dist/visual-QRMRFPUK.js +20 -0
  87. package/dist/{watch-ALXZGTOB.js → watch-SR6Q5SQN.js} +12 -12
  88. package/package.json +2 -2
  89. package/dist/chunk-3IB7LBHC.js +0 -57
  90. package/dist/chunk-3IB7LBHC.js.map +0 -1
  91. package/dist/chunk-62W7QCEX.cjs +0 -57
  92. package/dist/chunk-62W7QCEX.cjs.map +0 -1
  93. package/dist/chunk-CEIATRDW.cjs.map +0 -1
  94. package/dist/chunk-FKUDAIJI.cjs.map +0 -1
  95. package/dist/chunk-FSDJLAOW.js.map +0 -1
  96. package/dist/chunk-IQGIT2VX.js.map +0 -1
  97. package/dist/chunk-J3IF6KGW.js +0 -59
  98. package/dist/chunk-J3IF6KGW.js.map +0 -1
  99. package/dist/chunk-JFLAM3MM.js.map +0 -1
  100. package/dist/chunk-QOCS7W7R.js.map +0 -1
  101. package/dist/chunk-SFEQYKYJ.js.map +0 -1
  102. package/dist/chunk-STM3ORY7.cjs.map +0 -1
  103. package/dist/chunk-TXUKTOG2.js.map +0 -1
  104. package/dist/chunk-WDQTFAXY.cjs.map +0 -1
  105. package/dist/chunk-XTFN5WKS.cjs.map +0 -1
  106. package/dist/chunks-NUNIKS2J.js +0 -10
  107. package/dist/formula-WIMV5TUX.cjs.map +0 -1
  108. package/dist/image-ocr-AXWXR6JV.cjs.map +0 -1
  109. package/dist/parser-2LU4EVU3.cjs.map +0 -1
  110. package/dist/parser-N6476XAC.js.map +0 -1
  111. package/dist/parser-ZQM3664Z.js.map +0 -1
  112. package/dist/pdf-ocr-6DEE23IP.js +0 -15
  113. package/dist/pdf-ocr-GHEJVPX2.cjs +0 -16
  114. package/dist/pdf-ocr-GHEJVPX2.cjs.map +0 -1
  115. package/dist/pdf-ocr-MQTWODVU.js +0 -18
  116. package/dist/seal-XWMRUTI4.js +0 -10
  117. package/dist/visual-USPS4QRC.js +0 -20
  118. /package/dist/{-4G6MA4X6.js.map → -PRAKO6M4.js.map} +0 -0
  119. /package/dist/{chunk-AUWGDP4G.js.map → chunk-2KF5WZCR.js.map} +0 -0
  120. /package/dist/{chunk-UTP2WCHS.js.map → chunk-4GSF2R7T.js.map} +0 -0
  121. /package/dist/{chunk-4VODJKFL.js.map → chunk-6RGGYQQC.js.map} +0 -0
  122. /package/dist/{chunk-DES2LGFZ.js.map → chunk-A55JGNKD.js.map} +0 -0
  123. /package/dist/{chunk-LEIAEWZ7.js.map → chunk-BFT5IM2R.js.map} +0 -0
  124. /package/dist/{chunk-KIOQ4N37.js.map → chunk-BLPR4HRF.js.map} +0 -0
  125. /package/dist/{chunk-JELRWAZC.js.map → chunk-BZG7K44Z.js.map} +0 -0
  126. /package/dist/{chunk-F4NRLLY4.js.map → chunk-EFO6JZY6.js.map} +0 -0
  127. /package/dist/{chunk-MLIS253L.js.map → chunk-ML4NHUR7.js.map} +0 -0
  128. /package/dist/{chunk-GO5IFOUF.js.map → chunk-N6V7LQBL.js.map} +0 -0
  129. /package/dist/{chunk-S2PL227Q.js.map → chunk-PHID6WNG.js.map} +0 -0
  130. /package/dist/{chunk-OXLPVQYK.js.map → chunk-TDQ6XDB3.js.map} +0 -0
  131. /package/dist/{chunk-5KS5PWJR.js.map → chunk-WY5JAR4O.js.map} +0 -0
  132. /package/dist/{chunk-7KNGSQU7.js.map → chunk-YZ5HHS6A.js.map} +0 -0
  133. /package/dist/{chunk-P6ZDXTV6.js.map → chunk-ZF6KHPB7.js.map} +0 -0
  134. /package/dist/{chunks-NUNIKS2J.js.map → chunks-YBFBLJ4G.js.map} +0 -0
  135. /package/dist/{formula-NARUOXRX.js.map → formula-7MMFBRGJ.js.map} +0 -0
  136. /package/dist/{formula-EPKLPDU7.js.map → formula-SHBXRXOJ.js.map} +0 -0
  137. /package/dist/{image-ocr-3WJ4QH3T.js.map → image-ocr-P6ECJCT2.js.map} +0 -0
  138. /package/dist/{image-ocr-5BUJQ4TK.js.map → image-ocr-QCT3A7X5.js.map} +0 -0
  139. /package/dist/{model-bundle-IMBW7D2Z.js.map → model-bundle-OUHGCEZY.js.map} +0 -0
  140. /package/dist/{models-SY75IBOE.js.map → models-6D6LMOTH.js.map} +0 -0
  141. /package/dist/{pdf-ocr-6DEE23IP.js.map → pdf-ocr-462ZFSHX.js.map} +0 -0
  142. /package/dist/{pdf-ocr-MQTWODVU.js.map → pdf-ocr-L35FQYGP.js.map} +0 -0
  143. /package/dist/{profile-io-GJPKCYPT.js.map → profile-io-YZUW6MAN.js.map} +0 -0
  144. /package/dist/{rasterize-UOALJQDS.js.map → rasterize-OUR2QJXX.js.map} +0 -0
  145. /package/dist/{redact-doc-22SYW3KH.js.map → redact-doc-4OQKG4SM.js.map} +0 -0
  146. /package/dist/{render-MJ4H3R23.js.map → render-XNO6PZJO.js.map} +0 -0
  147. /package/dist/{seal-XWMRUTI4.js.map → seal-CJ7C44JN.js.map} +0 -0
  148. /package/dist/{setup-2URYVUEO.js.map → setup-UU3EIHNI.js.map} +0 -0
  149. /package/dist/{visual-USPS4QRC.js.map → visual-QRMRFPUK.js.map} +0 -0
  150. /package/dist/{watch-ALXZGTOB.js.map → watch-SR6Q5SQN.js.map} +0 -0
package/dist/index.d.cts CHANGED
@@ -210,6 +210,10 @@ interface ParseOptions {
210
210
  * 기본 false: 마크다운 가독성을 위해 후행 빈 열을 트림.
211
211
  * 양식 인식 경로(parse_form·fill)는 내부적으로 항상 켠다. */
212
212
  keepTrailingEmptyCols?: boolean;
213
+ /** 테두리가 안 보이는 틀 표 처리 (v4.17.0). 기본 `"visual"`: 보이는 대로 — 선이 없는 틀은 글로 풀고 선이 보이는 부분만 표로,
214
+ * 칸으로 조립한 분수는 `$\frac{…}{…}$` 수식으로. `"keep"`: 종전대로 원본 표 구조 그대로(왕복 패치·양식 채우기처럼
215
+ * 원본 표 서수가 필요한 경로). HWPX·HWP5·PDF */
216
+ layoutTables?: "visual" | "keep";
213
217
  /** 구조 파싱 뒤 표를 의미표/레이아웃/불확실로 분류해 `IRTable.classification` 에 붙인다 (#76).
214
218
  * 기본 false — 기본 parse 출력 불변. 중첩표·셀 blocks·캡션 blocks 까지 재귀, 원문 순서는 바꾸지 않는다. */
215
219
  classifyTables?: boolean;
package/dist/index.d.ts CHANGED
@@ -210,6 +210,10 @@ interface ParseOptions {
210
210
  * 기본 false: 마크다운 가독성을 위해 후행 빈 열을 트림.
211
211
  * 양식 인식 경로(parse_form·fill)는 내부적으로 항상 켠다. */
212
212
  keepTrailingEmptyCols?: boolean;
213
+ /** 테두리가 안 보이는 틀 표 처리 (v4.17.0). 기본 `"visual"`: 보이는 대로 — 선이 없는 틀은 글로 풀고 선이 보이는 부분만 표로,
214
+ * 칸으로 조립한 분수는 `$\frac{…}{…}$` 수식으로. `"keep"`: 종전대로 원본 표 구조 그대로(왕복 패치·양식 채우기처럼
215
+ * 원본 표 서수가 필요한 경로). HWPX·HWP5·PDF */
216
+ layoutTables?: "visual" | "keep";
213
217
  /** 구조 파싱 뒤 표를 의미표/레이아웃/불확실로 분류해 `IRTable.classification` 에 붙인다 (#76).
214
218
  * 기본 false — 기본 parse 출력 불변. 중첩표·셀 blocks·캡션 blocks 까지 재귀, 원문 순서는 바꾸지 않는다. */
215
219
  classifyTables?: boolean;
package/dist/index.js CHANGED
@@ -16,19 +16,21 @@ import {
16
16
  mapPuaText,
17
17
  markNonLayoutTable,
18
18
  noteSuffix
19
- } from "./chunk-7KNGSQU7.js";
19
+ } from "./chunk-YZ5HHS6A.js";
20
20
  import {
21
21
  parsePageRange
22
22
  } from "./chunk-V4BH5SRQ.js";
23
23
  import {
24
+ CELL_EDGES,
24
25
  HEADING_RATIO_H1,
25
26
  HEADING_RATIO_H2,
26
27
  HEADING_RATIO_H3,
27
28
  plainScripts,
28
29
  stripScriptTags,
29
30
  tidyScriptTags,
31
+ unframeLayoutTables,
30
32
  wrapScript
31
- } from "./chunk-3IB7LBHC.js";
33
+ } from "./chunk-MJBCXW5R.js";
32
34
  import {
33
35
  KordocError,
34
36
  OPTIONAL_DEP_INSTALL_HINT,
@@ -45,7 +47,7 @@ import {
45
47
  toArrayBuffer,
46
48
  unescapeHtml,
47
49
  unzipLimitBytes
48
- } from "./chunk-MLIS253L.js";
50
+ } from "./chunk-ML4NHUR7.js";
49
51
 
50
52
  // src/plain-markdown.ts
51
53
  function toPlainMarkdown(md2) {
@@ -714,7 +716,8 @@ async function extractHwpxStyles(zip, decompressed) {
714
716
  numberings: /* @__PURE__ */ new Map(),
715
717
  bullets: /* @__PURE__ */ new Map(),
716
718
  paraHeadings: /* @__PURE__ */ new Map(),
717
- paraIndents: /* @__PURE__ */ new Map()
719
+ paraIndents: /* @__PURE__ */ new Map(),
720
+ borderEdges: /* @__PURE__ */ new Map()
718
721
  };
719
722
  const headerPaths = ["Contents/header.xml", "header.xml", "Contents/head.xml", "head.xml"];
720
723
  for (const hp of headerPaths) {
@@ -737,6 +740,7 @@ async function extractHwpxStyles(zip, decompressed) {
737
740
  parseBullets(domDoc, result.bullets);
738
741
  parseParaHeadings(domDoc, result.paraHeadings);
739
742
  parseParaIndents(domDoc, result.paraIndents);
743
+ parseBorderEdges(domDoc, result.borderEdges);
740
744
  break;
741
745
  } catch {
742
746
  continue;
@@ -874,6 +878,26 @@ function parseParaIndents(doc, map) {
874
878
  if (map.size > 0) break;
875
879
  }
876
880
  }
881
+ function parseBorderEdges(doc, map) {
882
+ const tagNames = ["hh:borderFill", "borderFill"];
883
+ for (const tagName of tagNames) {
884
+ const elements3 = doc.getElementsByTagName(tagName);
885
+ for (let i = 0; i < elements3.length; i++) {
886
+ const el = elements3[i];
887
+ const id = el.getAttribute("id") || "";
888
+ if (!id) continue;
889
+ const seen = (name) => {
890
+ const b = findChildByLocalName(el, name);
891
+ if (!b) return false;
892
+ const type = b.getAttribute("type") || "NONE";
893
+ const color = (b.getAttribute("color") || "#000000").toUpperCase();
894
+ return type !== "NONE" && color !== "#FFFFFF";
895
+ };
896
+ map.set(id, { t: seen("topBorder"), b: seen("bottomBorder"), l: seen("leftBorder"), r: seen("rightBorder") });
897
+ }
898
+ if (map.size > 0) break;
899
+ }
900
+ }
877
901
  function parseParaHeadings(doc, map) {
878
902
  const tagNames = ["hh:paraPr", "paraPr"];
879
903
  for (const tagName of tagNames) {
@@ -1553,6 +1577,13 @@ function buildTableWithCellMeta(state, keepAnchoredEmptyCols, keepEmptyParagraph
1553
1577
  }
1554
1578
  const srcCount = state.rows.reduce((s, r) => s + r.length, 0);
1555
1579
  const ordinalReliable = anchors2.length === srcCount;
1580
+ for (const row of state.rows) {
1581
+ for (const src of row) {
1582
+ if (!src.edges || src.rowAddr === void 0 || src.colAddr === void 0) continue;
1583
+ const cand = table2.cells[src.rowAddr]?.[src.colAddr];
1584
+ if (cand && anchors2.includes(cand)) CELL_EDGES.set(cand, src.edges);
1585
+ }
1586
+ }
1556
1587
  const claimed = /* @__PURE__ */ new Set();
1557
1588
  let flatIdx = -1;
1558
1589
  for (const row of state.rows) {
@@ -1995,6 +2026,8 @@ function walkSection(node, blocks, tableCtx, tableStack, ctx, depth = 0) {
1995
2026
  if (tableCtx) {
1996
2027
  tableCtx.cell = { text: "", colSpan: 1, rowSpan: 1 };
1997
2028
  if (el.getAttribute("header") === "1" || el.getAttribute("header") === "true") tableCtx.cell.isHeader = true;
2029
+ const edges = ctx.styleMap?.borderEdges.get(el.getAttribute("borderFillIDRef") ?? "");
2030
+ if (edges) tableCtx.cell.edges = edges;
1998
2031
  walkSection(el, blocks, tableCtx, tableStack, ctx, depth + 1);
1999
2032
  if (tableCtx.cell) {
2000
2033
  tableCtx.currentRow.push(tableCtx.cell);
@@ -2671,15 +2704,13 @@ function extractParagraphInfo(para2, styleMap, ctx) {
2671
2704
  walk(child, depth + 1);
2672
2705
  break;
2673
2706
  // 자식 순회 (tab 등 하위 요소 처리)
2674
- case "tab": {
2675
- const leader = child.getAttribute("leader");
2676
- if (leader && leader !== "0") {
2677
- text += "";
2678
- } else {
2679
- text += " ";
2680
- }
2707
+ // 탭 — 채움(leader≠0, 목차 점선) 탭도 보통 탭. 채움선은 글이 아니라 내지 않고 뒤 글은 남긴다(PDF tab-leaders 가
2708
+ // 점 채움을 탭 하나로 바꾸고 쪽 번호를 남기는 것과 같다). 종전엔 채움 탭 뒤를 쪽 번호로 보고 잘라, 목차 쪽 번호만이 아니라
2709
+ // 일정표 "사용자 의견조사 ····· '26년 8~9월"(gate-fill 36646162)의 일정, 줄바꿈 뒤 다음 목차 항목
2710
+ // "<참고2> 직종별사업체노동력조사 개요"(rhwp issue6044)까지 사라졌다
2711
+ case "tab":
2712
+ text += " ";
2681
2713
  break;
2682
- }
2683
2714
  case "br":
2684
2715
  if ((child.getAttribute("type") || "line") === "line") text += "\n";
2685
2716
  break;
@@ -2801,7 +2832,7 @@ function extractParagraphInfo(para2, styleMap, ctx) {
2801
2832
  walk(child, depth + 1);
2802
2833
  if (script && linkRanges.length === nLinks && openFields.length === nOpen && runHasOnlyText(child)) {
2803
2834
  const added = text.slice(start);
2804
- if (added && !/[\x1E\x1F\n$]/.test(added)) text = text.slice(0, start) + wrapScript(added, script);
2835
+ if (added && !/[\x1E\n$]/.test(added)) text = text.slice(0, start) + wrapScript(added, script);
2805
2836
  }
2806
2837
  break;
2807
2838
  }
@@ -2818,14 +2849,12 @@ function extractParagraphInfo(para2, styleMap, ctx) {
2818
2849
  for (const r of closed) {
2819
2850
  if (applied.some(([s, e]) => r.start < e && r.end > s)) continue;
2820
2851
  const anchor = text.slice(r.start, r.end);
2821
- if (!anchor.trim() || /[\n\x1F\x1E\[\]]/.test(anchor)) continue;
2852
+ if (!anchor.trim() || /[\n\x1E\[\]]/.test(anchor)) continue;
2822
2853
  text = text.slice(0, r.start) + `[${anchor}](${r.url})` + text.slice(r.end);
2823
2854
  applied.push([r.start, r.end]);
2824
2855
  }
2825
2856
  if (applied.length) href = void 0;
2826
2857
  }
2827
- const leaderIdx = text.indexOf("");
2828
- if (leaderIdx >= 0) text = text.substring(0, leaderIdx);
2829
2858
  text = tidyScriptTags(text);
2830
2859
  const cleanParaText = (raw) => {
2831
2860
  let t = raw.replace(/[ \t]+/g, " ").trim();
@@ -2885,6 +2914,7 @@ var TAG_SHAPE_COMPONENT_PICTURE = 85;
2885
2914
  var TAG_SHAPE_COMPONENT_CONTAINER = 86;
2886
2915
  var TAG_EQEDIT = 88;
2887
2916
  var TAG_ID_MAPPINGS = 17;
2917
+ var TAG_BORDER_FILL = 20;
2888
2918
  var TAG_BIN_DATA = 18;
2889
2919
  var TAG_FACE_NAME = 19;
2890
2920
  var TAG_DOC_CHAR_SHAPE = 21;
@@ -2959,7 +2989,15 @@ function parseDocInfo(records) {
2959
2989
  const binData = [];
2960
2990
  const numberings = [];
2961
2991
  const bullets = [];
2992
+ const borderEdges = [];
2962
2993
  for (const rec of records) {
2994
+ if (rec.tagId === TAG_BORDER_FILL) {
2995
+ const seen = (k) => {
2996
+ const o = 2 + 6 * k;
2997
+ return rec.data.length >= o + 6 && rec.data[o] !== 0 && (rec.data.readUInt32LE(o + 2) & 16777215) !== 16777215;
2998
+ };
2999
+ borderEdges.push({ l: seen(0), r: seen(1), t: seen(2), b: seen(3) });
3000
+ }
2963
3001
  if (rec.tagId === TAG_DOC_PARA_SHAPE && rec.data.length >= 4) {
2964
3002
  const attr1 = rec.data.readUInt32LE(0);
2965
3003
  const headType = attr1 >>> 23 & 3;
@@ -3054,9 +3092,8 @@ function parseDocInfo(records) {
3054
3092
  }
3055
3093
  }
3056
3094
  }
3057
- return { charShapes, paraShapes, styles, binData, numberings, bullets };
3095
+ return { charShapes, paraShapes, styles, binData, numberings, bullets, borderEdges };
3058
3096
  }
3059
- var LEADER_TAB_MARK = "";
3060
3097
  var LITERAL_DOLLAR_MARK = "\uFDD0";
3061
3098
  function createParaTextState() {
3062
3099
  return { text: "", ctrlIdx: 0, fieldStack: [], fieldRanges: [] };
@@ -3120,9 +3157,10 @@ function appendParaText(state, data, resolveControl) {
3120
3157
  break;
3121
3158
  // 고정폭 공백
3122
3159
  // ── inline 타입 (2바이트 + 14바이트 확장) ──
3123
- // 확장 u16[7] 중 [2] 의 하위 바이트 = 채움 모양(0 없음·3 점선 …), 상위 = 탭 종류+1 (rhwp tab_extended 실측)
3160
+ // 확장 u16[7] 중 [2] 의 하위 바이트 = 채움 모양(0 없음·3 점선 …), 상위 = 탭 종류+1 (rhwp tab_extended 실측).
3161
+ // 채움 탭도 보통 탭 — 채움선은 글이 아니고 뒤 글(목차 쪽 번호 등)은 남긴다 (HWPX section-walker "tab" 과 같은 정책)
3124
3162
  case CHAR_TAB:
3125
- result += state.leaderMark && i + 14 <= data.length && data[i + 4] !== 0 ? LEADER_TAB_MARK : " ";
3163
+ result += " ";
3126
3164
  if (i + 14 <= data.length) i += 14;
3127
3165
  break;
3128
3166
  default:
@@ -3801,10 +3839,11 @@ async function parseHwpxDocument(buffer, options) {
3801
3839
  const blocks = allBlocks;
3802
3840
  applyPageText(blocks, shared);
3803
3841
  const images = await extractImagesFromZip(zip, blocks, decompressed, warnings, !options?.pages);
3804
- detectHwpxHeadings(blocks, styleMap);
3805
- const outline = blocks.filter((b) => b.type === "heading" && b.level && b.text).map((b) => ({ level: b.level, text: b.text, pageNumber: b.pageNumber }));
3806
- const markdown = blocksToMarkdown(blocks);
3807
- return { markdown, blocks, metadata, outline: outline.length > 0 ? outline : void 0, warnings: warnings.length > 0 ? warnings : void 0, images: images.length > 0 ? images : void 0 };
3842
+ const shown = options?.layoutTables === "keep" ? blocks : unframeLayoutTables(blocks, !!options?.keepTrailingEmptyCols);
3843
+ detectHwpxHeadings(shown, styleMap);
3844
+ const outline = shown.filter((b) => b.type === "heading" && b.level && b.text).map((b) => ({ level: b.level, text: b.text, pageNumber: b.pageNumber }));
3845
+ const markdown = blocksToMarkdown(shown);
3846
+ return { markdown, blocks: shown, metadata, outline: outline.length > 0 ? outline : void 0, warnings: warnings.length > 0 ? warnings : void 0, images: images.length > 0 ? images : void 0 };
3808
3847
  }
3809
3848
 
3810
3849
  // src/hwp5/aes.ts
@@ -5114,6 +5153,10 @@ function buildAddressedTable(cells, rows, cols, keepAnchoredEmptyCols) {
5114
5153
  const table2 = buildTable(byRow, { keepAnchoredEmptyCols });
5115
5154
  if (table2.rows === 0) return null;
5116
5155
  for (const c of placed) {
5156
+ if (c.edges) {
5157
+ const at = table2.cells[c.rowAddr ?? 0]?.[c.colAddr ?? 0];
5158
+ if (at) CELL_EDGES.set(at, c.edges);
5159
+ }
5117
5160
  if (!c.blocks?.length && !c.isHeader) continue;
5118
5161
  const target = table2.cells[c.rowAddr ?? 0]?.[c.colAddr ?? 0];
5119
5162
  if (!target || target.text !== c.text.trim()) continue;
@@ -5279,7 +5322,6 @@ function parseParagraph(records, start, end, ctx) {
5279
5322
  }
5280
5323
  }
5281
5324
  const state = createParaTextState();
5282
- state.leaderMark = true;
5283
5325
  state.dollarMark = true;
5284
5326
  state.scriptAt = scriptLookup(charShapeRuns, ctx.docInfo);
5285
5327
  const resolver = (idx, id) => {
@@ -5315,13 +5357,11 @@ function parseParagraph(records, start, end, ctx) {
5315
5357
  if (!ctrl.href) continue;
5316
5358
  const href = sanitizeHref(ctrl.href);
5317
5359
  if (!href) continue;
5318
- if (!anchor.trim() || anchor.includes(INLINE_TABLE_MARK) || anchor.includes(LEADER_TAB_MARK)) continue;
5360
+ if (!anchor.trim() || anchor.includes(INLINE_TABLE_MARK)) continue;
5319
5361
  text = text.slice(0, r.start) + `[${anchor}](${href})` + text.slice(r.end);
5320
5362
  applied.push([r.start, r.end]);
5321
5363
  }
5322
5364
  }
5323
- const leaderAt = text.indexOf(LEADER_TAB_MARK);
5324
- if (leaderAt >= 0) text = text.slice(0, leaderAt);
5325
5365
  if (text.includes(LITERAL_DOLLAR_MARK)) text = text.replaceAll(LITERAL_DOLLAR_MARK, "\\$");
5326
5366
  text = tidyScriptTags(text);
5327
5367
  let headingLevel = 0;
@@ -5616,6 +5656,10 @@ function parseCell(records, lhIdx, end, ctx) {
5616
5656
  if (hasStructure && blocks.length > 0) cell2.blocks = blocks;
5617
5657
  if (isHeader) cell2.isHeader = true;
5618
5658
  if (rec.data.length >= 24 && rec.data.readUInt32LE(20) > A4_HEIGHT) cell2.pageSpanning = true;
5659
+ if (rec.data.length >= 34) {
5660
+ const edges = ctx.docInfo?.borderEdges?.[rec.data.readUInt16LE(32) - 1];
5661
+ if (edges) cell2.edges = edges;
5662
+ }
5619
5663
  return cell2;
5620
5664
  }
5621
5665
  function arrangeCells(rows, cols, cells) {
@@ -5891,6 +5935,7 @@ function parseHwp5Document(buffer, options) {
5891
5935
  const blocks = [...doc.headerBlocks, ...bodyBlocks, ...doc.footerBlocks];
5892
5936
  const images = cfb ? extractHwp5Images(cfb.FileIndex, blocks, warnings, !pageFilter) : extractHwp5ImagesLenient(lenientCfb, blocks, warnings, !pageFilter);
5893
5937
  let flatBlocks = flattenLayoutTables(blocks);
5938
+ if (options?.layoutTables !== "keep") flatBlocks = unframeLayoutTables(flatBlocks, !!options?.keepTrailingEmptyCols);
5894
5939
  if (options?.dedupeRunningHeaders) {
5895
5940
  const deduped = dedupeRunningHeaders(flatBlocks);
5896
5941
  const removed = flatBlocks.length - deduped.length;
@@ -18710,7 +18755,6 @@ var MAX_DECOMPRESS_SIZE3 = 100 * 1024 * 1024;
18710
18755
  var PIC_TYPE_DRAWING = 3;
18711
18756
  var OBJ_EQUATION = 2;
18712
18757
  var OPT_HYPERTEXT = 16;
18713
- var LEADER_TAB_MARK2 = "";
18714
18758
  var OUTLINE_FORMATS = [
18715
18759
  { fmt: "romanUpper", before: "", after: "." },
18716
18760
  { fmt: "digit", before: "", after: ")" },
@@ -18850,8 +18894,6 @@ function parseParagraphList2(reader, ctx, sink) {
18850
18894
  }
18851
18895
  function emitHwp3Paragraph(para2) {
18852
18896
  let text = para2.text;
18853
- const leaderAt = text.indexOf(LEADER_TAB_MARK2);
18854
- if (leaderAt >= 0) text = text.slice(0, leaderAt);
18855
18897
  let headMarker = null;
18856
18898
  if (para2.outline) {
18857
18899
  const { level, numbers } = para2.outline;
@@ -18879,11 +18921,9 @@ function parseCharStream(reader, charCount, ctx, para2) {
18879
18921
  continue;
18880
18922
  }
18881
18923
  if (ch === 9) {
18882
- reader.skip(2);
18883
- const leader = reader.readU16();
18884
- reader.skip(2);
18924
+ reader.skip(6);
18885
18925
  i += 3;
18886
- para2.text += leader ? LEADER_TAB_MARK2 : " ";
18926
+ para2.text += " ";
18887
18927
  continue;
18888
18928
  }
18889
18929
  if (ch === 18) {
@@ -21431,8 +21471,10 @@ function collectCharText(node, parts, depth = 0) {
21431
21471
  if (el.nodeType !== 1) continue;
21432
21472
  const tag = localName(el);
21433
21473
  if (tag === "CHAR") {
21434
- const t = rawTextContent(el);
21435
- if (t) parts.push(t);
21474
+ for (const c of Array.from(el.childNodes)) {
21475
+ const t = c.nodeType === 1 ? localName(c) === "TAB" ? " " : rawTextContent(c) : c.nodeType === 3 || c.nodeType === 4 ? c.nodeValue ?? "" : "";
21476
+ if (t) parts.push(t);
21477
+ }
21436
21478
  } else if (tag === "TABLE" || tag === "PICTURE" || tag === "SHAPEOBJECT") {
21437
21479
  } else if (tag === "AUTONUM") {
21438
21480
  } else {
@@ -28327,6 +28369,18 @@ function removeNestedTables(html) {
28327
28369
  if (depth === 0) result += html.slice(last);
28328
28370
  return result;
28329
28371
  }
28372
+ var LAYOUT_MODE_MISMATCH = '\uD3B8\uC9D1 \uB9C8\uD06C\uB2E4\uC6B4\uC774 \uAE30\uBCF8(\uBCF4\uC774\uB294 \uB300\uB85C) \uCD9C\uB825\uC5D0\uC11C \uB098\uC654\uC2B5\uB2C8\uB2E4. \uC774 \uBB38\uC11C\uC5D0\uB294 \uD14C\uB450\uB9AC\uAC00 \uC548 \uBCF4\uC774\uB294 \uD2C0 \uD45C\uAC00 \uC788\uC5B4 \uADF8 \uCD9C\uB825\uC73C\uB85C\uB294 \uC6D0\uBCF8 \uC704\uCE58\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 `kordoc <\uC6D0\uBCF8> --keep-layout-tables`(API: parse(\uC6D0\uBCF8, { layoutTables: "keep" }))\uB85C \uBF51\uC740 \uB9C8\uD06C\uB2E4\uC6B4\uC744 \uACE0\uCCD0 \uB2E4\uC2DC \uD328\uCE58\uD558\uC138\uC694';
28373
+ function editedFromVisual(keepMarkdown, visualMarkdown, editedMarkdown) {
28374
+ if (keepMarkdown === visualMarkdown) return false;
28375
+ const keep = new Set(splitMarkdownUnits(keepMarkdown).map((u) => u.raw));
28376
+ const visual = new Set(splitMarkdownUnits(visualMarkdown).map((u) => u.raw));
28377
+ let k = 0, v = 0;
28378
+ for (const u of splitMarkdownUnits(editedMarkdown)) {
28379
+ if (keep.has(u.raw)) k++;
28380
+ if (visual.has(u.raw)) v++;
28381
+ }
28382
+ return v > k;
28383
+ }
28330
28384
 
28331
28385
  // src/hwpx/gen-table-bf.ts
28332
28386
  var THIN = ["0.12 mm", "#000000"];
@@ -34289,7 +34343,9 @@ async function patchHwp(original, editedMarkdown, options) {
34289
34343
  const compressed = (flags & FLAG_COMPRESSED) !== 0;
34290
34344
  let origBlocks;
34291
34345
  try {
34292
- origBlocks = parseHwp5Document(originalBuf).blocks;
34346
+ const kept = parseHwp5Document(originalBuf, { layoutTables: "keep" });
34347
+ origBlocks = kept.blocks;
34348
+ if (editedFromVisual(kept.markdown, parseHwp5Document(originalBuf).markdown, editedMarkdown)) return fail(LAYOUT_MODE_MISMATCH);
34293
34349
  } catch (err) {
34294
34350
  return fail(`\uC6D0\uBCF8 HWP \uD30C\uC2F1 \uC2E4\uD328: ${msg(err)}`);
34295
34351
  }
@@ -34351,7 +34407,7 @@ async function patchHwp(original, editedMarkdown, options) {
34351
34407
  let verification;
34352
34408
  if (options?.verify !== false) {
34353
34409
  try {
34354
- const reparsed = parseHwp5Document(Buffer.from(data));
34410
+ const reparsed = parseHwp5Document(Buffer.from(data), { layoutTables: "keep" });
34355
34411
  const normBr = (u) => ({ ...u, raw: u.raw.replace(/<br\s*\/?\s*>/gi, "\n") });
34356
34412
  verification = diffUnitLists(splitMarkdownUnits(reparsed.markdown).map(normBr), editedUnits.map(normBr));
34357
34413
  } catch (err) {
@@ -34950,8 +35006,10 @@ async function patchHwpx(original, editedMarkdown, options) {
34950
35006
  }
34951
35007
  let origBlocks;
34952
35008
  try {
34953
- const parsed = await parseHwpxDocument(u8ToArrayBuffer(original));
35009
+ const parsed = await parseHwpxDocument(u8ToArrayBuffer(original), { layoutTables: "keep" });
34954
35010
  origBlocks = parsed.blocks;
35011
+ const visual = await parseHwpxDocument(u8ToArrayBuffer(original));
35012
+ if (editedFromVisual(parsed.markdown, visual.markdown, editedMarkdown)) return { success: false, applied: 0, skipped, error: LAYOUT_MODE_MISMATCH };
34955
35013
  } catch (err) {
34956
35014
  return { success: false, applied: 0, skipped, error: `\uC6D0\uBCF8 HWPX \uD30C\uC2F1 \uC2E4\uD328: ${err instanceof Error ? err.message : String(err)}` };
34957
35015
  }
@@ -35035,7 +35093,7 @@ async function patchHwpx(original, editedMarkdown, options) {
35035
35093
  let verification;
35036
35094
  if (options?.verify !== false) {
35037
35095
  try {
35038
- const reparsed = await parseHwpxDocument(u8ToArrayBuffer(data));
35096
+ const reparsed = await parseHwpxDocument(u8ToArrayBuffer(data), { layoutTables: "keep" });
35039
35097
  verification = diffUnitLists(splitMarkdownUnits(reparsed.markdown), editedUnits);
35040
35098
  } catch (err) {
35041
35099
  return { success: false, applied, skipped, error: `\uD328\uCE58\uBCF8 \uC7AC\uD30C\uC2F1 \uC2E4\uD328 \u2014 \uD328\uCE58 \uC911\uB2E8: ${err instanceof Error ? err.message : String(err)}` };
@@ -37960,7 +38018,7 @@ import JSZip13 from "jszip";
37960
38018
  async function buildState(bytes) {
37961
38019
  const format2 = detectFormat(new Uint8Array(bytes.subarray(0, 512)).buffer);
37962
38020
  if (format2 !== "hwpx") throw new Error(`HWPX \uC138\uC158\uC740 HWPX \uBB38\uC11C\uB9CC \uC5FD\uB2C8\uB2E4 (\uAC10\uC9C0\uB41C \uD3EC\uB9F7: ${format2}) \u2014 HWP 5.x \uB294 patchHwp \uB85C \uD328\uCE58\uD558\uC138\uC694`);
37963
- const parsed = await parseHwpxDocument(u8ToArrayBuffer2(bytes));
38021
+ const parsed = await parseHwpxDocument(u8ToArrayBuffer2(bytes), { layoutTables: "keep" });
37964
38022
  const zip = await JSZip13.loadAsync(bytes);
37965
38023
  const sectionPaths = await resolveSectionEntryNames(zip);
37966
38024
  if (sectionPaths.length === 0) {
@@ -39684,7 +39742,7 @@ async function renderHwpxPages(input, options, select) {
39684
39742
  }
39685
39743
 
39686
39744
  // src/render/hwp5-scene.ts
39687
- var TAG_BORDER_FILL = 20;
39745
+ var TAG_BORDER_FILL2 = 20;
39688
39746
  var TAG_PAGE_DEF = 73;
39689
39747
  var TAG_SHAPE_COMPONENT_LINE = 78;
39690
39748
  var TAG_SHAPE_COMPONENT_POLYGON = 82;
@@ -39813,7 +39871,7 @@ function buildHwp5RenderStyles(records) {
39813
39871
  }
39814
39872
  const borderFill = /* @__PURE__ */ new Map();
39815
39873
  let bi = 0;
39816
- for (const r of records) if (r.tagId === TAG_BORDER_FILL) borderFill.set(String(++bi), borderFillOf(r.data));
39874
+ for (const r of records) if (r.tagId === TAG_BORDER_FILL2) borderFill.set(String(++bi), borderFillOf(r.data));
39817
39875
  return { charPr: charPr2, paraAlign, paraGeom, borderFill };
39818
39876
  }
39819
39877
  var VREL = ["PAPER", "PAGE", "PARA", "PARA"];
@@ -40465,7 +40523,7 @@ function wantsCrop(kind, policy) {
40465
40523
  async function extractTables(input, options = {}) {
40466
40524
  const policy = options.policy ?? "non-tabular-and-uncertain";
40467
40525
  const buffer = typeof input === "string" ? toArrayBuffer(await readFile2(input)) : Buffer.isBuffer(input) ? toArrayBuffer(input) : input;
40468
- const parsed = await parse(buffer, { classifyTables: true });
40526
+ const parsed = await parse(buffer, { classifyTables: true, layoutTables: "keep" });
40469
40527
  if (!parsed.success) throw new KordocError(`\uD30C\uC2F1 \uC2E4\uD328: ${parsed.error}`);
40470
40528
  const blocks = collectTableBlocks(parsed.blocks);
40471
40529
  const out = blocks.map((b, i) => ({
@@ -40605,7 +40663,7 @@ function scriptsOff(r, off) {
40605
40663
  }
40606
40664
  async function parseImage(buffer, options) {
40607
40665
  try {
40608
- const { parseImageDocument } = await import("./image-ocr-5BUJQ4TK.js");
40666
+ const { parseImageDocument } = await import("./image-ocr-QCT3A7X5.js");
40609
40667
  const { blocks, warnings } = await parseImageDocument(buffer, options);
40610
40668
  stripScriptTags({ blocks });
40611
40669
  return {
@@ -40665,7 +40723,7 @@ async function parseHwp(buffer, options) {
40665
40723
  async function parsePdf(buffer, options) {
40666
40724
  let parsePdfDocument;
40667
40725
  try {
40668
- const mod = await import("./parser-ZQM3664Z.js");
40726
+ const mod = await import("./parser-CTRB7PKD.js");
40669
40727
  parsePdfDocument = mod.parsePdfDocument;
40670
40728
  } catch {
40671
40729
  return {
@@ -40742,7 +40800,7 @@ async function fillForm(input, values, outputFormat = "markdown") {
40742
40800
  fill: { filled: hwpxResult.filled, unmatched: hwpxResult.unmatched }
40743
40801
  };
40744
40802
  }
40745
- const parsed = await parse(buffer);
40803
+ const parsed = await parse(buffer, { layoutTables: "keep" });
40746
40804
  if (!parsed.success) {
40747
40805
  throw new Error(`\uC11C\uC2DD \uD30C\uC2F1 \uC2E4\uD328: ${parsed.error}`);
40748
40806
  }