kordoc 4.24.36 → 4.24.38

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 (204) hide show
  1. package/README.md +4 -3
  2. package/dist/{-P5YOBEUM.js → -LPQSUCWH.js} +60 -60
  3. package/dist/{chunk-UX6WP55C.js → chunk-3NZP2VXB.js} +2 -2
  4. package/dist/{chunk-BPBY6762.js → chunk-5IYRVLGJ.js} +206 -18
  5. package/dist/chunk-5IYRVLGJ.js.map +1 -0
  6. package/dist/{chunk-U6NRQARP.js → chunk-5PONVN46.js} +7 -7
  7. package/dist/{chunk-JUFBKWYZ.cjs → chunk-6COUUT2E.cjs} +2 -2
  8. package/dist/{chunk-JUFBKWYZ.cjs.map → chunk-6COUUT2E.cjs.map} +1 -1
  9. package/dist/{chunk-WH33GYVU.js → chunk-6MTH2EYN.js} +19 -19
  10. package/dist/{chunk-NDKS7IDO.js → chunk-7F42AI4O.js} +2 -2
  11. package/dist/{chunk-KBU3KKZF.js → chunk-7F63OEHI.js} +2 -2
  12. package/dist/{chunk-VKZ3F3QB.cjs → chunk-ACQBUQB6.cjs} +404 -170
  13. package/dist/chunk-ACQBUQB6.cjs.map +1 -0
  14. package/dist/{chunk-U433NRGQ.js → chunk-AZ56HT3M.js} +3 -3
  15. package/dist/{chunk-PSA7XLT5.js → chunk-CDVDRC77.js} +2 -2
  16. package/dist/{chunk-ZPGXCXSX.js → chunk-FA3MQGIF.js} +2 -2
  17. package/dist/{chunk-2QKOHKAH.cjs → chunk-FZWI6S7M.cjs} +3 -3
  18. package/dist/{chunk-2QKOHKAH.cjs.map → chunk-FZWI6S7M.cjs.map} +1 -1
  19. package/dist/{chunk-WLMTCXAJ.js → chunk-G3JUKDMB.js} +2 -2
  20. package/dist/{chunk-VX3TCX6T.js → chunk-HWSJAYMR.js} +16 -7
  21. package/dist/chunk-HWSJAYMR.js.map +1 -0
  22. package/dist/{chunk-KDUI534B.js → chunk-IDCU74H5.js} +2 -2
  23. package/dist/{chunk-G5JSF2AY.js → chunk-IFWGHRCQ.js} +3 -3
  24. package/dist/chunk-IFWGHRCQ.js.map +1 -0
  25. package/dist/{chunk-VRXROMUB.js → chunk-ISMJQJOP.js} +253 -23
  26. package/dist/chunk-ISMJQJOP.js.map +1 -0
  27. package/dist/{chunk-ORDEO3KS.cjs → chunk-JEHCRYUT.cjs} +98 -98
  28. package/dist/{chunk-ORDEO3KS.cjs.map → chunk-JEHCRYUT.cjs.map} +1 -1
  29. package/dist/{chunk-IQJKLIJV.js → chunk-JIMG76FW.js} +3 -3
  30. package/dist/chunk-JIMG76FW.js.map +1 -0
  31. package/dist/{chunk-34BCKDAB.js → chunk-JL5REIEZ.js} +210 -22
  32. package/dist/chunk-JL5REIEZ.js.map +1 -0
  33. package/dist/{chunk-5LOELQQQ.js → chunk-LAKPAL53.js} +2 -2
  34. package/dist/{chunk-IJT4GDQX.js → chunk-MLXVSAR3.js} +2 -2
  35. package/dist/{chunk-MN5T7HCP.js → chunk-MNXKORB5.js} +7 -3
  36. package/dist/chunk-MNXKORB5.js.map +1 -0
  37. package/dist/{chunk-LMJNZEGE.js → chunk-MSRDRUOV.js} +2 -2
  38. package/dist/{chunk-EBYDC56L.js → chunk-OBZV23UV.js} +51 -51
  39. package/dist/{chunk-F4O2VM2W.js → chunk-ONC5LWD4.js} +18 -9
  40. package/dist/chunk-ONC5LWD4.js.map +1 -0
  41. package/dist/{chunk-MFBSYY52.js → chunk-ONL4UTGQ.js} +32 -32
  42. package/dist/{chunk-AZMS6M6S.js → chunk-ORIA2IFK.js} +50 -50
  43. package/dist/{chunk-B3WLIR4K.js → chunk-OYIGWP2N.js} +2 -2
  44. package/dist/{chunk-46M3WU3M.js → chunk-P4OK4W2F.js} +3 -3
  45. package/dist/{chunk-A7PYKEKI.cjs → chunk-Q7WPRN62.cjs} +69 -60
  46. package/dist/chunk-Q7WPRN62.cjs.map +1 -0
  47. package/dist/{chunk-TBHWTT3M.js → chunk-RLED4WS3.js} +2 -2
  48. package/dist/{chunk-TBHWTT3M.js.map → chunk-RLED4WS3.js.map} +1 -1
  49. package/dist/{chunk-D25PORBW.cjs → chunk-SOI7R4LF.cjs} +303 -115
  50. package/dist/chunk-SOI7R4LF.cjs.map +1 -0
  51. package/dist/{chunk-JYYA6APD.js → chunk-SORWBCEP.js} +8 -8
  52. package/dist/{chunk-JY7PXRHS.js → chunk-TQUO7JR5.js} +3 -3
  53. package/dist/{chunk-PZOF2YT5.js → chunk-UJFT2OED.js} +2 -2
  54. package/dist/{chunk-57GAB2RG.js → chunk-UPO7Y3KL.js} +2 -2
  55. package/dist/{chunk-FUAWTU6H.js → chunk-UQ2ZA3X4.js} +2 -2
  56. package/dist/{chunk-N5GRO67A.js → chunk-UQTQXXHU.js} +14 -14
  57. package/dist/{chunk-A3LJ7BI2.cjs → chunk-URSNWPD4.cjs} +6 -6
  58. package/dist/{chunk-A3LJ7BI2.cjs.map → chunk-URSNWPD4.cjs.map} +1 -1
  59. package/dist/{chunk-65C5646Y.cjs → chunk-V5CNQSVW.cjs} +3 -3
  60. package/dist/{chunk-65C5646Y.cjs.map → chunk-V5CNQSVW.cjs.map} +1 -1
  61. package/dist/{chunk-GB5PO5NX.js → chunk-WOGL3YQK.js} +2 -2
  62. package/dist/{chunk-TPGYFICT.js → chunk-XQPF6I6P.js} +2 -2
  63. package/dist/{chunk-DNDMC7WW.js → chunk-XVWPMKPH.js} +262 -28
  64. package/dist/chunk-XVWPMKPH.js.map +1 -0
  65. package/dist/{chunk-LI7GH25U.js → chunk-YPLKE5WG.js} +2 -2
  66. package/dist/{chunk-LI7GH25U.js.map → chunk-YPLKE5WG.js.map} +1 -1
  67. package/dist/{chunk-E6QNUE6M.cjs → chunk-ZTN3T3GH.cjs} +3 -3
  68. package/dist/chunk-ZTN3T3GH.cjs.map +1 -0
  69. package/dist/chunks-5L5OU6AI.js +11 -0
  70. package/dist/cli.js +63 -62
  71. package/dist/cli.js.map +1 -1
  72. package/dist/{formula-7NQ4T267.js → formula-AV7SNRB4.js} +20 -17
  73. package/dist/{formula-7NQ4T267.js.map → formula-AV7SNRB4.js.map} +1 -1
  74. package/dist/{formula-6KDGXS6C.js → formula-BMSREXB5.js} +19 -16
  75. package/dist/{formula-6KDGXS6C.js.map → formula-BMSREXB5.js.map} +1 -1
  76. package/dist/{formula-DBHDZM4H.cjs → formula-NQHARDPX.cjs} +25 -22
  77. package/dist/formula-NQHARDPX.cjs.map +1 -0
  78. package/dist/generator-4CSCP6YH.js +17 -0
  79. package/dist/generator-OC366G6M.cjs +11 -0
  80. package/dist/{generator-AZ7UQRHE.cjs.map → generator-OC366G6M.cjs.map} +1 -1
  81. package/dist/generator-UPESZL4M.js +10 -0
  82. package/dist/hancom-3CHPVF7H.js +84 -0
  83. package/dist/hancom-3CHPVF7H.js.map +1 -0
  84. package/dist/hancom-6Z7O3G4K.js +85 -0
  85. package/dist/hancom-6Z7O3G4K.js.map +1 -0
  86. package/dist/hancom-F5OHWSWU.cjs +85 -0
  87. package/dist/hancom-F5OHWSWU.cjs.map +1 -0
  88. package/dist/{image-ocr-GR3FWG3K.cjs → image-ocr-JQ4OEBBK.cjs} +98 -20
  89. package/dist/image-ocr-JQ4OEBBK.cjs.map +1 -0
  90. package/dist/{image-ocr-CEEHL43H.js → image-ocr-PE5NAWAR.js} +92 -14
  91. package/dist/image-ocr-PE5NAWAR.js.map +1 -0
  92. package/dist/{image-ocr-BMRS4KAW.js → image-ocr-YJPNUWXR.js} +87 -9
  93. package/dist/image-ocr-YJPNUWXR.js.map +1 -0
  94. package/dist/index.cjs +1111 -1111
  95. package/dist/index.cjs.map +1 -1
  96. package/dist/index.d.cts +10 -3
  97. package/dist/index.d.ts +10 -3
  98. package/dist/index.js +71 -71
  99. package/dist/mcp.js +59 -58
  100. package/dist/mcp.js.map +1 -1
  101. package/dist/memo-OVXY467W.js +17 -0
  102. package/dist/{model-bundle-ZS54IR7N.js → model-bundle-AXHPS54Q.js} +5 -5
  103. package/dist/{models-6WPHCCXJ.js → models-CYRFBT43.js} +7 -5
  104. package/dist/{parse-worker-v2-Q5FGTPKF.js → parse-worker-v2-GUJQNNOF.js} +30 -30
  105. package/dist/parse-worker-v2-GUJQNNOF.js.map +1 -0
  106. package/dist/{parser-WV63YTSR.js → parser-533OXR3B.js} +19 -18
  107. package/dist/{parser-SMHLYJXN.js.map → parser-533OXR3B.js.map} +1 -1
  108. package/dist/{parser-5UAP4AO3.cjs → parser-JVJSUMCA.cjs} +197 -196
  109. package/dist/parser-JVJSUMCA.cjs.map +1 -0
  110. package/dist/{parser-SMHLYJXN.js → parser-S56VNTRV.js} +18 -17
  111. package/dist/{parser-WV63YTSR.js.map → parser-S56VNTRV.js.map} +1 -1
  112. package/dist/{pdf-ocr-E7RDHENX.js → pdf-ocr-FNW4IJWX.js} +11 -7
  113. package/dist/{pdf-ocr-5WTFJJZ6.js → pdf-ocr-JNR4HA7O.js} +15 -11
  114. package/dist/pdf-ocr-ZBKAXNIE.cjs +40 -0
  115. package/dist/pdf-ocr-ZBKAXNIE.cjs.map +1 -0
  116. package/dist/{profile-io-CCLANNVL.js → profile-io-GU5XFVSI.js} +3 -3
  117. package/dist/{rasterize-MZASSBE2.js → rasterize-XOHGVWKM.js} +3 -3
  118. package/dist/{redact-doc-42CDLQMK.js → redact-doc-G5K4NVUN.js} +38 -38
  119. package/dist/{render-MCK6Q6ZJ.js → render-J7VBXGFV.js} +14 -14
  120. package/dist/{rewrite-fallback-TKCGT3KA.js → rewrite-fallback-4QGM5F4V.js} +21 -21
  121. package/dist/seal-DSMAUDZ4.js +16 -0
  122. package/dist/{setup-6QDKNA5S.js → setup-R5IH4AGD.js} +3 -3
  123. package/dist/{to-hwpx-473Q2VSU.js → to-hwpx-5ENZWUTU.js} +9 -9
  124. package/dist/{to-hwpx-JM7ZHX6S.js → to-hwpx-I6LNQ4D4.js} +4 -4
  125. package/dist/to-hwpx-WCDMFRNN.cjs +16 -0
  126. package/dist/{to-hwpx-FHQLJL2B.cjs.map → to-hwpx-WCDMFRNN.cjs.map} +1 -1
  127. package/dist/visual-QFPNYNCF.js +37 -0
  128. package/dist/{watch-4CQCJXRJ.js → watch-UM5SMZQP.js} +28 -28
  129. package/package.json +1 -1
  130. package/dist/chunk-34BCKDAB.js.map +0 -1
  131. package/dist/chunk-A7PYKEKI.cjs.map +0 -1
  132. package/dist/chunk-BPBY6762.js.map +0 -1
  133. package/dist/chunk-D25PORBW.cjs.map +0 -1
  134. package/dist/chunk-DNDMC7WW.js.map +0 -1
  135. package/dist/chunk-E6QNUE6M.cjs.map +0 -1
  136. package/dist/chunk-F4O2VM2W.js.map +0 -1
  137. package/dist/chunk-G5JSF2AY.js.map +0 -1
  138. package/dist/chunk-IQJKLIJV.js.map +0 -1
  139. package/dist/chunk-MN5T7HCP.js.map +0 -1
  140. package/dist/chunk-VKZ3F3QB.cjs.map +0 -1
  141. package/dist/chunk-VRXROMUB.js.map +0 -1
  142. package/dist/chunk-VX3TCX6T.js.map +0 -1
  143. package/dist/chunks-GCOBHHXO.js +0 -11
  144. package/dist/formula-DBHDZM4H.cjs.map +0 -1
  145. package/dist/generator-AZ7UQRHE.cjs +0 -11
  146. package/dist/generator-OGOQGTEG.js +0 -10
  147. package/dist/generator-WUC766Y5.js +0 -17
  148. package/dist/image-ocr-BMRS4KAW.js.map +0 -1
  149. package/dist/image-ocr-CEEHL43H.js.map +0 -1
  150. package/dist/image-ocr-GR3FWG3K.cjs.map +0 -1
  151. package/dist/memo-5ZCEQWDF.js +0 -17
  152. package/dist/parse-worker-v2-Q5FGTPKF.js.map +0 -1
  153. package/dist/parser-5UAP4AO3.cjs.map +0 -1
  154. package/dist/pdf-ocr-JCNTIIYQ.cjs +0 -36
  155. package/dist/pdf-ocr-JCNTIIYQ.cjs.map +0 -1
  156. package/dist/seal-OZN5UW6W.js +0 -16
  157. package/dist/to-hwpx-FHQLJL2B.cjs +0 -16
  158. package/dist/visual-YLYML5VG.js +0 -37
  159. /package/dist/{-P5YOBEUM.js.map → -LPQSUCWH.js.map} +0 -0
  160. /package/dist/{chunk-UX6WP55C.js.map → chunk-3NZP2VXB.js.map} +0 -0
  161. /package/dist/{chunk-U6NRQARP.js.map → chunk-5PONVN46.js.map} +0 -0
  162. /package/dist/{chunk-WH33GYVU.js.map → chunk-6MTH2EYN.js.map} +0 -0
  163. /package/dist/{chunk-NDKS7IDO.js.map → chunk-7F42AI4O.js.map} +0 -0
  164. /package/dist/{chunk-KBU3KKZF.js.map → chunk-7F63OEHI.js.map} +0 -0
  165. /package/dist/{chunk-U433NRGQ.js.map → chunk-AZ56HT3M.js.map} +0 -0
  166. /package/dist/{chunk-PSA7XLT5.js.map → chunk-CDVDRC77.js.map} +0 -0
  167. /package/dist/{chunk-ZPGXCXSX.js.map → chunk-FA3MQGIF.js.map} +0 -0
  168. /package/dist/{chunk-WLMTCXAJ.js.map → chunk-G3JUKDMB.js.map} +0 -0
  169. /package/dist/{chunk-KDUI534B.js.map → chunk-IDCU74H5.js.map} +0 -0
  170. /package/dist/{chunk-5LOELQQQ.js.map → chunk-LAKPAL53.js.map} +0 -0
  171. /package/dist/{chunk-IJT4GDQX.js.map → chunk-MLXVSAR3.js.map} +0 -0
  172. /package/dist/{chunk-LMJNZEGE.js.map → chunk-MSRDRUOV.js.map} +0 -0
  173. /package/dist/{chunk-EBYDC56L.js.map → chunk-OBZV23UV.js.map} +0 -0
  174. /package/dist/{chunk-MFBSYY52.js.map → chunk-ONL4UTGQ.js.map} +0 -0
  175. /package/dist/{chunk-AZMS6M6S.js.map → chunk-ORIA2IFK.js.map} +0 -0
  176. /package/dist/{chunk-B3WLIR4K.js.map → chunk-OYIGWP2N.js.map} +0 -0
  177. /package/dist/{chunk-46M3WU3M.js.map → chunk-P4OK4W2F.js.map} +0 -0
  178. /package/dist/{chunk-JYYA6APD.js.map → chunk-SORWBCEP.js.map} +0 -0
  179. /package/dist/{chunk-JY7PXRHS.js.map → chunk-TQUO7JR5.js.map} +0 -0
  180. /package/dist/{chunk-PZOF2YT5.js.map → chunk-UJFT2OED.js.map} +0 -0
  181. /package/dist/{chunk-57GAB2RG.js.map → chunk-UPO7Y3KL.js.map} +0 -0
  182. /package/dist/{chunk-FUAWTU6H.js.map → chunk-UQ2ZA3X4.js.map} +0 -0
  183. /package/dist/{chunk-N5GRO67A.js.map → chunk-UQTQXXHU.js.map} +0 -0
  184. /package/dist/{chunk-GB5PO5NX.js.map → chunk-WOGL3YQK.js.map} +0 -0
  185. /package/dist/{chunk-TPGYFICT.js.map → chunk-XQPF6I6P.js.map} +0 -0
  186. /package/dist/{chunks-GCOBHHXO.js.map → chunks-5L5OU6AI.js.map} +0 -0
  187. /package/dist/{generator-OGOQGTEG.js.map → generator-4CSCP6YH.js.map} +0 -0
  188. /package/dist/{generator-WUC766Y5.js.map → generator-UPESZL4M.js.map} +0 -0
  189. /package/dist/{memo-5ZCEQWDF.js.map → memo-OVXY467W.js.map} +0 -0
  190. /package/dist/{model-bundle-ZS54IR7N.js.map → model-bundle-AXHPS54Q.js.map} +0 -0
  191. /package/dist/{models-6WPHCCXJ.js.map → models-CYRFBT43.js.map} +0 -0
  192. /package/dist/{pdf-ocr-5WTFJJZ6.js.map → pdf-ocr-FNW4IJWX.js.map} +0 -0
  193. /package/dist/{pdf-ocr-E7RDHENX.js.map → pdf-ocr-JNR4HA7O.js.map} +0 -0
  194. /package/dist/{profile-io-CCLANNVL.js.map → profile-io-GU5XFVSI.js.map} +0 -0
  195. /package/dist/{rasterize-MZASSBE2.js.map → rasterize-XOHGVWKM.js.map} +0 -0
  196. /package/dist/{redact-doc-42CDLQMK.js.map → redact-doc-G5K4NVUN.js.map} +0 -0
  197. /package/dist/{render-MCK6Q6ZJ.js.map → render-J7VBXGFV.js.map} +0 -0
  198. /package/dist/{rewrite-fallback-TKCGT3KA.js.map → rewrite-fallback-4QGM5F4V.js.map} +0 -0
  199. /package/dist/{seal-OZN5UW6W.js.map → seal-DSMAUDZ4.js.map} +0 -0
  200. /package/dist/{setup-6QDKNA5S.js.map → setup-R5IH4AGD.js.map} +0 -0
  201. /package/dist/{to-hwpx-473Q2VSU.js.map → to-hwpx-5ENZWUTU.js.map} +0 -0
  202. /package/dist/{to-hwpx-JM7ZHX6S.js.map → to-hwpx-I6LNQ4D4.js.map} +0 -0
  203. /package/dist/{visual-YLYML5VG.js.map → visual-QFPNYNCF.js.map} +0 -0
  204. /package/dist/{watch-4CQCJXRJ.js.map → watch-UM5SMZQP.js.map} +0 -0
package/README.md CHANGED
@@ -125,6 +125,7 @@ await writeFile("보고서.hwpx", Buffer.from(hwpx))
125
125
  | 법령 별표 | 272건 · 346표 | HWP **346/346**, PDF **343/346 · 99.13%** |
126
126
  | PDF 전체 | 1,911문서 중 1,729건 채점 | 글 커버리지 **99.80%** |
127
127
  | OCR | 53문서 · 102쪽 · 66표 | CER **0.0239** · 글자 재현율 **99.32%** · 표 구조 일치 **34/66** |
128
+ | KoOCR-Bench v2.0c (외부) | 공개 877쪽 (표·글·손글씨·순서) | 종합 기본값 **0.810** · `ocrRec: "auto"` **0.831** · 제목 재현율 0.798 (4.24.38) |
128
129
  | opendataloader-bench | PDF 200문서 | 종합 기본값 **0.964** · OCR 끔 **0.939** |
129
130
  | HWPX 렌더 배치 | 한컴 PDF 짝 1,565문서(쪽 수 일치 1,508) · 글 조각 111,480 · 그림 4,300 | 글 위치(±1.5pt) **106,904 · 95.9%** · 그림 위치(±2pt) **4,213 · 98.0%** |
130
131
 
@@ -132,15 +133,15 @@ await writeFile("보고서.hwpx", Buffer.from(hwpx))
132
133
 
133
134
  ## 최근 업데이트
134
135
 
135
- 최신 배포: **[4.24.36](https://github.com/chrisryugj/kordoc/releases/tag/v4.24.36)** · 2026-11-01
136
+ 최신 배포: **[4.24.38](https://github.com/chrisryugj/kordoc/releases/tag/v4.24.38)** · 2026-11-02
136
137
 
137
138
  | 버전 | 주요 변경 | 수치 변화 |
138
139
  | --- | --- | --- |
140
+ | 4.24.38 | OCR: 스캔·이미지 쪽 제목 판정(위아래로 쌓인 장 제목 잇기) · 작은 1칸 상자 쪽 두 단 읽기 순서 · 문장 조각 가짜 표 · 겹친 상자 이음매 글자 · 이미지 수식 OCR(`formulaOcr`) · to-hwpx 한/글 자체 저장 엔진(`--engine hancom`, 팝업 없이) | KoOCR-Bench 종합 0.807 → **0.810**(`auto` **0.831**) · 제목 재현율 0 → .798 · 자체 벤치 문서별 그대로 |
141
+ | 4.24.37 | OCR 표: 통계표 묶음 안 빈칸 열·괘선 없는 본문 행 펼치기·행 합침 아래 행 가르기·이웃 칸 값 몰림·음영 머리 띠와 칠한 첫 열 경계 · 머리 행 있는 표를 차트로 풀던 것 · 시험지 문항 상자 풀기 · `ocrRec: "auto"`(손글씨 쪽만 손글씨 모델) | KoOCR-Bench v2.0c 공개 877쪽 종합 0.742 → **0.807**(`ocrRec: "auto"` **0.828**) · 자체 벤치 문서별 그대로 |
139
142
  | 4.24.36 | PDF 띄어쓰기(한컴 PDF): 자간 줄인 글의 낱말 공백 되살림 · 공백 글리프 위에 덧찍은 쉼표("55만원,") · 날짜 "2026. 8. 28." 공백 유지 · 줄 끝에서 갈린 연도 "20⏎25년" | PDF 글 어절 F1 99.076 → **99.103%** · 그 밖 지표 그대로 |
140
143
  | 4.24.35 | PDF: 자간 줄인 수치의 같은 숫자 둘("129.9") 지키기 · 글자마다 찍은 첨자 줄을 본문에 붙이기 · 수 가운데서 꺾인 줄("1,2⏎96억"·"(4.1⏎6.~5.16.)")과 칸 소수 잇기 · 표지 책등 외기둥 세로 글 | PDF 표 칸 완전 일치 0.99264 → **0.99267** · 칸 내용 0.98902 → **0.98905** · PDF 글 순서 99.359 → **99.360%** · 어절 F1 99.073 → **99.076%** · 그 밖 지표 그대로 |
141
144
  | 4.24.34 | to-hwpx: 파일 여러 개·폴더 일괄 변환(하위 폴더, 이미 있는 .hwpx 건너뜀) · PDF: 기호 글꼴 작은 네모로 찍고 공백으로 적은 "·" 글머리 되살림 · 글자 단위로 나눈 줄 머리 어미("는데"·"졌다")·겹조사 잇기 | PDF 표 칸 완전 일치 0.99221 → **0.99264** · 칸 내용 0.98896 → **0.98902** · PDF 글 재현율 99.883 → **99.885%** · 순서 99.356 → **99.359%** · 어절 F1 99.071 → **99.073%** · 그 밖 지표 그대로 |
142
- | 4.24.33 | PDF 표: 모아찍기 쪽 넘김에서 오른쪽 쪽 머리 상자로 넘어간 칸 글을 앞 칸에 잇기 · 칸을 한 겹 더 깐 1×1 클립(통계표 숫자 칸)을 중첩 표 대신 칸 글로 | PDF 표 칸 내용 일치 0.98794 → **0.98896** · 칸 완전 일치 0.99153 → **0.99221** · 그 밖 벤치 지표 그대로 |
143
- | 4.24.32 | PDF·OCR 두 단: 같은 줄 격자 두 단(속기록·시험지)을 단으로 · 옆 단 머리 문항이 앞 선지 하위 목록이 되던 것 · 짝·홀 쪽에서 칸 순서가 뒤집히는 머리말 상자 걷기 | PDF 글 읽기 순서 99.348% → **99.356%** · 정확도 99.680% → **99.682%** · 어절 F1 99.069% → **99.071%** · OCR 정답(텍스트층) 회의록 두 단이 바로잡혀 CER 0.0237 → 0.0239(정답 변화, 채점 그대로) |
144
145
 
145
146
  이전 버전과 상세 내용은 [변경 이력](CHANGELOG.md)에 있습니다.
146
147
 
@@ -53,34 +53,7 @@ import {
53
53
  usesGaejosikMunche,
54
54
  usesLogicStructure,
55
55
  validateHwpx
56
- } from "./chunk-AZMS6M6S.js";
57
- import {
58
- blocksToPages
59
- } from "./chunk-IJT4GDQX.js";
60
- import {
61
- extractClickHereFields,
62
- patchHwp,
63
- patchHwpx
64
- } from "./chunk-EBYDC56L.js";
65
- import {
66
- addMemoHwpx
67
- } from "./chunk-JYYA6APD.js";
68
- import {
69
- placeSealHwpx
70
- } from "./chunk-U6NRQARP.js";
71
- import "./chunk-UX6WP55C.js";
72
- import {
73
- convertToHwpx,
74
- hwpToHwpx
75
- } from "./chunk-F4O2VM2W.js";
76
- import {
77
- DEFAULT_REDACT_RULES,
78
- redactMarkdown,
79
- redactText
80
- } from "./chunk-3WSNLH4B.js";
81
- import {
82
- blocksToChunks
83
- } from "./chunk-ZPGXCXSX.js";
56
+ } from "./chunk-ORIA2IFK.js";
84
57
  import {
85
58
  blocksToPdf,
86
59
  extractRenderedRegions,
@@ -91,25 +64,16 @@ import {
91
64
  renderHwp5Pages,
92
65
  renderHwpxToSvg,
93
66
  renderSceneToHtml
94
- } from "./chunk-MFBSYY52.js";
95
- import "./chunk-46M3WU3M.js";
96
- import "./chunk-B3WLIR4K.js";
97
- import "./chunk-N5GRO67A.js";
98
- import "./chunk-VKVDMH7X.js";
99
- import "./chunk-FUAWTU6H.js";
100
- import "./chunk-KDUI534B.js";
101
- import "./chunk-VQFA55IC.js";
67
+ } from "./chunk-ONL4UTGQ.js";
102
68
  import {
103
- detectFormat,
104
- detectOle2Format,
105
- detectZipFormat,
106
- isHwpxFile,
107
- isOldHwpFile,
108
- isPdfFile,
109
- isZipFile
110
- } from "./chunk-GHHVT4Q5.js";
111
- import "./chunk-H7K5YHWQ.js";
112
- import "./chunk-IQJKLIJV.js";
69
+ blocksToPages
70
+ } from "./chunk-MLXVSAR3.js";
71
+ import {
72
+ extractClickHereFields,
73
+ patchHwp,
74
+ patchHwpx
75
+ } from "./chunk-OBZV23UV.js";
76
+ import "./chunk-P4OK4W2F.js";
113
77
  import {
114
78
  AGENCY_COMMON_NAME,
115
79
  PRESET_ALIAS,
@@ -119,7 +83,24 @@ import {
119
83
  markdownToHwpx,
120
84
  normalizeGongmunPreset,
121
85
  placeTotalRow
122
- } from "./chunk-WH33GYVU.js";
86
+ } from "./chunk-6MTH2EYN.js";
87
+ import {
88
+ SPACE_EM_FIXED,
89
+ SPACE_EM_FONT,
90
+ charWidthEm1000,
91
+ fitRatioForFewerLines,
92
+ measureTextWidth,
93
+ simulateWrap,
94
+ simulateWrapKeepWord
95
+ } from "./chunk-UJFT2OED.js";
96
+ import {
97
+ addMemoHwpx
98
+ } from "./chunk-SORWBCEP.js";
99
+ import {
100
+ placeSealHwpx
101
+ } from "./chunk-5PONVN46.js";
102
+ import "./chunk-OYIGWP2N.js";
103
+ import "./chunk-3NZP2VXB.js";
123
104
  import {
124
105
  ValueCursor,
125
106
  applySplices,
@@ -133,28 +114,47 @@ import {
133
114
  isLabelCell,
134
115
  scanSectionXml
135
116
  } from "./chunk-3AHL6YEA.js";
117
+ import "./chunk-XQPF6I6P.js";
136
118
  import {
137
- SPACE_EM_FIXED,
138
- SPACE_EM_FONT,
139
- charWidthEm1000,
140
- fitRatioForFewerLines,
141
- measureTextWidth,
142
- simulateWrap,
143
- simulateWrapKeepWord
144
- } from "./chunk-PZOF2YT5.js";
145
- import "./chunk-TPGYFICT.js";
119
+ convertToHwpx,
120
+ hwpToHwpx
121
+ } from "./chunk-ONC5LWD4.js";
122
+ import "./chunk-UQTQXXHU.js";
123
+ import "./chunk-VKVDMH7X.js";
146
124
  import "./chunk-OFVHQ2TZ.js";
125
+ import "./chunk-UQ2ZA3X4.js";
147
126
  import "./chunk-WFKFG2RT.js";
148
- import "./chunk-TBHWTT3M.js";
127
+ import {
128
+ DEFAULT_REDACT_RULES,
129
+ redactMarkdown,
130
+ redactText
131
+ } from "./chunk-3WSNLH4B.js";
132
+ import {
133
+ blocksToChunks
134
+ } from "./chunk-FA3MQGIF.js";
135
+ import "./chunk-IDCU74H5.js";
136
+ import "./chunk-VQFA55IC.js";
137
+ import {
138
+ detectFormat,
139
+ detectOle2Format,
140
+ detectZipFormat,
141
+ isHwpxFile,
142
+ isOldHwpFile,
143
+ isPdfFile,
144
+ isZipFile
145
+ } from "./chunk-GHHVT4Q5.js";
146
+ import "./chunk-H7K5YHWQ.js";
147
+ import "./chunk-JIMG76FW.js";
148
+ import "./chunk-RLED4WS3.js";
149
149
  import {
150
150
  blocksToMarkdown,
151
151
  flattenLayoutTables,
152
152
  hasStructuredCellContent
153
- } from "./chunk-LMJNZEGE.js";
153
+ } from "./chunk-MSRDRUOV.js";
154
154
  import "./chunk-KFF47XZ7.js";
155
155
  import {
156
156
  VERSION
157
- } from "./chunk-5LOELQQQ.js";
157
+ } from "./chunk-LAKPAL53.js";
158
158
  export {
159
159
  AGENCY_COMMON_NAME,
160
160
  BUILTIN_TEMPLATES,
@@ -268,4 +268,4 @@ export {
268
268
  usesLogicStructure,
269
269
  validateHwpx
270
270
  };
271
- //# sourceMappingURL=-P5YOBEUM.js.map
271
+ //# sourceMappingURL=-LPQSUCWH.js.map
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  KordocError
4
- } from "./chunk-5LOELQQQ.js";
4
+ } from "./chunk-LAKPAL53.js";
5
5
 
6
6
  // src/roundtrip/zip-patch.ts
7
7
  import { deflateRawSync } from "zlib";
@@ -233,4 +233,4 @@ export {
233
233
  readZipEntries,
234
234
  patchZipEntries
235
235
  };
236
- //# sourceMappingURL=chunk-UX6WP55C.js.map
236
+ //# sourceMappingURL=chunk-3NZP2VXB.js.map
@@ -14,6 +14,7 @@ import {
14
14
  findOcrTextGaps,
15
15
  gapGlyphs,
16
16
  grayCrop,
17
+ handwritingFallbackModels,
17
18
  inkBounds,
18
19
  inkStats,
19
20
  leaderRuns,
@@ -33,17 +34,125 @@ import {
33
34
  splitRowBands,
34
35
  starRun,
35
36
  tallInkCount
36
- } from "./chunk-DNDMC7WW.js";
37
+ } from "./chunk-XVWPMKPH.js";
37
38
  import {
38
39
  validateOcrOptions
39
- } from "./chunk-G5JSF2AY.js";
40
+ } from "./chunk-IFWGHRCQ.js";
40
41
  import {
41
42
  disableOrtTelemetry
42
43
  } from "./chunk-DC6CTQTK.js";
43
44
  import {
44
45
  KordocError,
45
46
  OPTIONAL_DEP_INSTALL_HINT
46
- } from "./chunk-NDKS7IDO.js";
47
+ } from "./chunk-7F42AI4O.js";
48
+
49
+ // src/ocr/ocr-stacked-rows.ts
50
+ var NUMERIC = /^[-+−△▲▽▼]?[\d.,]+[%p]?$|^[-–—·∼~]$|^\([-+−△▲▽▼]?[\d.,]+\)$/;
51
+ var blank = () => ({ text: "", colSpan: 1, rowSpan: 1 });
52
+ function rowAnchors(t, r, coveredFromAbove) {
53
+ const out = [];
54
+ const row = t.cells[r] ?? [];
55
+ for (let c = 0; c < row.length; ) {
56
+ if (coveredFromAbove[c]) {
57
+ c++;
58
+ continue;
59
+ }
60
+ const cell = row[c];
61
+ out.push({ c, cell });
62
+ c += Math.max(1, cell.colSpan ?? 1);
63
+ }
64
+ return out;
65
+ }
66
+ function splitTable(t) {
67
+ let changed = false;
68
+ const cover = Array.from({ length: t.rows }, () => Array(t.cols).fill(null));
69
+ for (let r = 0; r < t.rows; r++) for (let c = 0; c < (t.cells[r] ?? []).length; c++) {
70
+ const cell = t.cells[r][c], rs = Math.max(1, cell.rowSpan ?? 1), cs = Math.max(1, cell.colSpan ?? 1);
71
+ if (cover[r][c]) continue;
72
+ for (let i = r + 1; i < Math.min(t.rows, r + rs); i++) for (let j = c; j < Math.min(t.cols, c + cs); j++) cover[i][j] = cell;
73
+ }
74
+ const newRows = [];
75
+ for (let r = 0; r < t.rows; r++) {
76
+ const row = t.cells[r] ?? [];
77
+ const anchors = rowAnchors(t, r, cover[r]);
78
+ const lines = anchors.map((a) => a.cell.text.split("\n").map((s) => s.trim()).filter(Boolean));
79
+ const counts = /* @__PURE__ */ new Map();
80
+ for (const ls of lines) if (ls.length >= 2) counts.set(ls.length, (counts.get(ls.length) ?? 0) + 1);
81
+ const [k, n] = [...counts].sort((a, b) => b[1] - a[1])[0] ?? [0, 0];
82
+ const nonEmpty = lines.filter((ls) => ls.length).length;
83
+ const near = (ls) => ls.length >= 2 && ls.length <= k && ls.length >= Math.ceil(k * 0.8);
84
+ const stacked = lines.filter(near);
85
+ const flat = stacked.flat();
86
+ const numericShare = flat.length ? flat.filter((s) => NUMERIC.test(s.replace(/\s+/g, ""))).length / flat.length : 0;
87
+ const spanOk = anchors.every((a, i) => (a.cell.rowSpan ?? 1) === 1 || lines[i].length <= 1);
88
+ const ok = spanOk && k >= 2 && n >= 2 && stacked.length * 2 >= nonEmpty && lines.every((ls) => ls.length <= 1 || near(ls)) && numericShare >= 0.6;
89
+ if (!ok) {
90
+ newRows.push(row);
91
+ continue;
92
+ }
93
+ const sub = Array.from({ length: k }, () => Array.from({ length: row.length }, blank));
94
+ anchors.forEach(({ c, cell }, idx) => {
95
+ const ls = lines[idx], cs = Math.max(1, cell.colSpan ?? 1);
96
+ if (ls.length >= 2) for (let i = 0; i < k; i++) sub[i][c] = { ...cell, text: ls[i] ?? "", colSpan: cs, rowSpan: 1 };
97
+ else {
98
+ cell.rowSpan = (cell.rowSpan ?? 1) + k - 1;
99
+ sub[0][c] = cell;
100
+ }
101
+ });
102
+ for (const owner of new Set(cover[r].filter((x) => !!x))) owner.rowSpan = (owner.rowSpan ?? 1) + k - 1;
103
+ newRows.push(...sub);
104
+ changed = true;
105
+ }
106
+ if (changed) {
107
+ t.cells = newRows;
108
+ t.rows = newRows.length;
109
+ }
110
+ return changed;
111
+ }
112
+ function splitStackedOcrRows(blocks) {
113
+ for (const b of blocks) if (b.type === "table" && b.table) splitTable(b.table);
114
+ return blocks;
115
+ }
116
+ function unframeOcrLayoutTables(blocks, pageHeight) {
117
+ const out = [];
118
+ for (const b of blocks) {
119
+ const t = b.table;
120
+ if (b.type !== "table" || !t || !b.bbox || b.bbox.height < pageHeight * 0.5 || t.cols > 4) {
121
+ out.push(b);
122
+ continue;
123
+ }
124
+ const anchors = [];
125
+ const covered = Array.from({ length: t.rows }, () => /* @__PURE__ */ new Set());
126
+ for (let r = 0; r < t.rows; r++) for (let c = 0; c < (t.cells[r] ?? []).length; c++) {
127
+ if (covered[r].has(c)) continue;
128
+ const cell = t.cells[r][c];
129
+ const rs = Math.max(1, cell.rowSpan ?? 1), cs = Math.max(1, cell.colSpan ?? 1);
130
+ for (let i = r; i < Math.min(t.rows, r + rs); i++) for (let j = c; j < c + cs; j++) if (i !== r || j !== c) covered[i].add(j);
131
+ anchors.push({ r, c, cell });
132
+ }
133
+ const texts = anchors.map((a) => a.cell.text.trim()).filter(Boolean);
134
+ if (!texts.length) {
135
+ out.push(b);
136
+ continue;
137
+ }
138
+ const numeric = texts.filter((s) => NUMERIC.test(s.replace(/\s+/g, ""))).length;
139
+ const avg = texts.reduce((n, s) => n + s.length, 0) / texts.length;
140
+ if (numeric >= texts.length * 0.3 || avg < 15 || texts.filter((s) => /[①-⑤]/.test(s)).length < 2) {
141
+ out.push(b);
142
+ continue;
143
+ }
144
+ anchors.sort((a, b2) => a.c - b2.c || a.r - b2.r);
145
+ for (const { cell } of anchors) {
146
+ if (cell.blocks?.length) {
147
+ out.push(...cell.blocks);
148
+ continue;
149
+ }
150
+ const text = cell.text.trim();
151
+ if (text) out.push({ type: "paragraph", text, pageNumber: b.pageNumber, bbox: b.bbox });
152
+ }
153
+ }
154
+ return out;
155
+ }
47
156
 
48
157
  // src/ocr/ruling-lines.ts
49
158
  var INK_LUMA_MAX = 205;
@@ -122,6 +231,14 @@ function detectRulingLines(rgba, width, height, scale) {
122
231
  if (lo - p.x1 <= chainEnd && p.x2 - hi <= chainEnd && cover >= (p.x2 - p.x1) * 0.5) fillH.push({ ...inside[0], x1: p.x1, x2: p.x2 });
123
232
  }
124
233
  }
234
+ for (const a of fill.horizontals) {
235
+ if (fillH.some((f) => f === a || Math.abs(f.y1 - a.y1) <= tol && f.x1 <= a.x1 + tol && f.x2 >= a.x2 - tol)) continue;
236
+ const p = ruleH.find((p2) => (Math.abs(p2.x1 - a.x1) <= tol || Math.abs(p2.x2 - a.x2) <= tol) && a.x1 >= p2.x1 - tol && a.x2 <= p2.x2 + tol && a.x2 - a.x1 >= (p2.x2 - p2.x1) * 0.7);
237
+ if (!p || ruleH.some((q) => Math.abs(q.y1 - a.y1) <= tol)) continue;
238
+ const loose = Math.abs(p.x1 - a.x1) <= tol ? a.x2 : a.x1;
239
+ const depthPx = Math.round(FILL_MIN_DEPTH_PT * scale);
240
+ if (fill.verticals.some((v) => Math.abs(v.x1 - loose) <= tol * 2 && Math.abs(v.y1 - a.y1) <= tol * 2 && v.y2 - v.y1 >= depthPx * 4)) fillH.push({ ...a, x1: p.x1, x2: p.x2 });
241
+ }
125
242
  horizontals.push(...fillH);
126
243
  const edgeH = [...ruleH, ...fillH];
127
244
  const depth = Math.round(FILL_MIN_DEPTH_PT * scale);
@@ -131,6 +248,7 @@ function detectRulingLines(rgba, width, height, scale) {
131
248
  const top = edgeH.filter((h) => h.x1 <= a.x1 + tol && h.x2 >= a.x1 - tol && h.y1 < a.y1 && h.y1 >= a.y1 - depth);
132
249
  verticals.push({ ...a, y1: top.length ? Math.min(...top.map((h) => h.y1)) : a.y1 });
133
250
  } else if (edgeH.some((p) => (Math.abs(p.x1 - a.x1) <= tol || Math.abs(p.x2 - a.x1) <= tol) && p.y1 >= a.y1 - tol && p.y1 <= a.y2 + tol)) verticals.push(a);
251
+ else if ([a.y1, a.y2].every((y) => edgeH.some((p) => Math.abs(p.y1 - y) <= tol * 2 && p.x1 < a.x1 - tol && p.x2 > a.x1 + tol)) && ruleH.some((p) => p.y1 > a.y1 + tol * 2 && p.y1 < a.y2 - tol * 2 && p.x1 < a.x1 - tol && p.x2 > a.x1 + tol)) verticals.push(a);
134
252
  }
135
253
  for (const v of shortV) {
136
254
  if (!endsOn(v.x1, v.y1).length || !endsOn(v.x1, v.y2).length) continue;
@@ -428,11 +546,11 @@ function trimInklessEnds(rgba, pageW, box, text, steps, stepPx) {
428
546
  for (let x = Math.max(0, Math.floor(cx - half)); x <= Math.min(w - 1, Math.ceil(cx + half)); x++) if (cols[x] && cols[x] < h * 0.9) return false;
429
547
  return true;
430
548
  };
431
- const blank = (i) => !chars[i].trim();
549
+ const blank2 = (i) => !chars[i].trim();
432
550
  const mark = (i) => /\p{P}/u.test(chars[i]);
433
551
  let a = 0, b = chars.length;
434
- while (a < b && (blank(a) || !mark(a) && inkless(a))) a++;
435
- while (b > a && (blank(b - 1) || !mark(b - 1) && inkless(b - 1))) b--;
552
+ while (a < b && (blank2(a) || !mark(a) && inkless(a))) a++;
553
+ while (b > a && (blank2(b - 1) || !mark(b - 1) && inkless(b - 1))) b--;
436
554
  const cut = (from, to) => chars.slice(from, to).some((c) => c.trim());
437
555
  if (a === b || !cut(0, a) && !cut(b, chars.length)) return { text, steps };
438
556
  return { text: chars.slice(a, b).join(""), steps: steps.slice(a, b) };
@@ -1126,17 +1244,17 @@ function midBlankRow(gray, w, h, ink) {
1126
1244
  const isInk = (v) => ink.darkInk ? v <= ink.threshold : v > ink.threshold;
1127
1245
  const colInk = new Uint32Array(w);
1128
1246
  for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) if (isInk(gray[y * w + x])) colInk[x]++;
1129
- const blank = (y) => {
1247
+ const blank2 = (y) => {
1130
1248
  let n = 0;
1131
1249
  for (let x = 0; x < w; x++) if (colInk[x] < h * 0.85 && isInk(gray[y * w + x])) n++;
1132
1250
  return n <= Math.max(1, w * 0.02);
1133
1251
  };
1134
1252
  let best = null;
1135
1253
  for (let y = Math.floor(h * 0.3); y < Math.ceil(h * 0.7); y++) {
1136
- if (!blank(y)) continue;
1254
+ if (!blank2(y)) continue;
1137
1255
  let a = y, b = y + 1;
1138
- while (a > 0 && blank(a - 1)) a--;
1139
- while (b < h && blank(b)) b++;
1256
+ while (a > 0 && blank2(a - 1)) a--;
1257
+ while (b < h && blank2(b)) b++;
1140
1258
  if (!best || Math.abs((a + b) / 2 - h / 2) < Math.abs((best[0] + best[1]) / 2 - h / 2)) best = [a, b];
1141
1259
  y = b;
1142
1260
  }
@@ -1938,12 +2056,27 @@ function builtinOcrConfig(o) {
1938
2056
  if (!o || o.ocrDet === void 0 && o.ocrDetLongSide === void 0 && o.ocrRec === void 0 && o.ocrRecDict === void 0 && !o.ocrLines) return DEFAULT_BUILTIN_OCR;
1939
2057
  const bad = validateOcrOptions(o);
1940
2058
  if (bad) throw new KordocError(bad);
2059
+ const models = resolveOcrModels(o);
1941
2060
  return {
1942
- models: resolveOcrModels(o),
2061
+ models,
2062
+ ...o.ocrRec === "auto" ? { handwriting: handwritingFallbackModels(models) } : {},
1943
2063
  tuning: o.ocrDetLongSide === void 0 ? DEFAULT_OCR_TUNING : { ...DEFAULT_OCR_TUNING, detLongSide: o.ocrDetLongSide },
1944
2064
  lines: o.ocrLines === "all" ? "all" : o.ocrLines ? "kept" : void 0
1945
2065
  };
1946
2066
  }
2067
+ var AUTO_REC_PAGE_CONF = 0.9;
2068
+ var AUTO_REC_GAIN = 0.06;
2069
+ async function recognizeOcrPage(engine, config, tuning, rgba, width, height, stats, cellRules, rowRules) {
2070
+ const items = await engine.recognizePage(rgba, width, height, stats, tuning, cellRules, rowRules);
2071
+ if (!config.handwriting) return items;
2072
+ const base = meanConfidence(items);
2073
+ if (base >= AUTO_REC_PAGE_CONF) return items;
2074
+ const altStats = { droppedLowConf: 0, ...stats.dropped ? { dropped: [] } : {} };
2075
+ const alt = await (await getOcrEngine(config.handwriting)).recognizePage(rgba, width, height, altStats, tuning, cellRules, rowRules);
2076
+ if (meanConfidence(alt) - base < AUTO_REC_GAIN) return items;
2077
+ Object.assign(stats, altStats);
2078
+ return alt;
2079
+ }
1947
2080
  var announced = /* @__PURE__ */ new Set();
1948
2081
  function reportModelDownload(p) {
1949
2082
  if (p.phase !== "download" || announced.has(p.spec.filename)) return;
@@ -1974,6 +2107,7 @@ async function runPdfOcr(buffer, targets, mode, warnings, onProgress, detectTabl
1974
2107
  () => import("@hyzyla/pdfium")
1975
2108
  );
1976
2109
  if (mode === "builtin") await ensureOcrModelsFor(config.models, reportModelDownload);
2110
+ if (mode === "builtin" && config.handwriting) await ensureOcrModelsFor(config.handwriting, reportModelDownload);
1977
2111
  const engine = mode === "builtin" ? await getOcrEngine(config.models) : null;
1978
2112
  const pdfium = await pdfiumMod.PDFiumLibrary.init();
1979
2113
  const doc = await pdfium.loadDocument(new Uint8Array(buffer));
@@ -2023,12 +2157,14 @@ async function ocrOnePage(page, pageNo, mode, engine, warnings, detectTables, ve
2023
2157
  const stats = { droppedLowConf: 0, ...config.lines === "all" ? { dropped: [] } : {} };
2024
2158
  const ruling = vectorOps ? void 0 : detectRulingLines(upright, rw, rh, rh / pdfH);
2025
2159
  const tuning = regions ? regionTuning(config.tuning) : config.tuning;
2026
- const items = await engine.recognizePage(
2160
+ const items = await recognizeOcrPage(
2161
+ engine,
2162
+ config,
2163
+ tuning,
2027
2164
  upright,
2028
2165
  rw,
2029
2166
  rh,
2030
2167
  stats,
2031
- tuning,
2032
2168
  ruling ? [...ruling.cellDividers ?? [], ...cellEdgeVerticals(ruling)] : void 0,
2033
2169
  ruling ? cellEdgeHorizontals(ruling) : void 0
2034
2170
  );
@@ -2175,6 +2311,7 @@ function groupOcrLines(items, figureRegion = false) {
2175
2311
  function ocrItemsToBlocks(items, pageNumber, pdfW, pdfH, scale, extraLines, detectTables = true, opList = { fnArray: [], argsArray: [] }, figureRegion = false) {
2176
2312
  const bottoms = items.map((it) => it.y + it.h);
2177
2313
  const xs = items.map((it) => ({ x: it.x, r: it.x + it.w, space: false }));
2314
+ const texts = items.map((it) => it.text);
2178
2315
  for (const line of groupOcrLines(items, figureRegion)) {
2179
2316
  if (line.length < 2) continue;
2180
2317
  const sorted = line.map((j) => items[j].y + items[j].h).sort((a, b) => a - b);
@@ -2183,6 +2320,13 @@ function ocrItemsToBlocks(items, pageNumber, pdfW, pdfH, scale, extraLines, dete
2183
2320
  for (let k = 1; k < byX.length; k++) {
2184
2321
  const L = xs[byX[k - 1]], R = xs[byX[k]];
2185
2322
  if (R.x >= L.r) continue;
2323
+ const lt = [...texts[byX[k - 1]]], rt = [...texts[byX[k]]], per = items[byX[k]].w / Math.max(1, rt.length);
2324
+ for (let n = Math.min(3, rt.length, lt.length); n >= 1; n--) {
2325
+ if (lt.slice(-n).join("") === rt.slice(0, n).join("") && /[가-힣]/.test(rt[0]) && L.r - R.x >= per * n * 0.5) {
2326
+ texts[byX[k]] = rt.slice(n).join("");
2327
+ break;
2328
+ }
2329
+ }
2186
2330
  const mid = (R.x + L.r) / 2, half = Math.min(items[byX[k - 1]].h, items[byX[k]].h) * 0.15;
2187
2331
  L.r = Math.max(L.x + 1, mid - half);
2188
2332
  R.x = Math.min(R.r - 1, mid + half);
@@ -2192,7 +2336,7 @@ function ocrItemsToBlocks(items, pageNumber, pdfW, pdfH, scale, extraLines, dete
2192
2336
  const norm = items.map((it, i) => {
2193
2337
  const h = (figureRegion ? Math.max(1, bottoms[i] - it.y) : it.h) / scale;
2194
2338
  const normalized = {
2195
- text: it.text,
2339
+ text: texts[i],
2196
2340
  x: Math.round(xs[i].x / scale),
2197
2341
  // NormItem.y 는 pdfjs transform[5] = 베이스라인 (bottom-up) — 잉크 하단으로 근사
2198
2342
  y: Math.round(pdfH - bottoms[i] / scale),
@@ -2206,8 +2350,50 @@ function ocrItemsToBlocks(items, pageNumber, pdfW, pdfH, scale, extraLines, dete
2206
2350
  };
2207
2351
  copyOcrTextGaps(it, normalized, 1 / scale);
2208
2352
  return normalized;
2209
- });
2210
- return extractPageBlocksWithLines(norm, pageNumber, opList, pdfW, pdfH, extraLines, detectTables);
2353
+ }).filter((it) => it.text);
2354
+ const blocks = unframeOcrLayoutTables(splitStackedOcrRows(extractPageBlocksWithLines(norm, pageNumber, opList, pdfW, pdfH, extraLines, detectTables)), pdfH);
2355
+ if (!figureRegion) markOcrHeadings(blocks);
2356
+ return blocks;
2357
+ }
2358
+ function markOcrHeadings(blocks) {
2359
+ const sized = blocks.filter((b) => b.type === "paragraph" && b.text && b.style?.fontSize);
2360
+ const weights = sized.map((b) => [b.style.fontSize, b.text.length]);
2361
+ const total = weights.reduce((n, [, w]) => n + w, 0);
2362
+ if (total < 100) return;
2363
+ weights.sort((a, b) => a[0] - b[0]);
2364
+ let acc = 0, body = 0;
2365
+ for (const [size, w] of weights) {
2366
+ acc += w;
2367
+ if (acc >= total / 2) {
2368
+ body = size;
2369
+ break;
2370
+ }
2371
+ }
2372
+ if (!body) return;
2373
+ for (const b of sized) {
2374
+ const text = b.text.trim(), size = b.style.fontSize;
2375
+ const ratio = size / body;
2376
+ if (ratio < 1.3) continue;
2377
+ if (text.length < 2 || text.length > (ratio >= 1.5 ? 60 : 40) || /[.!?。]$|다\.?$/.test(text) || /^[\d\s.,%·-]+$/.test(text)) continue;
2378
+ if (b.bbox && b.bbox.height > size * (ratio >= 1.5 ? 3.2 : 1.6)) continue;
2379
+ b.type = "heading";
2380
+ b.level = ratio >= 1.5 ? 1 : 2;
2381
+ }
2382
+ for (let i = blocks.length - 1; i > 0; i--) {
2383
+ const a = blocks[i - 1], b = blocks[i];
2384
+ if (a.type !== "heading" || b.type !== "heading" || !a.bbox || !b.bbox) continue;
2385
+ const marker = /^(?:제\s*\d+\s*[장절편부]|chapter\s*\d+|part\s*\d+)$/i.test(a.text.trim());
2386
+ const sa = a.style?.fontSize ?? 0, sb = b.style?.fontSize ?? 0;
2387
+ if (!marker && (a.level !== b.level || Math.max(sa, sb) > Math.min(sa, sb) * 1.3)) continue;
2388
+ const gap = a.bbox.y - (b.bbox.y + b.bbox.height), size = Math.max(a.style?.fontSize ?? 0, b.style?.fontSize ?? 0);
2389
+ const across = Math.min(a.bbox.x + a.bbox.width, b.bbox.x + b.bbox.width) - Math.max(a.bbox.x, b.bbox.x);
2390
+ if (gap < 0 || gap > size * (marker ? 3 : 1.2) || across <= 0) continue;
2391
+ a.level = Math.min(a.level ?? 2, b.level ?? 2);
2392
+ a.text = `${a.text.trim()} ${b.text.trim()}`;
2393
+ const x = Math.min(a.bbox.x, b.bbox.x);
2394
+ a.bbox = { ...a.bbox, x, y: b.bbox.y, width: Math.max(a.bbox.x + a.bbox.width, b.bbox.x + b.bbox.width) - x, height: a.bbox.y + a.bbox.height - b.bbox.y };
2395
+ blocks.splice(i, 1);
2396
+ }
2211
2397
  }
2212
2398
  async function tryImport2(name, loader) {
2213
2399
  try {
@@ -2252,6 +2438,7 @@ export {
2252
2438
  MAX_OCR_PIXELS,
2253
2439
  DEFAULT_BUILTIN_OCR,
2254
2440
  builtinOcrConfig,
2441
+ recognizeOcrPage,
2255
2442
  reportModelDownload,
2256
2443
  toOcrLine,
2257
2444
  pageLines,
@@ -2260,6 +2447,7 @@ export {
2260
2447
  runPdfOcr,
2261
2448
  ocrOnePage,
2262
2449
  groupOcrLines,
2263
- ocrItemsToBlocks
2450
+ ocrItemsToBlocks,
2451
+ markOcrHeadings
2264
2452
  };
2265
- //# sourceMappingURL=chunk-BPBY6762.js.map
2453
+ //# sourceMappingURL=chunk-5IYRVLGJ.js.map