kordoc 4.24.35 → 4.24.37

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 (193) hide show
  1. package/README.md +6 -5
  2. package/dist/{-2E4K77IT.js → -HX3RWDQF.js} +22 -22
  3. package/dist/{chunk-X3K7J6KP.js → chunk-2LDFUVVF.js} +4 -4
  4. package/dist/{chunk-HSAD274D.js → chunk-33BKUIIC.js} +2 -2
  5. package/dist/{chunk-J4RNO2J4.js → chunk-4MWGWS3B.js} +2 -2
  6. package/dist/{chunk-NVGJZZJZ.js → chunk-4OWD6WRS.js} +152 -15
  7. package/dist/chunk-4OWD6WRS.js.map +1 -0
  8. package/dist/{chunk-Q2R7VRJY.js → chunk-524MGRNW.js} +153 -16
  9. package/dist/chunk-524MGRNW.js.map +1 -0
  10. package/dist/{chunk-QNSLRHBL.js → chunk-64MVXWS5.js} +2 -2
  11. package/dist/{chunk-GHYRE6BO.js → chunk-6QK6ZQTF.js} +3 -3
  12. package/dist/{chunk-JRK2WVVB.js → chunk-6SVVVZLV.js} +7 -3
  13. package/dist/chunk-6SVVVZLV.js.map +1 -0
  14. package/dist/{chunk-F25GZK3B.js → chunk-6XOJ6CEK.js} +3 -3
  15. package/dist/{chunk-6XO2M6UP.js → chunk-7GN7O3K2.js} +11 -11
  16. package/dist/{chunk-ZCM2RLTI.js → chunk-7MGZWDO4.js} +2 -2
  17. package/dist/{chunk-ZCM2RLTI.js.map → chunk-7MGZWDO4.js.map} +1 -1
  18. package/dist/{chunk-D7FENV7F.js → chunk-B2VRTOQZ.js} +6 -6
  19. package/dist/{chunk-F7MA5KWA.js → chunk-BT5RPLV4.js} +6 -6
  20. package/dist/{chunk-E6ZXDD6B.js → chunk-BYOAA442.js} +2 -2
  21. package/dist/{chunk-SNNWVEQR.js → chunk-DRDW5ULI.js} +5 -5
  22. package/dist/{chunk-AY5WCVGD.cjs → chunk-E527FG6I.cjs} +3 -3
  23. package/dist/{chunk-AY5WCVGD.cjs.map → chunk-E527FG6I.cjs.map} +1 -1
  24. package/dist/{chunk-IL7RLPEW.cjs → chunk-EJKCEP3Q.cjs} +6 -6
  25. package/dist/{chunk-IL7RLPEW.cjs.map → chunk-EJKCEP3Q.cjs.map} +1 -1
  26. package/dist/{chunk-FDVVHEFI.js → chunk-GCFG67SF.js} +16 -16
  27. package/dist/{chunk-G5JSF2AY.js → chunk-IFWGHRCQ.js} +3 -3
  28. package/dist/chunk-IFWGHRCQ.js.map +1 -0
  29. package/dist/{chunk-DKQD2257.cjs → chunk-IOABX3GR.cjs} +448 -179
  30. package/dist/chunk-IOABX3GR.cjs.map +1 -0
  31. package/dist/{chunk-Z4TAVTCF.js → chunk-IWCWO3GP.js} +2 -2
  32. package/dist/{chunk-MFORI4AF.js → chunk-J26TNCL7.js} +2 -2
  33. package/dist/{chunk-T66JN7AP.js → chunk-J42O5KBX.js} +2 -2
  34. package/dist/{chunk-IQJKLIJV.js → chunk-JIMG76FW.js} +3 -3
  35. package/dist/chunk-JIMG76FW.js.map +1 -0
  36. package/dist/{chunk-YU2WR5ZT.js → chunk-JRVZBYQ2.js} +301 -32
  37. package/dist/chunk-JRVZBYQ2.js.map +1 -0
  38. package/dist/{chunk-ONRVMMIU.js → chunk-KFJKRST5.js} +295 -30
  39. package/dist/chunk-KFJKRST5.js.map +1 -0
  40. package/dist/{chunk-TOYB5KCE.cjs → chunk-KGX4TLBX.cjs} +3 -3
  41. package/dist/{chunk-TOYB5KCE.cjs.map → chunk-KGX4TLBX.cjs.map} +1 -1
  42. package/dist/{chunk-4ZTFAQQ5.cjs → chunk-LG4SU2D4.cjs} +57 -57
  43. package/dist/{chunk-4ZTFAQQ5.cjs.map → chunk-LG4SU2D4.cjs.map} +1 -1
  44. package/dist/{chunk-QWJ5KIWV.js → chunk-LM7LYH54.js} +2 -2
  45. package/dist/{chunk-DRNG6PLM.js → chunk-LWSMZJLR.js} +2 -2
  46. package/dist/{chunk-5CWZMORL.js → chunk-MTSWWXSB.js} +2 -2
  47. package/dist/{chunk-SHZ4NZPS.js → chunk-OHLJEWS6.js} +2 -2
  48. package/dist/{chunk-N5XWNVSD.js → chunk-ONCXLQV4.js} +3 -3
  49. package/dist/{chunk-24TTJGKB.js → chunk-OTB6UOIB.js} +2 -2
  50. package/dist/{chunk-N7IJJD5D.js → chunk-P3SZJJRK.js} +5 -5
  51. package/dist/{chunk-W5TUVES3.cjs → chunk-QWYIYVIZ.cjs} +2 -2
  52. package/dist/{chunk-W5TUVES3.cjs.map → chunk-QWYIYVIZ.cjs.map} +1 -1
  53. package/dist/{chunk-WJKVPRPH.cjs → chunk-RBCDHT55.cjs} +98 -98
  54. package/dist/{chunk-WJKVPRPH.cjs.map → chunk-RBCDHT55.cjs.map} +1 -1
  55. package/dist/{chunk-ZEXTE425.js → chunk-RR2FKPFN.js} +2 -2
  56. package/dist/{chunk-ZEXTE425.js.map → chunk-RR2FKPFN.js.map} +1 -1
  57. package/dist/{chunk-2F7XY5YH.js → chunk-T7K3CLXM.js} +2 -2
  58. package/dist/{chunk-LQXAR5I6.js → chunk-TQBNSAAS.js} +2 -2
  59. package/dist/{chunk-TZOBWD7J.js → chunk-UMQADPM5.js} +2 -2
  60. package/dist/{chunk-IWCCZ3SL.js → chunk-VRCXXZHT.js} +2 -2
  61. package/dist/{chunk-NMTVPGAG.cjs → chunk-WZUTLJP2.cjs} +250 -113
  62. package/dist/chunk-WZUTLJP2.cjs.map +1 -0
  63. package/dist/{chunk-QQUXPVOH.js → chunk-XCMDIXWE.js} +10 -10
  64. package/dist/{chunk-FGPDVC3H.js → chunk-YNIZLSM4.js} +6 -6
  65. package/dist/{chunk-E6QNUE6M.cjs → chunk-ZTN3T3GH.cjs} +3 -3
  66. package/dist/chunk-ZTN3T3GH.cjs.map +1 -0
  67. package/dist/chunks-NRA4GFK2.js +11 -0
  68. package/dist/cli.js +46 -46
  69. package/dist/cli.js.map +1 -1
  70. package/dist/{formula-CONAN5NV.js → formula-2MQWKUKR.js} +3 -3
  71. package/dist/{formula-5CRHNMRG.js → formula-DLZUYM6F.js} +4 -4
  72. package/dist/{formula-F7X5NBGL.cjs → formula-T2YY77JT.cjs} +9 -9
  73. package/dist/{formula-F7X5NBGL.cjs.map → formula-T2YY77JT.cjs.map} +1 -1
  74. package/dist/generator-2WFWLJFG.js +10 -0
  75. package/dist/generator-5NWKWLOO.js +17 -0
  76. package/dist/generator-YBWCA6B5.cjs +11 -0
  77. package/dist/{generator-7QULUIZN.cjs.map → generator-YBWCA6B5.cjs.map} +1 -1
  78. package/dist/{image-ocr-MGYQA47N.js → image-ocr-3NF3QJM7.js} +26 -12
  79. package/dist/image-ocr-3NF3QJM7.js.map +1 -0
  80. package/dist/{image-ocr-N2KTIH26.cjs → image-ocr-N7UKGSRY.cjs} +33 -19
  81. package/dist/image-ocr-N7UKGSRY.cjs.map +1 -0
  82. package/dist/{image-ocr-CNN2BO6A.js → image-ocr-XRKFK7AA.js} +23 -9
  83. package/dist/image-ocr-XRKFK7AA.js.map +1 -0
  84. package/dist/index.cjs +1107 -1107
  85. package/dist/index.d.cts +3 -2
  86. package/dist/index.d.ts +3 -2
  87. package/dist/index.js +10 -10
  88. package/dist/mcp.js +38 -38
  89. package/dist/mcp.js.map +1 -1
  90. package/dist/memo-XJ4NBV4J.js +17 -0
  91. package/dist/{model-bundle-CBAGMKXY.js → model-bundle-MO5I26AO.js} +5 -5
  92. package/dist/{models-4F3PY6ZH.js → models-IIP4GZ57.js} +7 -5
  93. package/dist/{parse-worker-v2-NY4RCZH2.js → parse-worker-v2-GWOL3TTO.js} +24 -24
  94. package/dist/parse-worker-v2-GWOL3TTO.js.map +1 -0
  95. package/dist/{parser-V3T4JIHZ.js → parser-DJQFM6VE.js} +20 -14
  96. package/dist/{parser-SDYZKFGP.js.map → parser-DJQFM6VE.js.map} +1 -1
  97. package/dist/{parser-SGTP2HRJ.cjs → parser-R6UJQ6CV.cjs} +200 -194
  98. package/dist/parser-R6UJQ6CV.cjs.map +1 -0
  99. package/dist/{parser-SDYZKFGP.js → parser-UCLUVQPA.js} +17 -11
  100. package/dist/{parser-V3T4JIHZ.js.map → parser-UCLUVQPA.js.map} +1 -1
  101. package/dist/{pdf-ocr-OMPJG4A3.js → pdf-ocr-7PSNKQOI.js} +12 -10
  102. package/dist/{pdf-ocr-N67HU6JI.js → pdf-ocr-EWXGN4JB.js} +9 -7
  103. package/dist/pdf-ocr-HECUBUMU.cjs +38 -0
  104. package/dist/pdf-ocr-HECUBUMU.cjs.map +1 -0
  105. package/dist/{profile-io-3NMJP3MS.js → profile-io-NUU3VKNW.js} +3 -3
  106. package/dist/{rasterize-6KOICUFD.js → rasterize-RARCMBJ3.js} +3 -3
  107. package/dist/{redact-doc-AYVTPSJ7.js → redact-doc-JKGDXGNJ.js} +22 -22
  108. package/dist/{render-G2S6NLNT.js → render-SGLKO3PG.js} +12 -12
  109. package/dist/{rewrite-fallback-6X6RAE44.js → rewrite-fallback-7YZXSR5K.js} +13 -13
  110. package/dist/seal-LSS7WY6Z.js +16 -0
  111. package/dist/{setup-MPJIJVRK.js → setup-5IYW4FZ7.js} +3 -3
  112. package/dist/{to-hwpx-4LMIQ72J.js → to-hwpx-4PM6XV2L.js} +4 -4
  113. package/dist/to-hwpx-DEBHREDM.cjs +16 -0
  114. package/dist/{to-hwpx-KWYINCWF.cjs.map → to-hwpx-DEBHREDM.cjs.map} +1 -1
  115. package/dist/{to-hwpx-DIDCKP5A.js → to-hwpx-QMANIAWY.js} +7 -7
  116. package/dist/visual-QXYBJJ5D.js +37 -0
  117. package/dist/{watch-YPXIB3MW.js → watch-YZS76EOS.js} +23 -23
  118. package/package.json +1 -1
  119. package/dist/chunk-DKQD2257.cjs.map +0 -1
  120. package/dist/chunk-E6QNUE6M.cjs.map +0 -1
  121. package/dist/chunk-G5JSF2AY.js.map +0 -1
  122. package/dist/chunk-IQJKLIJV.js.map +0 -1
  123. package/dist/chunk-JRK2WVVB.js.map +0 -1
  124. package/dist/chunk-NMTVPGAG.cjs.map +0 -1
  125. package/dist/chunk-NVGJZZJZ.js.map +0 -1
  126. package/dist/chunk-ONRVMMIU.js.map +0 -1
  127. package/dist/chunk-Q2R7VRJY.js.map +0 -1
  128. package/dist/chunk-YU2WR5ZT.js.map +0 -1
  129. package/dist/chunks-YK64NQKA.js +0 -11
  130. package/dist/generator-7QULUIZN.cjs +0 -11
  131. package/dist/generator-QRTY3A2B.js +0 -10
  132. package/dist/generator-S3CJBUNO.js +0 -17
  133. package/dist/image-ocr-CNN2BO6A.js.map +0 -1
  134. package/dist/image-ocr-MGYQA47N.js.map +0 -1
  135. package/dist/image-ocr-N2KTIH26.cjs.map +0 -1
  136. package/dist/memo-3D7ZMPXA.js +0 -17
  137. package/dist/parse-worker-v2-NY4RCZH2.js.map +0 -1
  138. package/dist/parser-SGTP2HRJ.cjs.map +0 -1
  139. package/dist/pdf-ocr-4VVHZACS.cjs +0 -36
  140. package/dist/pdf-ocr-4VVHZACS.cjs.map +0 -1
  141. package/dist/seal-3YKKLMQE.js +0 -16
  142. package/dist/to-hwpx-KWYINCWF.cjs +0 -16
  143. package/dist/visual-BMEMSXMR.js +0 -37
  144. /package/dist/{-2E4K77IT.js.map → -HX3RWDQF.js.map} +0 -0
  145. /package/dist/{chunk-X3K7J6KP.js.map → chunk-2LDFUVVF.js.map} +0 -0
  146. /package/dist/{chunk-HSAD274D.js.map → chunk-33BKUIIC.js.map} +0 -0
  147. /package/dist/{chunk-J4RNO2J4.js.map → chunk-4MWGWS3B.js.map} +0 -0
  148. /package/dist/{chunk-QNSLRHBL.js.map → chunk-64MVXWS5.js.map} +0 -0
  149. /package/dist/{chunk-GHYRE6BO.js.map → chunk-6QK6ZQTF.js.map} +0 -0
  150. /package/dist/{chunk-F25GZK3B.js.map → chunk-6XOJ6CEK.js.map} +0 -0
  151. /package/dist/{chunk-6XO2M6UP.js.map → chunk-7GN7O3K2.js.map} +0 -0
  152. /package/dist/{chunk-D7FENV7F.js.map → chunk-B2VRTOQZ.js.map} +0 -0
  153. /package/dist/{chunk-F7MA5KWA.js.map → chunk-BT5RPLV4.js.map} +0 -0
  154. /package/dist/{chunk-E6ZXDD6B.js.map → chunk-BYOAA442.js.map} +0 -0
  155. /package/dist/{chunk-SNNWVEQR.js.map → chunk-DRDW5ULI.js.map} +0 -0
  156. /package/dist/{chunk-FDVVHEFI.js.map → chunk-GCFG67SF.js.map} +0 -0
  157. /package/dist/{chunk-Z4TAVTCF.js.map → chunk-IWCWO3GP.js.map} +0 -0
  158. /package/dist/{chunk-MFORI4AF.js.map → chunk-J26TNCL7.js.map} +0 -0
  159. /package/dist/{chunk-T66JN7AP.js.map → chunk-J42O5KBX.js.map} +0 -0
  160. /package/dist/{chunk-QWJ5KIWV.js.map → chunk-LM7LYH54.js.map} +0 -0
  161. /package/dist/{chunk-DRNG6PLM.js.map → chunk-LWSMZJLR.js.map} +0 -0
  162. /package/dist/{chunk-5CWZMORL.js.map → chunk-MTSWWXSB.js.map} +0 -0
  163. /package/dist/{chunk-SHZ4NZPS.js.map → chunk-OHLJEWS6.js.map} +0 -0
  164. /package/dist/{chunk-N5XWNVSD.js.map → chunk-ONCXLQV4.js.map} +0 -0
  165. /package/dist/{chunk-24TTJGKB.js.map → chunk-OTB6UOIB.js.map} +0 -0
  166. /package/dist/{chunk-N7IJJD5D.js.map → chunk-P3SZJJRK.js.map} +0 -0
  167. /package/dist/{chunk-2F7XY5YH.js.map → chunk-T7K3CLXM.js.map} +0 -0
  168. /package/dist/{chunk-LQXAR5I6.js.map → chunk-TQBNSAAS.js.map} +0 -0
  169. /package/dist/{chunk-TZOBWD7J.js.map → chunk-UMQADPM5.js.map} +0 -0
  170. /package/dist/{chunk-IWCCZ3SL.js.map → chunk-VRCXXZHT.js.map} +0 -0
  171. /package/dist/{chunk-QQUXPVOH.js.map → chunk-XCMDIXWE.js.map} +0 -0
  172. /package/dist/{chunk-FGPDVC3H.js.map → chunk-YNIZLSM4.js.map} +0 -0
  173. /package/dist/{chunks-YK64NQKA.js.map → chunks-NRA4GFK2.js.map} +0 -0
  174. /package/dist/{formula-CONAN5NV.js.map → formula-2MQWKUKR.js.map} +0 -0
  175. /package/dist/{formula-5CRHNMRG.js.map → formula-DLZUYM6F.js.map} +0 -0
  176. /package/dist/{generator-QRTY3A2B.js.map → generator-2WFWLJFG.js.map} +0 -0
  177. /package/dist/{generator-S3CJBUNO.js.map → generator-5NWKWLOO.js.map} +0 -0
  178. /package/dist/{memo-3D7ZMPXA.js.map → memo-XJ4NBV4J.js.map} +0 -0
  179. /package/dist/{model-bundle-CBAGMKXY.js.map → model-bundle-MO5I26AO.js.map} +0 -0
  180. /package/dist/{models-4F3PY6ZH.js.map → models-IIP4GZ57.js.map} +0 -0
  181. /package/dist/{pdf-ocr-N67HU6JI.js.map → pdf-ocr-7PSNKQOI.js.map} +0 -0
  182. /package/dist/{pdf-ocr-OMPJG4A3.js.map → pdf-ocr-EWXGN4JB.js.map} +0 -0
  183. /package/dist/{profile-io-3NMJP3MS.js.map → profile-io-NUU3VKNW.js.map} +0 -0
  184. /package/dist/{rasterize-6KOICUFD.js.map → rasterize-RARCMBJ3.js.map} +0 -0
  185. /package/dist/{redact-doc-AYVTPSJ7.js.map → redact-doc-JKGDXGNJ.js.map} +0 -0
  186. /package/dist/{render-G2S6NLNT.js.map → render-SGLKO3PG.js.map} +0 -0
  187. /package/dist/{rewrite-fallback-6X6RAE44.js.map → rewrite-fallback-7YZXSR5K.js.map} +0 -0
  188. /package/dist/{seal-3YKKLMQE.js.map → seal-LSS7WY6Z.js.map} +0 -0
  189. /package/dist/{setup-MPJIJVRK.js.map → setup-5IYW4FZ7.js.map} +0 -0
  190. /package/dist/{to-hwpx-4LMIQ72J.js.map → to-hwpx-4PM6XV2L.js.map} +0 -0
  191. /package/dist/{to-hwpx-DIDCKP5A.js.map → to-hwpx-QMANIAWY.js.map} +0 -0
  192. /package/dist/{visual-BMEMSXMR.js.map → visual-QXYBJJ5D.js.map} +0 -0
  193. /package/dist/{watch-YPXIB3MW.js.map → watch-YZS76EOS.js.map} +0 -0
package/README.md CHANGED
@@ -111,20 +111,21 @@ await writeFile("보고서.hwpx", Buffer.from(hwpx))
111
111
 
112
112
  ## 검증 결과
113
113
 
114
- **4.22.14 · 2026-10-19 검증**(렌더 배치·별표는 4.24.24 · 2026-10-30, PDF 글·표는 4.24.35 · 2026-11-01, OCR 은 4.24.32 · 2026-11-01, HWP → HWPX 변환은 4.24.29 · 2026-10-31). 고정 코퍼스와 참조 기준으로 측정한 결과입니다.
114
+ **4.22.14 · 2026-10-19 검증**(렌더 배치·별표는 4.24.24 · 2026-10-30, PDF 글은 4.24.36 · 2026-11-01, PDF 표는 4.24.35 · 2026-11-01, OCR 은 4.24.32 · 2026-11-01, HWP → HWPX 변환은 4.24.29 · 2026-10-31). 고정 코퍼스와 참조 기준으로 측정한 결과입니다.
115
115
 
116
116
  | 대상 | 규모 | 결과 |
117
117
  | --- | --- | --- |
118
118
  | HWPX | 2,424문서 · 10,342표 | 표 구조 일치 **10,342/10,342 · 100%** |
119
119
  | HWP 5.x ↔ HWPX | 1,130쌍 · 4,315표 | 짝 문서의 표 구조 일치 **100%** |
120
120
  | HWP 5.x → HWPX 변환 | 한글 저장 짝 264쌍 (속성 대조 232쌍) | 속성 일치 **99.21%** · 완전 일치 **69/232** · 글 **264/264** · 한글 화면 대조 80문서 |
121
- | PDF 글 | 744쌍 | 글자 재현율 **99.885%** · 정확도 **99.682%** · 읽기 순서 **99.360%** · 어절 F1 **99.076%** |
121
+ | PDF 글 | 744쌍 | 글자 재현율 **99.885%** · 정확도 **99.682%** · 읽기 순서 **99.360%** · 어절 F1 **99.103%** |
122
122
  | PDF 표 | 708쌍 · 2,331표 | 표 탐지 **99.83%** · 구조 일치 **98.24%** · 칸 F1 **0.9919** |
123
123
  | HWPX 재조판 | 다중줄 25,187문단 (12개 폴더) | 모든 줄 시작 일치 **24,941/25,187 · 99.02%** |
124
124
  | PDF 중첩표 (텍스트층) | 29문서 · 101표 | 구조·칸 내용 **101/101 · 100%** |
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.807** · `ocrRec: "auto"` **0.828** (4.24.37) |
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.35](https://github.com/chrisryugj/kordoc/releases/tag/v4.24.35)** · 2026-11-01
136
+ 최신 배포: **[4.24.37](https://github.com/chrisryugj/kordoc/releases/tag/v4.24.37)** · 2026-11-02
136
137
 
137
138
  | 버전 | 주요 변경 | 수치 변화 |
138
139
  | --- | --- | --- |
140
+ | 4.24.37 | OCR 표: 통계표 묶음 안 빈칸 열·괘선 없는 본문 행 펼치기·행 합침 아래 행 가르기·이웃 칸 값 몰림·음영 머리 띠와 칠한 첫 열 경계 · 머리 행 있는 표를 차트로 풀던 것 · 시험지 문항 상자 풀기 · `ocrRec: "auto"`(손글씨 쪽만 손글씨 모델) | KoOCR-Bench v2.0c 공개 877쪽 종합 0.742 → **0.807**(`ocrRec: "auto"` **0.828**) · 자체 벤치 문서별 그대로 |
141
+ | 4.24.36 | PDF 띄어쓰기(한컴 PDF): 자간 줄인 글의 낱말 공백 되살림 · 공백 글리프 위에 덧찍은 쉼표("55만원,") · 날짜 "2026. 8. 28." 공백 유지 · 줄 끝에서 갈린 연도 "20⏎25년" | PDF 글 어절 F1 99.076 → **99.103%** · 그 밖 지표 그대로 |
139
142
  | 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%** · 그 밖 지표 그대로 |
140
143
  | 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%** · 그 밖 지표 그대로 |
141
144
  | 4.24.33 | PDF 표: 모아찍기 쪽 넘김에서 오른쪽 쪽 머리 상자로 넘어간 칸 글을 앞 칸에 잇기 · 칸을 한 겹 더 깐 1×1 클립(통계표 숫자 칸)을 중첩 표 대신 칸 글로 | PDF 표 칸 내용 일치 0.98794 → **0.98896** · 칸 완전 일치 0.99153 → **0.99221** · 그 밖 벤치 지표 그대로 |
142
- | 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(정답 변화, 채점 그대로) |
143
- | 4.24.31 | PDF 읽기 순서: 그림 위주 2-up 쪽 · 결재문서 머리 나란한 표(문서번호 → 결재 칸) · PDF 가짜 표: 고시안 머리 "○○고시 제 호" 아래 본문 · HWP 3 기술 교재 고정폭 낱말 줄 | PDF 글 읽기 순서 99.312% → **99.348%** · 정확도 99.678% → **99.680%** · 표 순서 뒤바뀜 5 → **2** · 그 밖 벤치 지표 그대로 |
144
145
 
145
146
  이전 버전과 상세 내용은 [변경 이력](CHANGELOG.md)에 있습니다.
146
147
 
@@ -53,26 +53,26 @@ import {
53
53
  usesGaejosikMunche,
54
54
  usesLogicStructure,
55
55
  validateHwpx
56
- } from "./chunk-FDVVHEFI.js";
56
+ } from "./chunk-GCFG67SF.js";
57
57
  import {
58
58
  blocksToPages
59
- } from "./chunk-MFORI4AF.js";
59
+ } from "./chunk-J26TNCL7.js";
60
60
  import {
61
61
  extractClickHereFields,
62
62
  patchHwp,
63
63
  patchHwpx
64
- } from "./chunk-6XO2M6UP.js";
64
+ } from "./chunk-7GN7O3K2.js";
65
65
  import {
66
66
  addMemoHwpx
67
- } from "./chunk-FGPDVC3H.js";
67
+ } from "./chunk-YNIZLSM4.js";
68
68
  import {
69
69
  placeSealHwpx
70
- } from "./chunk-N7IJJD5D.js";
71
- import "./chunk-IWCCZ3SL.js";
70
+ } from "./chunk-P3SZJJRK.js";
71
+ import "./chunk-VRCXXZHT.js";
72
72
  import {
73
73
  convertToHwpx,
74
74
  hwpToHwpx
75
- } from "./chunk-D7FENV7F.js";
75
+ } from "./chunk-B2VRTOQZ.js";
76
76
  import {
77
77
  DEFAULT_REDACT_RULES,
78
78
  redactMarkdown,
@@ -80,7 +80,7 @@ import {
80
80
  } from "./chunk-3WSNLH4B.js";
81
81
  import {
82
82
  blocksToChunks
83
- } from "./chunk-5CWZMORL.js";
83
+ } from "./chunk-MTSWWXSB.js";
84
84
  import {
85
85
  blocksToPdf,
86
86
  extractRenderedRegions,
@@ -91,13 +91,13 @@ import {
91
91
  renderHwp5Pages,
92
92
  renderHwpxToSvg,
93
93
  renderSceneToHtml
94
- } from "./chunk-QQUXPVOH.js";
95
- import "./chunk-N5XWNVSD.js";
96
- import "./chunk-2F7XY5YH.js";
97
- import "./chunk-SNNWVEQR.js";
94
+ } from "./chunk-XCMDIXWE.js";
95
+ import "./chunk-ONCXLQV4.js";
96
+ import "./chunk-T7K3CLXM.js";
97
+ import "./chunk-DRDW5ULI.js";
98
98
  import "./chunk-VKVDMH7X.js";
99
- import "./chunk-QWJ5KIWV.js";
100
- import "./chunk-T66JN7AP.js";
99
+ import "./chunk-LM7LYH54.js";
100
+ import "./chunk-J42O5KBX.js";
101
101
  import "./chunk-VQFA55IC.js";
102
102
  import {
103
103
  detectFormat,
@@ -109,7 +109,7 @@ import {
109
109
  isZipFile
110
110
  } from "./chunk-GHHVT4Q5.js";
111
111
  import "./chunk-H7K5YHWQ.js";
112
- import "./chunk-IQJKLIJV.js";
112
+ import "./chunk-JIMG76FW.js";
113
113
  import {
114
114
  AGENCY_COMMON_NAME,
115
115
  PRESET_ALIAS,
@@ -119,7 +119,7 @@ import {
119
119
  markdownToHwpx,
120
120
  normalizeGongmunPreset,
121
121
  placeTotalRow
122
- } from "./chunk-F7MA5KWA.js";
122
+ } from "./chunk-BT5RPLV4.js";
123
123
  import {
124
124
  ValueCursor,
125
125
  applySplices,
@@ -141,20 +141,20 @@ import {
141
141
  measureTextWidth,
142
142
  simulateWrap,
143
143
  simulateWrapKeepWord
144
- } from "./chunk-DRNG6PLM.js";
145
- import "./chunk-LQXAR5I6.js";
144
+ } from "./chunk-LWSMZJLR.js";
145
+ import "./chunk-TQBNSAAS.js";
146
146
  import "./chunk-OFVHQ2TZ.js";
147
147
  import "./chunk-WFKFG2RT.js";
148
- import "./chunk-ZCM2RLTI.js";
148
+ import "./chunk-7MGZWDO4.js";
149
149
  import {
150
150
  blocksToMarkdown,
151
151
  flattenLayoutTables,
152
152
  hasStructuredCellContent
153
- } from "./chunk-HSAD274D.js";
153
+ } from "./chunk-33BKUIIC.js";
154
154
  import "./chunk-KFF47XZ7.js";
155
155
  import {
156
156
  VERSION
157
- } from "./chunk-E6ZXDD6B.js";
157
+ } from "./chunk-BYOAA442.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=-2E4K77IT.js.map
271
+ //# sourceMappingURL=-HX3RWDQF.js.map
@@ -39,12 +39,12 @@ import {
39
39
  sanitizeText,
40
40
  tidyScriptTags,
41
41
  unframeLayoutTables
42
- } from "./chunk-ZEXTE425.js";
42
+ } from "./chunk-RR2FKPFN.js";
43
43
  import {
44
44
  KordocError,
45
45
  VERSION,
46
46
  sanitizeHref
47
- } from "./chunk-QNSLRHBL.js";
47
+ } from "./chunk-64MVXWS5.js";
48
48
 
49
49
  // src/hwp5/to-hwpx/index.ts
50
50
  import JSZip from "jszip";
@@ -5270,7 +5270,7 @@ async function convertToHwpx(input, options) {
5270
5270
  if (format === "hwpx") return { data: bytes, mode: "preserve", warnings: ["\uC774\uBBF8 HWPX \uBB38\uC11C\uC785\uB2C8\uB2E4 \u2014 \uADF8\uB300\uB85C \uB461\uB2C8\uB2E4"] };
5271
5271
  if (format !== "hwp3" && format !== "hwpml") throw new KordocError(`HWP \uBB38\uC11C\uAC00 \uC544\uB2D9\uB2C8\uB2E4 (${format})`);
5272
5272
  const { parse } = await import("./index.js");
5273
- const { markdownToHwpx } = await import("./generator-QRTY3A2B.js");
5273
+ const { markdownToHwpx } = await import("./generator-2WFWLJFG.js");
5274
5274
  const parsed = await parse(ab, { password: options?.password });
5275
5275
  if (!parsed.success) throw new KordocError(parsed.error ?? "\uBB38\uC11C\uB97C \uC77D\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4");
5276
5276
  return {
@@ -5332,4 +5332,4 @@ export {
5332
5332
  hwpToHwpx,
5333
5333
  convertToHwpx
5334
5334
  };
5335
- //# sourceMappingURL=chunk-X3K7J6KP.js.map
5335
+ //# sourceMappingURL=chunk-2LDFUVVF.js.map
@@ -5,7 +5,7 @@ import {
5
5
  import {
6
6
  escapeHtml,
7
7
  sanitizeHref
8
- } from "./chunk-E6ZXDD6B.js";
8
+ } from "./chunk-BYOAA442.js";
9
9
 
10
10
  // src/table/builder.ts
11
11
  var MAX_COLS = 200;
@@ -673,4 +673,4 @@ export {
673
673
  hasInlineCellBlocks,
674
674
  cellToMarkdown
675
675
  };
676
- //# sourceMappingURL=chunk-HSAD274D.js.map
676
+ //# sourceMappingURL=chunk-33BKUIIC.js.map
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  KordocError
4
- } from "./chunk-E6ZXDD6B.js";
4
+ } from "./chunk-BYOAA442.js";
5
5
 
6
6
  // src/hwpx/profile-io.ts
7
7
  import { z } from "zod";
@@ -89,4 +89,4 @@ export {
89
89
  formatProfileSchema,
90
90
  parseFormatProfileJson
91
91
  };
92
- //# sourceMappingURL=chunk-J4RNO2J4.js.map
92
+ //# sourceMappingURL=chunk-4MWGWS3B.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-YU2WR5ZT.js";
37
+ } from "./chunk-JRVZBYQ2.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-QNSLRHBL.js";
47
+ } from "./chunk-64MVXWS5.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
  );
@@ -2207,7 +2343,7 @@ function ocrItemsToBlocks(items, pageNumber, pdfW, pdfH, scale, extraLines, dete
2207
2343
  copyOcrTextGaps(it, normalized, 1 / scale);
2208
2344
  return normalized;
2209
2345
  });
2210
- return extractPageBlocksWithLines(norm, pageNumber, opList, pdfW, pdfH, extraLines, detectTables);
2346
+ return unframeOcrLayoutTables(splitStackedOcrRows(extractPageBlocksWithLines(norm, pageNumber, opList, pdfW, pdfH, extraLines, detectTables)), pdfH);
2211
2347
  }
2212
2348
  async function tryImport2(name, loader) {
2213
2349
  try {
@@ -2252,6 +2388,7 @@ export {
2252
2388
  MAX_OCR_PIXELS,
2253
2389
  DEFAULT_BUILTIN_OCR,
2254
2390
  builtinOcrConfig,
2391
+ recognizeOcrPage,
2255
2392
  reportModelDownload,
2256
2393
  toOcrLine,
2257
2394
  pageLines,
@@ -2262,4 +2399,4 @@ export {
2262
2399
  groupOcrLines,
2263
2400
  ocrItemsToBlocks
2264
2401
  };
2265
- //# sourceMappingURL=chunk-NVGJZZJZ.js.map
2402
+ //# sourceMappingURL=chunk-4OWD6WRS.js.map