kordoc 4.14.4 → 4.15.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 (134) hide show
  1. package/README.md +15 -1
  2. package/dist/{-Z4TJ3QGK.js → -WXTUDCBW.js} +10 -10
  3. package/dist/{chunk-YT3GC62E.js → chunk-23AJ65GX.js} +64 -20
  4. package/dist/chunk-23AJ65GX.js.map +1 -0
  5. package/dist/{chunk-DPWWXUOG.js → chunk-2FT2DHE6.js} +214 -36
  6. package/dist/chunk-2FT2DHE6.js.map +1 -0
  7. package/dist/{chunk-5LAZQCUJ.cjs → chunk-3CWXYYCL.cjs} +38 -30
  8. package/dist/chunk-3CWXYYCL.cjs.map +1 -0
  9. package/dist/{chunk-YAVHVFRY.js → chunk-4OLS5RDH.js} +64 -20
  10. package/dist/chunk-4OLS5RDH.js.map +1 -0
  11. package/dist/{chunk-GUJ4Q73I.js → chunk-6Y6KUOA6.js} +14 -3
  12. package/dist/chunk-6Y6KUOA6.js.map +1 -0
  13. package/dist/{chunk-NLU6LBLY.js → chunk-AKWMQ5XT.js} +17 -9
  14. package/dist/chunk-AKWMQ5XT.js.map +1 -0
  15. package/dist/{chunk-WJJGW5PL.js → chunk-B3NAP4FA.js} +2 -2
  16. package/dist/{chunk-XVXHXQIO.js → chunk-DTM6FV74.js} +1726 -1679
  17. package/dist/chunk-DTM6FV74.js.map +1 -0
  18. package/dist/{chunk-IMAAFSAX.cjs → chunk-EP4DTCVJ.cjs} +6 -6
  19. package/dist/{chunk-IMAAFSAX.cjs.map → chunk-EP4DTCVJ.cjs.map} +1 -1
  20. package/dist/{chunk-QEJUY6SK.cjs → chunk-FSQJJTHS.cjs} +3 -3
  21. package/dist/{chunk-QEJUY6SK.cjs.map → chunk-FSQJJTHS.cjs.map} +1 -1
  22. package/dist/{chunk-7SE4EZ45.js → chunk-H5XLUEZG.js} +2 -2
  23. package/dist/{chunk-LCW7SEPG.js → chunk-IMR5HSB5.js} +2 -2
  24. package/dist/{chunk-GLSBEEZJ.js → chunk-KNZUG3SV.js} +2 -2
  25. package/dist/{chunk-TITDJWVZ.js → chunk-KO5S25RD.js} +23 -3
  26. package/dist/chunk-KO5S25RD.js.map +1 -0
  27. package/dist/{chunk-WJHAU73B.cjs → chunk-LBYKEJUJ.cjs} +17 -6
  28. package/dist/chunk-LBYKEJUJ.cjs.map +1 -0
  29. package/dist/{chunk-QIF2VXBQ.js → chunk-M26GSDV3.js} +16 -8
  30. package/dist/chunk-M26GSDV3.js.map +1 -0
  31. package/dist/{chunk-VJIL6PIL.js → chunk-MX36AA26.js} +2 -2
  32. package/dist/{chunk-LFSQAMSM.js → chunk-NJP76IZ5.js} +3 -3
  33. package/dist/{chunk-D4U2TXDC.js → chunk-PJ2K6B3V.js} +77 -66
  34. package/dist/chunk-PJ2K6B3V.js.map +1 -0
  35. package/dist/{chunk-V3DX4DUF.js → chunk-QMUM25RB.js} +2 -2
  36. package/dist/{chunk-MG6DJEX4.js → chunk-S5PJ7ZE2.js} +3 -3
  37. package/dist/{chunk-HIJUKZHM.js → chunk-TDYGL267.js} +11 -13
  38. package/dist/{chunk-HIJUKZHM.js.map → chunk-TDYGL267.js.map} +1 -1
  39. package/dist/{chunk-VDN7LMPL.js → chunk-UCDOIHGW.js} +2 -2
  40. package/dist/{chunk-USHQCF5T.cjs → chunk-W33QEDLY.cjs} +71 -27
  41. package/dist/chunk-W33QEDLY.cjs.map +1 -0
  42. package/dist/{chunk-YAKC46HZ.js → chunk-YINJO3PR.js} +2 -2
  43. package/dist/chunks-S2XR4JDI.js +10 -0
  44. package/dist/cli.js +42 -43
  45. package/dist/cli.js.map +1 -1
  46. package/dist/{formula-5LOVHAKE.cjs → formula-RGK6WGD2.cjs} +9 -9
  47. package/dist/{formula-5LOVHAKE.cjs.map → formula-RGK6WGD2.cjs.map} +1 -1
  48. package/dist/{formula-7RANCW5L.js → formula-USQK6NHN.js} +3 -3
  49. package/dist/{formula-7UYJOVTB.js → formula-WGBYDRVU.js} +4 -4
  50. package/dist/{image-ocr-NYBJEOH2.cjs → image-ocr-JPP6JRFU.cjs} +12 -12
  51. package/dist/{image-ocr-NYBJEOH2.cjs.map → image-ocr-JPP6JRFU.cjs.map} +1 -1
  52. package/dist/{image-ocr-ZV6PDBUQ.js → image-ocr-SLYPOW7G.js} +5 -5
  53. package/dist/{image-ocr-I6JOCATS.js → image-ocr-WCKYPPXY.js} +7 -7
  54. package/dist/index.cjs +2540 -2307
  55. package/dist/index.cjs.map +1 -1
  56. package/dist/index.d.cts +8 -16
  57. package/dist/index.d.ts +8 -16
  58. package/dist/index.js +2013 -1780
  59. package/dist/index.js.map +1 -1
  60. package/dist/mcp.js +43 -32
  61. package/dist/mcp.js.map +1 -1
  62. package/dist/{model-bundle-2C5IYDLE.js → model-bundle-BIS5HNXA.js} +5 -5
  63. package/dist/{models-SEQZHZFT.js → models-APDLDMGJ.js} +5 -5
  64. package/dist/{parser-3FNXEP6T.js → parser-4D2YTTIH.js} +16 -10
  65. package/dist/parser-4D2YTTIH.js.map +1 -0
  66. package/dist/{parser-FJBJXRJK.js → parser-6ZWJPQC2.js} +16 -10
  67. package/dist/parser-6ZWJPQC2.js.map +1 -0
  68. package/dist/{parser-HVEKF73B.cjs → parser-Y5NX55GC.cjs} +65 -59
  69. package/dist/parser-Y5NX55GC.cjs.map +1 -0
  70. package/dist/pdf-ocr-E3IZ3EI5.cjs +14 -0
  71. package/dist/{pdf-ocr-JRDBJ6TA.cjs.map → pdf-ocr-E3IZ3EI5.cjs.map} +1 -1
  72. package/dist/pdf-ocr-FNXYMNM6.js +16 -0
  73. package/dist/pdf-ocr-WSYPOBBU.js +13 -0
  74. package/dist/{profile-io-RUFZOZO6.js → profile-io-VJUZYOJA.js} +3 -3
  75. package/dist/{rasterize-IOYXR5SI.js → rasterize-6U5NWILD.js} +3 -3
  76. package/dist/{redact-NMIKVHMP.js → redact-3CY3KGVD.js} +2 -2
  77. package/dist/{redact-doc-QBHNJ2BU.js → redact-doc-H56QYJGQ.js} +10 -10
  78. package/dist/{render-YZ2TEC3N.js → render-HNMDLMI2.js} +6 -6
  79. package/dist/seal-VXNEOHZV.js +10 -0
  80. package/dist/{setup-MPIEP6XT.js → setup-RZVYAXAV.js} +3 -3
  81. package/dist/visual-NSZP2FWZ.js +19 -0
  82. package/dist/{watch-RYO7MN5B.js → watch-VA6GK56C.js} +45 -22
  83. package/dist/watch-VA6GK56C.js.map +1 -0
  84. package/package.json +1 -1
  85. package/dist/chunk-5LAZQCUJ.cjs.map +0 -1
  86. package/dist/chunk-D4U2TXDC.js.map +0 -1
  87. package/dist/chunk-DPWWXUOG.js.map +0 -1
  88. package/dist/chunk-GUJ4Q73I.js.map +0 -1
  89. package/dist/chunk-NLU6LBLY.js.map +0 -1
  90. package/dist/chunk-QIF2VXBQ.js.map +0 -1
  91. package/dist/chunk-TITDJWVZ.js.map +0 -1
  92. package/dist/chunk-USHQCF5T.cjs.map +0 -1
  93. package/dist/chunk-WJHAU73B.cjs.map +0 -1
  94. package/dist/chunk-XVXHXQIO.js.map +0 -1
  95. package/dist/chunk-YAVHVFRY.js.map +0 -1
  96. package/dist/chunk-YT3GC62E.js.map +0 -1
  97. package/dist/chunks-CCMRL4MJ.js +0 -10
  98. package/dist/parser-3FNXEP6T.js.map +0 -1
  99. package/dist/parser-FJBJXRJK.js.map +0 -1
  100. package/dist/parser-HVEKF73B.cjs.map +0 -1
  101. package/dist/pdf-ocr-JRDBJ6TA.cjs +0 -14
  102. package/dist/pdf-ocr-UUBWBEHL.js +0 -16
  103. package/dist/pdf-ocr-VJSF2SQC.js +0 -13
  104. package/dist/seal-ECV44TUH.js +0 -10
  105. package/dist/visual-ICMOWUCR.js +0 -19
  106. package/dist/watch-RYO7MN5B.js.map +0 -1
  107. /package/dist/{-Z4TJ3QGK.js.map → -WXTUDCBW.js.map} +0 -0
  108. /package/dist/{chunk-WJJGW5PL.js.map → chunk-B3NAP4FA.js.map} +0 -0
  109. /package/dist/{chunk-7SE4EZ45.js.map → chunk-H5XLUEZG.js.map} +0 -0
  110. /package/dist/{chunk-LCW7SEPG.js.map → chunk-IMR5HSB5.js.map} +0 -0
  111. /package/dist/{chunk-GLSBEEZJ.js.map → chunk-KNZUG3SV.js.map} +0 -0
  112. /package/dist/{chunk-VJIL6PIL.js.map → chunk-MX36AA26.js.map} +0 -0
  113. /package/dist/{chunk-LFSQAMSM.js.map → chunk-NJP76IZ5.js.map} +0 -0
  114. /package/dist/{chunk-V3DX4DUF.js.map → chunk-QMUM25RB.js.map} +0 -0
  115. /package/dist/{chunk-MG6DJEX4.js.map → chunk-S5PJ7ZE2.js.map} +0 -0
  116. /package/dist/{chunk-VDN7LMPL.js.map → chunk-UCDOIHGW.js.map} +0 -0
  117. /package/dist/{chunk-YAKC46HZ.js.map → chunk-YINJO3PR.js.map} +0 -0
  118. /package/dist/{chunks-CCMRL4MJ.js.map → chunks-S2XR4JDI.js.map} +0 -0
  119. /package/dist/{formula-7RANCW5L.js.map → formula-USQK6NHN.js.map} +0 -0
  120. /package/dist/{formula-7UYJOVTB.js.map → formula-WGBYDRVU.js.map} +0 -0
  121. /package/dist/{image-ocr-ZV6PDBUQ.js.map → image-ocr-SLYPOW7G.js.map} +0 -0
  122. /package/dist/{image-ocr-I6JOCATS.js.map → image-ocr-WCKYPPXY.js.map} +0 -0
  123. /package/dist/{model-bundle-2C5IYDLE.js.map → model-bundle-BIS5HNXA.js.map} +0 -0
  124. /package/dist/{models-SEQZHZFT.js.map → models-APDLDMGJ.js.map} +0 -0
  125. /package/dist/{pdf-ocr-UUBWBEHL.js.map → pdf-ocr-FNXYMNM6.js.map} +0 -0
  126. /package/dist/{pdf-ocr-VJSF2SQC.js.map → pdf-ocr-WSYPOBBU.js.map} +0 -0
  127. /package/dist/{profile-io-RUFZOZO6.js.map → profile-io-VJUZYOJA.js.map} +0 -0
  128. /package/dist/{rasterize-IOYXR5SI.js.map → rasterize-6U5NWILD.js.map} +0 -0
  129. /package/dist/{redact-NMIKVHMP.js.map → redact-3CY3KGVD.js.map} +0 -0
  130. /package/dist/{redact-doc-QBHNJ2BU.js.map → redact-doc-H56QYJGQ.js.map} +0 -0
  131. /package/dist/{render-YZ2TEC3N.js.map → render-HNMDLMI2.js.map} +0 -0
  132. /package/dist/{seal-ECV44TUH.js.map → seal-VXNEOHZV.js.map} +0 -0
  133. /package/dist/{setup-MPIEP6XT.js.map → setup-RZVYAXAV.js.map} +0 -0
  134. /package/dist/{visual-ICMOWUCR.js.map → visual-NSZP2FWZ.js.map} +0 -0
package/README.md CHANGED
@@ -89,6 +89,18 @@ MCP 등록 대신 스킬(SKILL.md) 형태로 쓰려면:
89
89
 
90
90
  ---
91
91
 
92
+ ## v4.15.0 변경사항
93
+
94
+ 실문서 코퍼스를 보강하고 프로덕션 리뷰에서 확인한 파싱·보안·성능 문제를 고쳤습니다.
95
+
96
+ - **긴 스프레드시트**: XLSX·XLS의 1만 행 무경고 절단을 수정하고, 칸 예산을 초과하면 경고합니다. XLSX는 XML을 행 묶음으로 읽어 대형 시트 메모리를 줄이고, XLS는 희소 격자를 사용합니다. 동아시아 날짜와 DOCX 머리글·바닥글도 보완했습니다.
97
+ - **PDF 글**: 실제 낱말을 붙이던 균등배분 정리, 표 칸의 글자 조각, 날짜 빈칸, 넓은 줄간격의 문단 이음을 고쳤습니다.
98
+ - **보안**: HTML 표 칸의 원문을 이스케이프하고, 인쇄·렌더 PDF는 JavaScript를 끄고 `data:`·`about:` 외 요청을 차단합니다. webhook의 IPv6 SSRF 방어와 MCP 쓰기 경로 제한도 보강했습니다.
99
+ - **안정성과 속도**: CLI·MCP·parse-worker의 진단 메시지를 stderr로 보내 JSON 출력을 보호합니다. 확장자가 실제 형식과 다른 문서의 무손실 패치를 고치고, 문서 비교·여러 구간 치환의 반복 계산을 줄였습니다.
100
+ - **인명·주소 마스킹**: 회의록 발언자·기관 직함 앞 이름·결재란과 도로명 변형을 보강하고 오탐을 줄였습니다(opt-in).
101
+ - **디렉토리 감시**: `watch -d`가 하위 폴더 구조를 보존해 같은 파일명끼리 덮어쓰지 않습니다. [PR #82](https://github.com/chrisryugj/kordoc/pull/82), 기여: @ROTl24.
102
+ - **검증 코퍼스**: 정책브리핑 HWPX↔PDF 300쌍, DOCX↔LibreOffice PDF 36쌍과 XLS 정답지 트랙을 추가했습니다. 자세한 실측은 [CHANGELOG](CHANGELOG.md)를 참고하세요.
103
+
92
104
  ## v4.14.4 변경사항
93
105
 
94
106
  읽기 품질을 한 번 더 점검했습니다. 원본 HWPX 를 정답으로 PDF 글을 재는 벤치를 새로 세워 띄어쓰기·읽기 순서까지 보고, PDF 표·OCR·개인정보 마스킹을 고쳤습니다.
@@ -932,7 +944,7 @@ CLI 는 `kordoc 문서.pdf --format json --no-images`. 대량 변환은 `kordoc
932
944
  stdin 으로 한 줄씩 요청합니다 (한 줄 = 한 JSON).
933
945
 
934
946
  ```text
935
- 시작 {"ready":true,"version":"4.14.4","protocol":1}
947
+ 시작 {"ready":true,"version":"4.15.0","protocol":1}
936
948
  요청 {"id":1,"file":"문서.hwpx","images":false,"ocr":"off"}
937
949
  응답 {"id":1,"rss":183500800,"result":{ …--format json 과 같은 결과, 실패도 success:false 로… }}
938
950
  종료 {"cmd":"quit"} (또는 stdin 닫기)
@@ -1030,6 +1042,8 @@ npx kordoc watch ./수신함 -d ./변환결과 # 폴더 감시 모
1030
1042
  npx kordoc watch ./문서 --webhook https://api/hook # 웹훅 알림
1031
1043
  ```
1032
1044
 
1045
+ `watch -d` 는 감시 폴더 안의 하위 폴더 구조를 출력에도 그대로 둡니다: `수신함/팀/보고서.hwpx` → `변환결과/팀/보고서.md`(또는 `.json`). `수신함` 바로 아래 파일은 종전처럼 `변환결과` 바로 아래에 씁니다.
1046
+
1033
1047
  > `check-ocr-models` · `check-formula-models` 는 이름과 달리 **없거나 SHA 가 안 맞으면 내려받습니다**.
1034
1048
  > 상태만 보려면 `--status-only` 를 붙이세요 (수식 모델은 ~155MB).
1035
1049
  >
@@ -51,22 +51,22 @@ import {
51
51
  unknownFontWarnings,
52
52
  usesGaejosikMunche,
53
53
  validateHwpx
54
- } from "./chunk-XVXHXQIO.js";
54
+ } from "./chunk-DTM6FV74.js";
55
55
  import {
56
56
  applySplices,
57
57
  buildParagraphSplices,
58
58
  buildRangeSplices,
59
59
  placeSealHwpx,
60
60
  scanSectionXml
61
- } from "./chunk-HIJUKZHM.js";
61
+ } from "./chunk-TDYGL267.js";
62
62
  import {
63
63
  DEFAULT_REDACT_RULES,
64
64
  redactMarkdown,
65
65
  redactText
66
- } from "./chunk-DPWWXUOG.js";
66
+ } from "./chunk-2FT2DHE6.js";
67
67
  import {
68
68
  blocksToChunks
69
- } from "./chunk-VJIL6PIL.js";
69
+ } from "./chunk-MX36AA26.js";
70
70
  import {
71
71
  SPACE_EM_FIXED,
72
72
  SPACE_EM_FONT,
@@ -91,20 +91,20 @@ import {
91
91
  renderSceneToHtml,
92
92
  simulateWrap,
93
93
  simulateWrapKeepWord
94
- } from "./chunk-D4U2TXDC.js";
94
+ } from "./chunk-PJ2K6B3V.js";
95
95
  import "./chunk-7FCNOKOR.js";
96
96
  import "./chunk-6BWU6SQX.js";
97
97
  import "./chunk-MOL7MDBG.js";
98
- import "./chunk-YAKC46HZ.js";
98
+ import "./chunk-YINJO3PR.js";
99
99
  import {
100
100
  blocksToMarkdown,
101
101
  flattenLayoutTables,
102
102
  hasStructuredCellContent
103
- } from "./chunk-NLU6LBLY.js";
104
- import "./chunk-V3DX4DUF.js";
103
+ } from "./chunk-AKWMQ5XT.js";
104
+ import "./chunk-QMUM25RB.js";
105
105
  import {
106
106
  VERSION
107
- } from "./chunk-TITDJWVZ.js";
107
+ } from "./chunk-KO5S25RD.js";
108
108
  export {
109
109
  BUILTIN_TEMPLATES,
110
110
  DEFAULT_REDACT_RULES,
@@ -194,4 +194,4 @@ export {
194
194
  usesGaejosikMunche,
195
195
  validateHwpx
196
196
  };
197
- //# sourceMappingURL=-Z4TJ3QGK.js.map
197
+ //# sourceMappingURL=-WXTUDCBW.js.map
@@ -6,7 +6,7 @@ import {
6
6
  import {
7
7
  safeMax,
8
8
  safeMin
9
- } from "./chunk-GUJ4Q73I.js";
9
+ } from "./chunk-6Y6KUOA6.js";
10
10
 
11
11
  // src/pdf/image-regions.ts
12
12
  import { OPS } from "pdfjs-dist/legacy/build/pdf.mjs";
@@ -1570,6 +1570,8 @@ var WrapLexicon = class {
1570
1570
  spaced2 = /* @__PURE__ */ new Map();
1571
1571
  /** 줄 안(줄 첫·끝 어절 제외 — 꺾인 조각일 수 있다) 홀로 선 어절 수 — 한 음절 조각이 낱말("등"·"및"·"그")인지 가른다 */
1572
1572
  words = /* @__PURE__ */ new Map();
1573
+ /** 줄 안에서 이웃한 두 어절("개인정보 처리") — 이어 붙인 꼴 증거(joinedWord)의 거부권 */
1574
+ pairs = /* @__PURE__ */ new Set();
1573
1575
  /** 줄 글 한 줄을 증거로 더한다 — 탭(큰 갭)으로 나뉜 조각은 다른 칸·단이라 조각 사이는 어절 경계 증거로 쓰지 않는다 */
1574
1576
  addLine(text) {
1575
1577
  for (const seg of text.replace(MARKUP, "").split(" ")) {
@@ -1577,6 +1579,7 @@ var WrapLexicon = class {
1577
1579
  for (let i = 0; i < toks.length; i++) {
1578
1580
  const t = toks[i], n = t.length;
1579
1581
  if (i > 0 && i < toks.length - 1) bump(this.words, t);
1582
+ if (i + 1 < toks.length) this.pairs.add(t + " " + toks[i + 1]);
1580
1583
  for (let a = 0; a + 1 < n; a++) {
1581
1584
  const c1 = t.charCodeAt(a), c2 = t.charCodeAt(a + 1);
1582
1585
  bump(this.joined1, pairKey(c1, c2));
@@ -1594,6 +1597,10 @@ var WrapLexicon = class {
1594
1597
  isWord(word) {
1595
1598
  return (this.words.get(word) ?? 0) > 0;
1596
1599
  }
1600
+ /** 꺾인 두 조각을 이어 붙인 꼴이 줄 안 한 어절로 나왔고, 두 조각이 줄 안에서 띄어 쓴 이웃 어절로는 나온 적 없나 */
1601
+ joinedWord(left, right) {
1602
+ return this.isWord(left + right) && !this.pairs.has(left + " " + right);
1603
+ }
1597
1604
  /** 꺾인 자리 증거: "" 붙음 · " " 띄움 · null 모름 (두 글자 증거가 갈리거나 없으면 한 글자) */
1598
1605
  evidence(left, right) {
1599
1606
  const n = left.length;
@@ -1617,10 +1624,14 @@ var hasBatchim = (c) => {
1617
1624
  var CLOSE_TAIL = /[’”」』)\]〉》>]+$/;
1618
1625
  var TAIL = "[.,)\u300D\u300F\u2019\u201D]*";
1619
1626
  var PARTICLE = new RegExp(`^(?:\uB2E4\\.|\uC758|\uC5D0|\uC5D0\uC11C|\uC5D0\uB294|\uC5D0\uAC8C|\uC5D0\uB3C4|\uC5D0\uC11C\uB294|\uAE4C\uC9C0|\uBD80\uD130|\uCC98\uB7FC|\uB9C8\uB2E4|\uB9CC|\uB3C4|\uB4E4|\uB4E4\uC774|\uB4E4\uC758|\uB4E4\uC740|\uB4E4\uC744|\uB4E4\uC5D0\uAC8C|\uC11C\uB294|\uC11C\uB3C4|\uB77C\uACE0|\uB77C\uBA70|\uC740|\uB294|\uC744|\uB97C|\uC73C\uB85C|\uB85C|\uC73C\uB85C\uC11C|\uB85C\uC11C|\uC73C\uB85C\uC368|\uB85C\uC368)${TAIL}$`);
1620
- var PARTICLE_AFTER_CONS = new RegExp(`^(?:\uC774|\uACFC|\uC774\uB2E4|\uC774\uBA70|\uC774\uACE0|\uC774\uB098|\uC774\uB77C\uB294|\uC774\uB77C\uACE0|\uC774\uB77C\uBA70)${TAIL}$`);
1627
+ var PARTICLE_AFTER_CONS = new RegExp(`^(?:\uC774|\uACFC|\uC774\uB2E4|\uC774\uBA70|\uC774\uACE0|\uC774\uB098|\uC774\uB77C\uB294|\uC774\uB77C\uACE0|\uC774\uB77C\uBA70|\uC778|\uC778\uB370|\uC778\uC9C0|\uC784|\uC774\uC5B4\uC57C|\uC774\uBBC0\uB85C|\uC774\uC9C0\uB9CC|\uC774\uBA74\uC11C)${TAIL}$`);
1621
1628
  var PARTICLE_AFTER_VOWEL = new RegExp(`^(?:\uAC00|\uC640|\uB77C\uB294)${TAIL}$`);
1622
- var VERB_FORM = new RegExp(`^(?:\uD558\uB294|\uD558\uC5EC|\uD558\uACE0|\uD558\uBA70|\uD558\uC600\uB2E4|\uD558\uC600\uC73C\uBA70|\uD558\uC600\uACE0|\uD558\uC600\uB2E4\uACE0|\uD588\uB2E4|\uD588\uC73C\uBA70|\uD588\uACE0|\uD588\uB2E4\uACE0|\uD588\uB2E4\uBA70|\uD55C\uB2E4|\uD55C\uB2E4\uACE0|\uD55C\uB2E4\uBA70|\uD55C\uB2E4\uB294|\uD560|\uD568|\uD558\uACA0\uB2E4|\uD558\uACA0\uB2E4\uACE0|\uD558\uACA0\uC2B5\uB2C8\uB2E4|\uD558\uAE30\uB85C|\uD558\uBA74|\uD558\uBA74\uC11C|\uD558\uB3C4\uB85D|\uD574\uC57C|\uB418\uB294|\uB418\uC5B4|\uB41C|\uB41C\uB2E4|\uB41C\uB2E4\uACE0|\uB41C\uB2E4\uB294|\uB418\uC5C8\uB2E4|\uB418\uC5C8\uC73C\uBA70|\uB418\uC5C8\uACE0|\uB410\uB2E4|\uB410\uC73C\uBA70|\uB418\uBA70|\uB418\uACE0|\uB418\uBA74|\uB428|\uB3FC)${TAIL}$`);
1623
- var AUX_BEFORE = /(?:야|록|로|게|도|히|자|를|을|면|서|고|며|지|라)$/;
1629
+ var ENDING = new RegExp(`^(?:\uC5B4\uC57C|\uC544\uC57C|\uC5EC\uC57C|\uBBC0\uB85C|\uC73C\uBBC0\uB85C|\uC9C0\uB9CC|\uBA74\uC11C|\uC73C\uBA74\uC11C|\uC73C\uBA70|\uAC70\uB098|\uB354\uB77C\uB3C4|\uB4E0\uC9C0|\uC5C8\uB2E4|\uC558\uB2E4|\uC600\uB2E4|\uC5C8\uC73C\uBA70|\uC558\uC73C\uBA70|\uC600\uC73C\uBA70|\uC5C8\uACE0|\uC558\uACE0|\uC600\uACE0|\uACA0\uB2E4|\uACA0\uACE0|\uACA0\uC73C\uBA70|\uC2B5\uB2C8\uB2E4|\uB2C8\uB2E4|\uB2C8\uAE4C)${TAIL}$`);
1630
+ var ADNOMINAL_HAN = new RegExp(`^\uD55C${TAIL}$`);
1631
+ var SUFFIX_JEOK = new RegExp(`^\uC801(?:\uC778|\uC73C\uB85C|\uC774\uB2E4|\uC774\uBA70|\uC774\uACE0|\uC784|\uC778\uB370|\uC774\uB77C|\uC774\uC9C0\uB9CC|\uC73C\uB85C\uC11C)${TAIL}$`);
1632
+ var SPLIT_PARTICLE = [["\uC5D0", new RegExp(`^\uC11C(?:\uB294|\uB3C4|\uC758|\uBD80\uD130|\uB9CC)?${TAIL}$`)], ["\uC774", new RegExp(`^\uB098${TAIL}$`)]];
1633
+ var VERB_FORM = new RegExp(`^(?:\uD558\uB294|\uD558\uC5EC|\uD558\uACE0|\uD558\uBA70|\uD558\uC600\uB2E4|\uD558\uC600\uC73C\uBA70|\uD558\uC600\uACE0|\uD558\uC600\uB2E4\uACE0|\uD588\uB2E4|\uD588\uC73C\uBA70|\uD588\uACE0|\uD588\uB2E4\uACE0|\uD588\uB2E4\uBA70|\uD55C\uB2E4|\uD55C\uB2E4\uACE0|\uD55C\uB2E4\uBA70|\uD55C\uB2E4\uB294|\uD560|\uD568|\uD558\uACA0\uB2E4|\uD558\uACA0\uB2E4\uACE0|\uD558\uACA0\uC2B5\uB2C8\uB2E4|\uD558\uAE30\uB85C|\uD558\uBA74|\uD558\uBA74\uC11C|\uD558\uB3C4\uB85D|\uD574\uC57C|\uB418\uB294|\uB418\uC5B4|\uB41C|\uB41C\uB2E4|\uB41C\uB2E4\uACE0|\uB41C\uB2E4\uB294|\uB418\uC5C8\uB2E4|\uB418\uC5C8\uC73C\uBA70|\uB418\uC5C8\uACE0|\uB410\uB2E4|\uB410\uC73C\uBA70|\uB418\uBA70|\uB418\uACE0|\uB418\uBA74|\uB428|\uB3FC|\uD558\uAE30|\uD558\uAC8C|\uD558\uC9C0|\uD558\uAC70\uB098|\uD574\uC11C|\uD558\uB294\uB370|\uD568\uC73C\uB85C\uC368|\uD568\uC5D0|\uD568\uC744|\uD568\uC774|\uD558\uAE30\uC5D0|\uD558\uAE30\uB3C4|\uB418\uC9C0|\uB418\uAE30|\uB418\uB3C4\uB85D|\uB418\uAC70\uB098|\uB418\uBA74\uC11C)${TAIL}$`);
1634
+ var AUX_BEFORE = /(?:야|록|로|게|도|히|자|를|을|면|서|고|며|라|려|까지)$/;
1624
1635
  var BECOME = /^[되된됐됨돼]/;
1625
1636
  var STANDALONE_SYLLABLE = /^(?:및|등|수|것|그|이|저|더|또|각|약|총|중|간|때|뿐|듯|채|전|후|내|외|한|두|세|네|몇|새|첫|온|본|곧|꼭|잘|못|안|좀|늘|다|왜|뭐|시|할|데|뒤|된)$/;
1626
1637
  var DATE_DAY_END = /(?:^|\s)\d*월\s+\d*일$/;
@@ -1634,15 +1645,21 @@ function endsAsWord(word) {
1634
1645
  if (/(?:가|와)$/.test(h)) return !hasBatchim(prev);
1635
1646
  return false;
1636
1647
  }
1637
- function particleContinues(left, right) {
1648
+ function particleContinues(left, rightWord) {
1649
+ const right = rightWord.replace(/[”’"」』].+$/, "");
1638
1650
  const h = left.replace(CLOSE_TAIL, "");
1639
1651
  const last = h[h.length - 1];
1640
1652
  if (!last || !/[가-힣A-Za-z0-9]/.test(last)) return false;
1641
1653
  if (PARTICLE.test(right)) return true;
1642
1654
  if (!/[가-힣]/.test(last)) return false;
1655
+ if (SUFFIX_JEOK.test(right)) return true;
1656
+ if (ENDING.test(right)) return true;
1657
+ if (/^다[.;:!?]/.test(right)) return true;
1658
+ if (SPLIT_PARTICLE.some(([a, b]) => last === a && b.test(right))) return true;
1643
1659
  if (PARTICLE_AFTER_CONS.test(right)) return hasBatchim(last);
1644
1660
  if (PARTICLE_AFTER_VOWEL.test(right)) return !hasBatchim(last);
1645
- return VERB_FORM.test(right) && !AUX_BEFORE.test(h) && !(last === "\uC774" && BECOME.test(right));
1661
+ if (ADNOMINAL_HAN.test(right)) return h.length >= 2 && !AUX_BEFORE.test(h) && !/(?:는|은|한|다|어|년)$/.test(h);
1662
+ return VERB_FORM.test(right) && !AUX_BEFORE.test(h) && !((last === "\uC774" || last === "\uAC00" && h.length >= 3) && BECOME.test(right));
1646
1663
  }
1647
1664
  var ITEM_HEAD = /^(?:[□■◆◇○●◎◦▪▫•※▶▷►❍❏❑✓✔➢➤☞]|[①-⑳]|[-–·∙ㆍ*](?=\s)|\(?\d{1,2}[.)](?!\d)|\(?[가-하][.)]|\([가-하\d]{1,2}\)|[ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩ]|제\d+[조항호장절])/;
1648
1665
  function startsNewItem(prevText, nextText) {
@@ -1658,10 +1675,11 @@ function wrapJoiner(prevText, nextText, lex) {
1658
1675
  const left = prev.slice(s), right = next.match(/^\S+/)[0];
1659
1676
  if (particleContinues(left, right)) return "";
1660
1677
  if (endsAsWord(left)) return " ";
1661
- if (/[·ㆍ‧-]/.test(a) && /[가-힣A-Za-z0-9]/.test(b)) return "";
1678
+ if (/[·ㆍ‧-]/.test(a) && !/[·ㆍ‧…]{2}$/.test(prev) && /[가-힣A-Za-z0-9]/.test(b)) return "";
1662
1679
  if (/[A-Za-z]/.test(a) && /[A-Za-z]/.test(b) || /\d/.test(a) && /\d/.test(b)) return " ";
1663
1680
  const ev = lex?.evidence(left, right);
1664
1681
  if (ev != null) return ev;
1682
+ if (lex && /[가-힣A-Za-z0-9]$/.test(left) && /^[가-힣A-Za-z0-9]/.test(right) && lex.joinedWord(left, right.replace(/[.,)」』’”;:]+$/, ""))) return "";
1665
1683
  if (/\d$/.test(left) && COUNTER.test(right)) return "";
1666
1684
  if (lex && /^[가-힣]$/.test(left) && /^[가-힣]/.test(right) && !STANDALONE_SYLLABLE.test(left) && ([...right].length === 1 || !lex.isWord(left))) return "";
1667
1685
  const r1 = right.replace(/[.,)」』’”]+$/, "");
@@ -1693,14 +1711,30 @@ function joinPageBreakWraps(blocks, lex) {
1693
1711
  var BODY_FULL_TOL = 0.25;
1694
1712
  var BODY_MIN_WIDTH_EM = 12;
1695
1713
  var BODY_MAX_PITCH_EM = 2;
1714
+ var BODY_PITCH_REL = 1.05;
1715
+ var BODY_MAX_PITCH_ABS_EM = 3.5;
1696
1716
  function bodyLineJoins(lines, lex) {
1697
1717
  let right = -Infinity;
1698
1718
  for (const l of lines) if (l.right > right) right = l.right;
1719
+ const pitch = (k) => lines[k].y - lines[k + 1].y;
1720
+ const sameSize = (k) => Math.abs(lines[k + 1].fontSize - lines[k].fontSize) <= 0.15 * lines[k].fontSize;
1721
+ let p1 = Infinity, p2 = Infinity, i1 = -1;
1722
+ for (let k = 0; k + 1 < lines.length; k++) {
1723
+ const p = pitch(k);
1724
+ if (p <= 0 || !sameSize(k)) continue;
1725
+ if (p < p1) {
1726
+ p2 = p1;
1727
+ p1 = p;
1728
+ i1 = k;
1729
+ } else if (p < p2) p2 = p;
1730
+ }
1699
1731
  const out = [];
1700
1732
  for (let i = 0; i + 1 < lines.length; i++) {
1701
1733
  const a = lines[i], b = lines[i + 1];
1702
1734
  const fs = a.fontSize;
1703
- const wraps = fs > 0 && right - a.right < BODY_FULL_TOL * fs && a.right - a.left >= BODY_MIN_WIDTH_EM * fs && a.y - b.y > 0 && a.y - b.y < BODY_MAX_PITCH_EM * fs && Math.abs(b.fontSize - fs) <= 0.15 * fs && !startsNewItem(a.text, b.text);
1735
+ const others = i === i1 ? p2 : p1;
1736
+ const maxPitch = Number.isFinite(others) ? Math.min(BODY_MAX_PITCH_ABS_EM * fs, Math.max(BODY_MAX_PITCH_EM * fs, others * BODY_PITCH_REL)) : BODY_MAX_PITCH_EM * fs;
1737
+ const wraps = fs > 0 && right - a.right < BODY_FULL_TOL * fs && a.right - a.left >= BODY_MIN_WIDTH_EM * fs && a.y - b.y > 0 && a.y - b.y < maxPitch && Math.abs(b.fontSize - fs) <= 0.15 * fs && !startsNewItem(a.text, b.text);
1704
1738
  out.push(wraps ? wrapJoiner(a.text, b.text, lex) : "\n");
1705
1739
  }
1706
1740
  return out;
@@ -1710,6 +1744,10 @@ function cellLineWraps(box, contentLeft, prevRight, fontSize, nextFirstCharW) {
1710
1744
  const pad = Math.min(CELL_PAD_MAX, Math.max(0, contentLeft - box.x1));
1711
1745
  return fontSize > 0 && prevRight + nextFirstCharW - (box.x2 - pad) > 0.5 * fontSize;
1712
1746
  }
1747
+ function cellLineFills(box, contentLeft, cellRight, lineRight, fontSize) {
1748
+ const pad = Math.min(CELL_PAD_MAX, Math.max(0, contentLeft - box.x1));
1749
+ return fontSize > 0 && cellRight - lineRight < BODY_FULL_TOL * fontSize && lineRight - contentLeft >= 0.8 * (box.x2 - box.x1 - 2 * pad);
1750
+ }
1713
1751
 
1714
1752
  // src/pdf/cell-text.ts
1715
1753
  var CELL_PADDING = 2;
@@ -1835,7 +1873,9 @@ function cellTextToString(items, wrap) {
1835
1873
  const first = s[0];
1836
1874
  return { right, fontSize: first.fontSize, firstCharW: first.w / Math.max(1, [...first.text].length) };
1837
1875
  });
1838
- const wraps = lineEnds.slice(0, -1).map((a, i) => cellLineWraps(wrap.box, contentLeft, a.right, a.fontSize, lineEnds[i + 1].firstCharW));
1876
+ let cellRight = -Infinity;
1877
+ for (const e of lineEnds) if (e.right > cellRight) cellRight = e.right;
1878
+ const wraps = lineEnds.slice(0, -1).map((a, i) => cellLineWraps(wrap.box, contentLeft, a.right, a.fontSize, lineEnds[i + 1].firstCharW) || lineEnds.length >= 3 && !textLines[i + 1].startsWith("(") && cellLineFills(wrap.box, contentLeft, cellRight, a.right, a.fontSize));
1839
1879
  return mergeCellTextLines(textLines, { wraps, lex: wrap.lex });
1840
1880
  }
1841
1881
  function mergeSuperscriptRows(lines) {
@@ -1907,6 +1947,12 @@ function detectEvenSpacedItems(items) {
1907
1947
  return result;
1908
1948
  }
1909
1949
  function markEvenRun(items, result, start, end) {
1950
+ let dateOnly = true;
1951
+ for (let i = start; i < end; i++) if (!/^[년월일시분초]$/.test(items[i].text)) {
1952
+ dateOnly = false;
1953
+ break;
1954
+ }
1955
+ if (dateOnly) return;
1910
1956
  const gaps = [];
1911
1957
  for (let i = start + 1; i < end; i++) {
1912
1958
  gaps.push(items[i].x - (items[i - 1].x + items[i - 1].w));
@@ -2092,14 +2138,14 @@ function filterHiddenText(items, pageWidth, pageHeight) {
2092
2138
  }
2093
2139
  return { visible, hiddenCount };
2094
2140
  }
2095
- function collapseEvenSpacing(text) {
2141
+ function collapseEvenSpacing(text, whole = true) {
2096
2142
  const tokens = text.split(" ");
2097
2143
  const singleCharCount = tokens.filter((t) => t.length === 1).length;
2098
- if (tokens.length >= 3 && singleCharCount / tokens.length >= 0.7 && !isDateUnitBlank(tokens)) {
2144
+ if (whole && tokens.length >= 3 && singleCharCount / tokens.length >= 0.7 && !isDateUnitBlank(tokens)) {
2099
2145
  return tokens.join("");
2100
2146
  }
2101
2147
  return text.replace(
2102
- /(?<![가-힣])[가-힣](?: [가-힣\d]){2,}(?![가-힣])/g,
2148
+ /(?<![^\s>])[가-힣](?: [가-힣\d]){2,}(?![^\s<])/g,
2103
2149
  (match) => isDateUnitBlank(match.split(" ")) ? match : match.replace(/ /g, "")
2104
2150
  );
2105
2151
  }
@@ -2414,7 +2460,7 @@ function detectHeadings(blocks, medianFontSize) {
2414
2460
  if (level > 0) {
2415
2461
  block.type = "heading";
2416
2462
  block.level = level;
2417
- block.text = collapseEvenSpacing(text);
2463
+ block.text = collapseEvenSpacing(text, false);
2418
2464
  }
2419
2465
  }
2420
2466
  }
@@ -3868,12 +3914,9 @@ function extractWithColumns(yLines, columns) {
3868
3914
  function buildGridTable(lines, columns) {
3869
3915
  const numCols = columns.length;
3870
3916
  const yRows = lines.map((items) => {
3871
- const row = Array(numCols).fill("");
3872
- for (const item of items) {
3873
- const col = findColumn(item.x, columns);
3874
- row[col] = row[col] ? row[col] + " " + item.text : item.text;
3875
- }
3876
- return row;
3917
+ const byCol = Array.from({ length: numCols }, () => []);
3918
+ for (const item of items) byCol[findColumn(item.x, columns)].push(item);
3919
+ return byCol.map((cell) => mergeLineSimple(cell));
3877
3920
  });
3878
3921
  const dataColStart = Math.max(2, Math.floor(numCols / 2));
3879
3922
  const merged = [];
@@ -4651,6 +4694,7 @@ export {
4651
4694
  collapseEvenSpacing,
4652
4695
  normalizeItems,
4653
4696
  WrapLexicon,
4697
+ wrapJoiner,
4654
4698
  joinPageBreakWraps,
4655
4699
  computeMedianFontSizeFromFreq,
4656
4700
  detectHeadings,
@@ -4668,4 +4712,4 @@ export {
4668
4712
  trimTrailingEmptyTableCols,
4669
4713
  extractPageBlocksWithLines
4670
4714
  };
4671
- //# sourceMappingURL=chunk-YT3GC62E.js.map
4715
+ //# sourceMappingURL=chunk-23AJ65GX.js.map