kordoc 4.24.7 → 4.24.9

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 (142) hide show
  1. package/README.md +8 -9
  2. package/dist/{-GQDQXRQN.js → -6LXREJIR.js} +11 -11
  3. package/dist/{chunk-OMJPCWKS.js → chunk-2A54AIAG.js} +2 -2
  4. package/dist/{chunk-AYGMF6H5.cjs → chunk-2WWQ6NU5.cjs} +3 -3
  5. package/dist/chunk-2WWQ6NU5.cjs.map +1 -0
  6. package/dist/{chunk-7BZBMXWD.cjs → chunk-4BPHV36H.cjs} +6 -6
  7. package/dist/chunk-4BPHV36H.cjs.map +1 -0
  8. package/dist/{chunk-C4AY47C5.js → chunk-7NEBICPS.js} +2 -2
  9. package/dist/{chunk-7ICC55XY.js → chunk-A4TUSLGL.js} +2 -2
  10. package/dist/{chunk-FIS3WY5Q.js → chunk-BEPBXIKP.js} +2 -2
  11. package/dist/{chunk-E35YPWOX.js → chunk-C6QK3PY2.js} +2 -2
  12. package/dist/{chunk-MLFPEETP.js → chunk-D5S5S6OP.js} +2 -2
  13. package/dist/{chunk-JKZY6FX5.js → chunk-DF3YCAK6.js} +2 -2
  14. package/dist/{chunk-DZRXJEOF.js → chunk-EF4MEYFE.js} +3 -3
  15. package/dist/{chunk-ZQQGV7K2.js → chunk-ESSLFMY3.js} +6 -6
  16. package/dist/{chunk-BLWDQEBY.js → chunk-GINSLCL7.js} +2 -2
  17. package/dist/chunk-GS7T56RP.cjs.map +1 -1
  18. package/dist/chunk-J55UQC5H.cjs.map +1 -1
  19. package/dist/{chunk-WVZXPEHX.js → chunk-LVEY2XHM.js} +7 -5
  20. package/dist/chunk-LVEY2XHM.js.map +1 -0
  21. package/dist/{chunk-FQVWYJNM.js → chunk-MSPBBDR5.js} +2 -2
  22. package/dist/{chunk-EIGCAAVM.js → chunk-NDC4AGLX.js} +61 -16
  23. package/dist/chunk-NDC4AGLX.js.map +1 -0
  24. package/dist/{chunk-YQB3LPKQ.cjs → chunk-NSCYUL4P.cjs} +2 -2
  25. package/dist/chunk-NSCYUL4P.cjs.map +1 -0
  26. package/dist/{chunk-OPNMXBUR.js → chunk-O34AXZ4G.js} +2 -2
  27. package/dist/{chunk-EJPPVB2Y.js → chunk-R3DNJRDD.js} +2 -2
  28. package/dist/{chunk-GMKFXPEI.js → chunk-SB4VN4TR.js} +7 -5
  29. package/dist/chunk-SB4VN4TR.js.map +1 -0
  30. package/dist/{chunk-O4RQCDN2.js → chunk-SQCECEOH.js} +62 -17
  31. package/dist/chunk-SQCECEOH.js.map +1 -0
  32. package/dist/chunk-TK3FDAEE.cjs.map +1 -1
  33. package/dist/{chunk-MNG25FK4.cjs → chunk-TMHPNZ37.cjs} +222 -177
  34. package/dist/chunk-TMHPNZ37.cjs.map +1 -0
  35. package/dist/{chunk-EIUXBJZP.js → chunk-TO4T2M7R.js} +3 -3
  36. package/dist/{chunk-CG2Z6NB3.js → chunk-TY4PED6S.js} +2 -2
  37. package/dist/{chunk-VHAOAQZZ.cjs → chunk-UVMVLE3M.cjs} +3 -3
  38. package/dist/chunk-UVMVLE3M.cjs.map +1 -0
  39. package/dist/{chunk-DEDUWANG.js → chunk-VAR3NCSE.js} +151 -20
  40. package/dist/chunk-VAR3NCSE.js.map +1 -0
  41. package/dist/{chunk-N2JZGYFK.js → chunk-W6R7HH2Q.js} +4 -4
  42. package/dist/chunk-W6R7HH2Q.js.map +1 -0
  43. package/dist/{chunk-UDUEOBS5.js → chunk-WPA45TPL.js} +2 -2
  44. package/dist/{chunk-BNIXSB6D.js → chunk-YAKEEIR2.js} +2 -2
  45. package/dist/{chunk-V4PS653P.cjs → chunk-ZFM7UP72.cjs} +59 -57
  46. package/dist/chunk-ZFM7UP72.cjs.map +1 -0
  47. package/dist/chunks-L66VMJHU.js +10 -0
  48. package/dist/cli.js +30 -30
  49. package/dist/{formula-C55TRBHH.cjs → formula-3SARACGW.cjs} +9 -9
  50. package/dist/formula-3SARACGW.cjs.map +1 -0
  51. package/dist/{formula-6XLWGUCB.js → formula-MDHYCNKI.js} +4 -4
  52. package/dist/{formula-DCC5EOSX.js → formula-S7CTLTGZ.js} +3 -3
  53. package/dist/{image-ocr-EV7GVD3W.cjs → image-ocr-2T4NBJ73.cjs} +17 -17
  54. package/dist/image-ocr-2T4NBJ73.cjs.map +1 -0
  55. package/dist/{image-ocr-K3ZXDUNU.js → image-ocr-V6AQ4IAY.js} +6 -6
  56. package/dist/{image-ocr-L736MGYD.js → image-ocr-VDMEI7TD.js} +9 -9
  57. package/dist/index.cjs +826 -695
  58. package/dist/index.cjs.map +1 -1
  59. package/dist/index.js +146 -15
  60. package/dist/index.js.map +1 -1
  61. package/dist/mcp.js +23 -23
  62. package/dist/{model-bundle-OOOJFMGR.js → model-bundle-HCTCHMA2.js} +5 -5
  63. package/dist/{models-ELFZII4R.js → models-NJROM7JD.js} +5 -5
  64. package/dist/page-range-QHHFTL7X.cjs.map +1 -1
  65. package/dist/{parse-worker-v2-WS35WZY5.js → parse-worker-v2-ALTGW2J3.js} +12 -12
  66. package/dist/{parser-K7Y44QHS.js → parser-6J5HFER5.js} +13 -12
  67. package/dist/parser-6J5HFER5.js.map +1 -0
  68. package/dist/{parser-X6P3JBT2.cjs → parser-L3G6ON4U.cjs} +160 -159
  69. package/dist/parser-L3G6ON4U.cjs.map +1 -0
  70. package/dist/{parser-CNRI55MG.js → parser-MY4WE64G.js} +10 -9
  71. package/dist/parser-MY4WE64G.js.map +1 -0
  72. package/dist/{pdf-ocr-QQPZLIHM.js → pdf-ocr-3VVSR4LN.js} +9 -9
  73. package/dist/{pdf-ocr-MVNHXGPS.js → pdf-ocr-HUSU2XMV.js} +6 -6
  74. package/dist/pdf-ocr-RF2SCGXD.cjs +31 -0
  75. package/dist/pdf-ocr-RF2SCGXD.cjs.map +1 -0
  76. package/dist/{profile-io-GSRLMU4D.js → profile-io-SQI3X7MD.js} +3 -3
  77. package/dist/{rasterize-3H5QKDH2.js → rasterize-BHW5UZY2.js} +3 -3
  78. package/dist/{redact-doc-TLBF7J7W.js → redact-doc-KJ6M63BP.js} +11 -11
  79. package/dist/{render-FTCOQ6WO.js → render-Z6N2GGKL.js} +7 -7
  80. package/dist/seal-PM4624BB.js +10 -0
  81. package/dist/{setup-WVZ47AKV.js → setup-MUBZ674O.js} +3 -3
  82. package/dist/visual-AV46N5ZN.js +21 -0
  83. package/dist/{watch-CA32Z7XB.js → watch-D4AUDKVN.js} +12 -12
  84. package/package.json +4 -4
  85. package/dist/chunk-7BZBMXWD.cjs.map +0 -1
  86. package/dist/chunk-AYGMF6H5.cjs.map +0 -1
  87. package/dist/chunk-DEDUWANG.js.map +0 -1
  88. package/dist/chunk-EIGCAAVM.js.map +0 -1
  89. package/dist/chunk-GMKFXPEI.js.map +0 -1
  90. package/dist/chunk-MNG25FK4.cjs.map +0 -1
  91. package/dist/chunk-N2JZGYFK.js.map +0 -1
  92. package/dist/chunk-O4RQCDN2.js.map +0 -1
  93. package/dist/chunk-V4PS653P.cjs.map +0 -1
  94. package/dist/chunk-VHAOAQZZ.cjs.map +0 -1
  95. package/dist/chunk-WVZXPEHX.js.map +0 -1
  96. package/dist/chunk-YQB3LPKQ.cjs.map +0 -1
  97. package/dist/chunks-XURCANPP.js +0 -10
  98. package/dist/formula-C55TRBHH.cjs.map +0 -1
  99. package/dist/image-ocr-EV7GVD3W.cjs.map +0 -1
  100. package/dist/parser-CNRI55MG.js.map +0 -1
  101. package/dist/parser-K7Y44QHS.js.map +0 -1
  102. package/dist/parser-X6P3JBT2.cjs.map +0 -1
  103. package/dist/pdf-ocr-S6GSKOPT.cjs +0 -31
  104. package/dist/pdf-ocr-S6GSKOPT.cjs.map +0 -1
  105. package/dist/seal-74R7KDRY.js +0 -10
  106. package/dist/visual-TEW6Y3NH.js +0 -21
  107. /package/dist/{-GQDQXRQN.js.map → -6LXREJIR.js.map} +0 -0
  108. /package/dist/{chunk-OMJPCWKS.js.map → chunk-2A54AIAG.js.map} +0 -0
  109. /package/dist/{chunk-C4AY47C5.js.map → chunk-7NEBICPS.js.map} +0 -0
  110. /package/dist/{chunk-7ICC55XY.js.map → chunk-A4TUSLGL.js.map} +0 -0
  111. /package/dist/{chunk-FIS3WY5Q.js.map → chunk-BEPBXIKP.js.map} +0 -0
  112. /package/dist/{chunk-E35YPWOX.js.map → chunk-C6QK3PY2.js.map} +0 -0
  113. /package/dist/{chunk-MLFPEETP.js.map → chunk-D5S5S6OP.js.map} +0 -0
  114. /package/dist/{chunk-JKZY6FX5.js.map → chunk-DF3YCAK6.js.map} +0 -0
  115. /package/dist/{chunk-DZRXJEOF.js.map → chunk-EF4MEYFE.js.map} +0 -0
  116. /package/dist/{chunk-ZQQGV7K2.js.map → chunk-ESSLFMY3.js.map} +0 -0
  117. /package/dist/{chunk-BLWDQEBY.js.map → chunk-GINSLCL7.js.map} +0 -0
  118. /package/dist/{chunk-FQVWYJNM.js.map → chunk-MSPBBDR5.js.map} +0 -0
  119. /package/dist/{chunk-OPNMXBUR.js.map → chunk-O34AXZ4G.js.map} +0 -0
  120. /package/dist/{chunk-EJPPVB2Y.js.map → chunk-R3DNJRDD.js.map} +0 -0
  121. /package/dist/{chunk-EIUXBJZP.js.map → chunk-TO4T2M7R.js.map} +0 -0
  122. /package/dist/{chunk-CG2Z6NB3.js.map → chunk-TY4PED6S.js.map} +0 -0
  123. /package/dist/{chunk-UDUEOBS5.js.map → chunk-WPA45TPL.js.map} +0 -0
  124. /package/dist/{chunk-BNIXSB6D.js.map → chunk-YAKEEIR2.js.map} +0 -0
  125. /package/dist/{chunks-XURCANPP.js.map → chunks-L66VMJHU.js.map} +0 -0
  126. /package/dist/{formula-6XLWGUCB.js.map → formula-MDHYCNKI.js.map} +0 -0
  127. /package/dist/{formula-DCC5EOSX.js.map → formula-S7CTLTGZ.js.map} +0 -0
  128. /package/dist/{image-ocr-K3ZXDUNU.js.map → image-ocr-V6AQ4IAY.js.map} +0 -0
  129. /package/dist/{image-ocr-L736MGYD.js.map → image-ocr-VDMEI7TD.js.map} +0 -0
  130. /package/dist/{model-bundle-OOOJFMGR.js.map → model-bundle-HCTCHMA2.js.map} +0 -0
  131. /package/dist/{models-ELFZII4R.js.map → models-NJROM7JD.js.map} +0 -0
  132. /package/dist/{parse-worker-v2-WS35WZY5.js.map → parse-worker-v2-ALTGW2J3.js.map} +0 -0
  133. /package/dist/{pdf-ocr-MVNHXGPS.js.map → pdf-ocr-3VVSR4LN.js.map} +0 -0
  134. /package/dist/{pdf-ocr-QQPZLIHM.js.map → pdf-ocr-HUSU2XMV.js.map} +0 -0
  135. /package/dist/{profile-io-GSRLMU4D.js.map → profile-io-SQI3X7MD.js.map} +0 -0
  136. /package/dist/{rasterize-3H5QKDH2.js.map → rasterize-BHW5UZY2.js.map} +0 -0
  137. /package/dist/{redact-doc-TLBF7J7W.js.map → redact-doc-KJ6M63BP.js.map} +0 -0
  138. /package/dist/{render-FTCOQ6WO.js.map → render-Z6N2GGKL.js.map} +0 -0
  139. /package/dist/{seal-74R7KDRY.js.map → seal-PM4624BB.js.map} +0 -0
  140. /package/dist/{setup-WVZ47AKV.js.map → setup-MUBZ674O.js.map} +0 -0
  141. /package/dist/{visual-TEW6Y3NH.js.map → visual-AV46N5ZN.js.map} +0 -0
  142. /package/dist/{watch-CA32Z7XB.js.map → watch-D4AUDKVN.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/ocr/ruling-lines.ts","../src/ocr/engine.ts","../src/ocr/glyph-restore.ts","../src/ocr/postprocess.ts","../src/ocr/crop.ts","../src/ocr/deskew.ts","../src/ocr/pdf-ocr.ts"],"sourcesContent":["/**\n * 래스터 괘선 감지 — 스캔/이미지 페이지의 픽셀에서 표 괘선(수평/수직 선)을\n * 찾아 선 기반 표 파이프라인(line-detector → table-grid)에 공급한다.\n *\n * PDF 그래픽 ops 가 없는 OCR 경로는 종전에 클러스터 감지기만 탔는데,\n * 병합 라벨 셀 + 다중줄 서술형 서식(정부 제출 서식류)은 라인 클러스터로\n * 행 경계를 잡을 수 없다. 괘선은 이미지에 실존하므로 이진화 + 런렝스로\n * 직접 감지한다 — ML 없음, 축 정렬 전제(스캔 스큐 보정은 스코프 밖).\n *\n * 오탐 방어 3겹 (실측: 우수사례 제출 서식 스캔 — 진짜 괘선은 길고 얇음):\n * - 최소 길이 20pt: 글리프 획(가로 ≤한 글자폭·세로 ≤글자높이)은 걸러짐\n * - 두께 상한 2.5pt: 굵은 제목 획(6~13px)·텍스트 줄 밴드·채운 색상바 탈락\n * - 양측 잉크 포위 제외: 색상바(붙임 박스 등) 안 흰 글자 틈새 슬리버 탈락\n *\n * 텍스트층 경로가 선으로 받는 것 중 잉크 런렝스로 안 보이는 두 가지도 선으로 낸다 — 점선 괘선(DOT_*),\n * 채움 사각형의 변(FILL_*). 둘 다 표 괘선에 맞물린 것만.\n */\n\nimport type { LineSegment } from \"../pdf/line-types.js\"\n\n/**\n * 잉크 판정 휘도 상한 — 셀 배경 음영(회색 ~220)은 통과, 실선(진회~검정·남색)만 잉크. 0.12mm 가는 괘선이 픽셀 경계에\n * 걸려 두 픽셀로 번지면 픽셀마다 192~207 이라 190 에선 놓쳤다(ice-geomjeong 시험일정표 \"구분│일자\" 세로선, ktdb 목록표)\n */\nconst INK_LUMA_MAX = 205\n/** 최소 선 길이 (pt) — 최소 셀 변(≈7mm)과 대형 글리프 획(≤1em) 사이 */\nconst MIN_LINE_LENGTH_PT = 20\n/** 선 두께 상한 (pt) — 진짜 괘선(0.12~0.7mm ≈ 0.3~2pt)과 굵은 글리프 획(2pt+) 분리 */\nconst MAX_LINE_WIDTH_PT = 2.5\n/** 런 내 허용 끊김 (px) — 스캔 노이즈/JPEG 아티팩트 */\nconst GAP_TOL_PX = 2\n/** 밴드 병합 시 요구하는 x(또는 y) 범위 겹침 비율 (짧은 쪽 기준) */\nconst BAND_OVERLAP_RATIO = 0.5\n/** 밴드 양측(±2px)이 이 비율 이상 잉크면 채운 영역 내부 슬리버로 판정 */\nconst SURROUND_INK_RATIO = 0.35\n/**\n * 점선·파선 괘선. 텍스트층 경로(line-extract.ts)는 대시 패턴을 보지 않고 경로 기하만 읽어 점선 괘선도\n * 실선으로 받는데, 래스터에선 점 사이 간격(216dpi 에서 2~4px)이 GAP_TOL_PX 를 넘어 선이 조각난다 —\n * 점선으로 행을 가른 표가 OCR 경로에서만 한 행으로 뭉쳤다(changwon-plan2026 p5 정원표 22행 → 2행,\n * ice-geomjeong-plan p3 세로 점선 표 → 열 없는 한 칸). 점 사이 간격 상한 1.3pt 안에서 짧은 점(대시 4pt 이하)이\n * 8개+ 이어지면 점선 후보. 목차 리더 점(\"······\")도 같은 모양이라 주기로 가른다 — 코퍼스 실측 괘선 점선 주기\n * 0.8~2.0pt(점 ≈ 간격), 리더 2.5~3.3pt(간격 ≈ 점 × 2). 대시가 간격보다 확연히 긴(1.5배+) 파선은 주기 무관.\n * 마지막으로 수직 방향 괘선(실선·점선)과 닿는 것만 남긴다 — 리더는 글자 사이에 떠 있고 괘선은 표·상자 테두리에 물린다.\n */\nconst DOT_MAX_GAP_PT = 1.3\nconst DOT_MAX_DASH_PT = 4\nconst DOT_MAX_PERIOD_PT = 2.2\nconst DOT_MIN_COUNT = 8\n/** 점선 후보가 수직 괘선에 닿았다고 보는 거리 (pt) */\nconst DOT_TOUCH_PT = 1.5\n/**\n * 채움 경계. 텍스트층 경로는 순수 채움 경로(머리 행 음영·색 칸의 사각형)의 변도 선으로 받는다(line-extract.ts\n * flushPath → classifyAndAdd). 잉크 감지(휘도 ≤ 190)는 연한 채움과 흰 바탕의 경계를 못 봐, 머리 행에만 좌우 변이\n * 있는 개방 변 표 두 개를 가상 테두리가 한 격자로 용접했고(ice-election-cases p5 8×3·10×3 → 19×5, seoul-archives p3,\n * ice-geomjeong p3), 흰 틈으로 칸을 가른 색 머리 행이 한 칸으로 뭉쳤다(cbe-record-guide). 쪽 바탕(휘도 최빈값)보다\n * FILL_DELTA 이상 어두운 곳이 안쪽으로 FILL_MIN_DEPTH_PT 이상 이어지는 바깥 경계 픽셀 런을 선으로 낸다 — 괘선(≤ 2.5pt)과\n * 글자 획은 그보다 얇다. 바탕을 흰색으로 못박으면 크림색 바탕 쪽 전체가 채움이 되어 본문이 통째로 표가 됐다\n * (pen-cyberbridge p2 바탕 휘도 242). 경계 안쪽 휘도가 고르지 않은 것(사진·그림·배경 도안)은 채움이 아니다.\n */\nconst FILL_DELTA = 10\n/** 채움 경계 최소 길이 (pt) — 색 머리 행 높이(15~18pt)의 칸 틈도 잡게 괘선(20pt)보다 짧게. 글자 획은 두께 조건이 막는다 */\nconst FILL_MIN_LENGTH_PT = 10\nconst FILL_MIN_DEPTH_PT = 4\nconst FILL_FLAT_TOL = 12\nconst FILL_FLAT_RATIO = 0.8\n/**\n * 채움 띠 안 밝은 칸 구분선 — 색 머리 행을 0.25pt 흰 선으로 가른 표는 216dpi 에서 선이 1px 도 안 돼 띠보다 조금 밝은 줄(띠 179 →\n * 223)로만 남아 쪽 바탕 기준 채움 판정에 묻혔다(cbe-record-guide \"페이지 | 주요 내용\" 이 한 칸). 양옆 FILL_DIV_SIDE 블록이 채움이고\n * 그 둘보다 FILL_DIV_DELTA 이상 밝은 블록 세로 런을 구분선 후보로 낸다 — 쓰임은 본문 세로 괘선과 같은 x 일 때만(detectRulingLines)\n */\nconst FILL_DIV_DELTA = 20\n/** 조각 난 채움 변을 이은 범위가 괘선 끝에서 떨어져도 되는 거리 (pt) — 칸 모서리 둥글림·첫 칸 글자 */\nconst FILL_CHAIN_END_PT = 8\nconst FILL_DIV_SIDE = 2\n\n/** 픽셀 공간(top-left origin) 선분 — 두께는 밴드 픽셀 수 */\nexport interface PxSegment {\n x1: number; y1: number\n x2: number; y2: number\n thicknessPx: number\n}\n\nexport interface RulingLines {\n /** Newly recovered faint column rules, used to split OCR boxes before recognition. */\n cellDividers?: PxSegment[]\n horizontals: PxSegment[]\n verticals: PxSegment[]\n}\n\n/**\n * RGBA 래스터에서 수평/수직 괘선 감지.\n * @param scale 렌더 스케일 (px/pt) — 길이·두께 임계를 pt 기준으로 환산\n */\nexport function detectRulingLines(\n rgba: Uint8Array,\n width: number,\n height: number,\n scale: number,\n): RulingLines {\n const minLenPx = Math.round(MIN_LINE_LENGTH_PT * scale)\n const maxThickPx = Math.max(1, Math.floor(MAX_LINE_WIDTH_PT * scale))\n\n // 잉크 마스크 1회 구축 — 수평/수직 두 패스가 공유\n const ink = new Uint8Array(width * height)\n // 휘도는 채움 경계(fillEdges)도 쓴다 — 한 번만 계산\n const luma = new Uint8Array(width * height)\n for (let p = 0, i = 0; p < ink.length; p++, i += 4) {\n // ITU-R BT.601 휘도 근사 (정수 시프트)\n const l = (rgba[i] * 77 + rgba[i + 1] * 150 + rgba[i + 2] * 29) >> 8\n luma[p] = l\n if (l <= INK_LUMA_MAX && rgba[i + 3] >= 128) ink[p] = 1\n }\n\n const solid: RunReader = (m, base, step, span) => solidRuns(m, base, step, span, minLenPx)\n const dot = {\n maxGap: Math.max(2, Math.round(DOT_MAX_GAP_PT * scale)),\n maxDash: Math.max(2, Math.round(DOT_MAX_DASH_PT * scale)),\n maxPeriod: DOT_MAX_PERIOD_PT * scale,\n }\n const dotted: RunReader = (m, base, step, span) => dottedRuns(m, base, step, span, minLenPx, dot.maxGap, dot.maxDash, dot.maxPeriod)\n const horizontals = detectBands(ink, width, height, maxThickPx, false, solid)\n const verticals = detectBands(ink, width, height, maxThickPx, true, solid)\n const dotH = detectBands(ink, width, height, maxThickPx, false, dotted)\n const dotV = detectBands(ink, width, height, maxThickPx, true, dotted)\n const tol = Math.max(2, Math.round(DOT_TOUCH_PT * scale))\n const allH = [...horizontals, ...dotH], allV = [...verticals, ...dotV]\n // 가로 a 가 세로 선(p.x1 = p.x2)과 만나거나 끝이 닿는지 (세로 a 는 축을 바꿔 같은 판정)\n const touchesV = (a: PxSegment) => allV.some(p => p.x1 >= a.x1 - tol && p.x1 <= a.x2 + tol && a.y1 >= p.y1 - tol && a.y1 <= p.y2 + tol)\n const touchesH = (a: PxSegment) => allH.some(p => p.y1 >= a.y1 - tol && p.y1 <= a.y2 + tol && a.x1 >= p.x1 - tol && a.x1 <= p.x2 + tol)\n horizontals.push(...dotH.filter(touchesV))\n verticals.push(...dotV.filter(touchesH))\n // 채움 경계는 표 괘선에 맞물린 것만 쓴다 — 머리 행 음영의 윗변·아랫변은 표 가로 괘선(세로 괘선과 만나는 것)과 폭(양 끝)이\n // 같고, 좌우 변은 그 가로선 끝점에, 칸 사이 흰 틈은 바로 위아래 본문 세로 괘선과 같은 x 에 선다. 칸 안에 놓인 단색\n // 로고·그림의 변은 어느 쪽도 아니다(kmcc-press 참여 기업 표: 로고 칸이 행·열을 쪼갰다 — 로고 밑줄은 잉크 선으로 잡히지만\n // 세로 괘선과 만나지 않는다)\n const inkV = [...verticals]\n const ruleH = horizontals.filter(p => inkV.some(v => v.x1 >= p.x1 - tol && v.x1 <= p.x2 + tol && p.y1 >= v.y1 - tol && p.y1 <= v.y2 + tol))\n const fill = fillEdges(luma, width, height, scale)\n const fillH = fill.horizontals.filter(a => ruleH.some(p => Math.abs(p.x1 - a.x1) <= tol && Math.abs(p.x2 - a.x2) <= tol))\n // 흰 틈으로 칸마다 가른 색 머리 띠는 윗변이 칸 조각으로 끊긴다(띠 위에 걸친 그림·흰 글자도 끊는다) — 같은 y 조각을 이은 범위가\n // 표 가로 괘선 폭 안에서 양 끝 FILL_CHAIN_END 안에 닿고 그 폭의 절반 이상을 덮으면 그 괘선 폭의 변 하나로 받는다\n // (pen-cyberbridge p3 주황 머리 \"구분 | 일정 | 내용 | 비고\" 윗변 4조각 — 받지 않아 머리 행이 표 밖 문단이 됐다)\n const rows = new Map<number, PxSegment[]>()\n for (const a of fill.horizontals) if (!fillH.includes(a)) (rows.get(Math.round(a.y1)) ?? rows.set(Math.round(a.y1), []).get(Math.round(a.y1))!).push(a)\n const chainEnd = Math.round(FILL_CHAIN_END_PT * scale)\n for (const [y, segs] of rows) {\n for (const p of ruleH) {\n const inside = segs.filter(a => a.x1 >= p.x1 - tol && a.x2 <= p.x2 + tol)\n if (inside.length < 2 || fillH.some(f => Math.abs(f.y1 - y) <= tol && f.x1 <= p.x1 + tol && f.x2 >= p.x2 - tol)) continue\n const lo = Math.min(...inside.map(a => a.x1)), hi = Math.max(...inside.map(a => a.x2))\n const cover = inside.reduce((n, a) => n + a.x2 - a.x1, 0)\n 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 })\n }\n }\n horizontals.push(...fillH)\n const edgeH = [...ruleH, ...fillH]\n const depth = Math.round(FILL_MIN_DEPTH_PT * scale)\n for (const a of fill.verticals) {\n // 머리 띠와 본문 사이 굵은 괘선(1pt)만큼은 떨어져도 같은 열 경계다 (pen-cyberbridge p3 띠 아래끝 → 세로 괘선 위끝 7px).\n // 그런 띠 안 구분선은 띠 높이를 다 긋는다 — 띠 가장자리 블록에서 짧아진 위끝을 띠 윗변에 잇는다(안 이으면 머리 행이 한 칸).\n // 아래끝을 세로 괘선 위끝까지 늘이지는 않는다 — 3° 기울인 cbe 2쪽에서 위아래 두 표가 한 격자로 용접됐다\n const col = inkV.find(p => Math.abs(p.x1 - a.x1) <= tol && p.y1 <= a.y2 + tol * 2 && p.y2 >= a.y1 - tol * 2)\n if (col) {\n const top = edgeH.filter(h => h.x1 <= a.x1 + tol && h.x2 >= a.x1 - tol && h.y1 < a.y1 && h.y1 >= a.y1 - depth)\n verticals.push({ ...a, y1: top.length ? Math.min(...top.map(h => h.y1)) : a.y1 })\n } 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)\n }\n // Faint internal rules are evidence only when enclosed by an existing dark\n // frame and repeated across both axes. Do not brighten the whole page's grid.\n const cellDividers: PxSegment[] = []\n const frames: Array<{ x1: number; x2: number; y1: number; y2: number }> = []\n for (let i = 0; i < horizontals.length && frames.length < 32; i++) {\n const a = horizontals[i]\n for (let j = i + 1; j < horizontals.length && frames.length < 32; j++) {\n const b = horizontals[j]\n if (Math.abs(a.x1 - b.x1) > tol || Math.abs(a.x2 - b.x2) > tol) continue\n const y1 = Math.min(a.y1, b.y1), y2 = Math.max(a.y1, b.y1)\n if (a.x2 - a.x1 < minLenPx * 3 || y2 - y1 < minLenPx * 2) continue\n if (![a.x1, a.x2].every(x => verticals.some(v => Math.abs(v.x1 - x) <= tol && v.y1 <= y1 + tol && v.y2 >= y2 - tol))) continue\n frames.push({ x1: a.x1, x2: a.x2, y1, y2 })\n }\n }\n if (frames.length) {\n const faint = new Uint8Array(ink.length)\n for (let p = 0; p < faint.length; p++) if (luma[p] <= 240 && rgba[p * 4 + 3] >= 128) faint[p] = 1\n const hs = detectBands(faint, width, height, maxThickPx, false, solid)\n const vs = detectBands(faint, width, height, maxThickPx, true, solid)\n for (const f of frames) {\n const innerH = hs.filter(h => h.y1 > f.y1 + tol && h.y1 < f.y2 - tol && Math.abs(h.x1 - f.x1) <= tol && Math.abs(h.x2 - f.x2) <= tol)\n const innerV = vs.filter(v => v.x1 > f.x1 + tol && v.x1 < f.x2 - tol && Math.abs(v.y1 - f.y1) <= tol && Math.abs(v.y2 - f.y2) <= tol)\n if (innerH.length < 3 || innerV.length < 1) continue\n for (const h of innerH) if (!horizontals.some(p => Math.abs(p.y1 - h.y1) <= tol && p.x1 <= h.x1 + tol && p.x2 >= h.x2 - tol)) horizontals.push(h)\n for (const v of innerV) if (!verticals.some(p => Math.abs(p.x1 - v.x1) <= tol && p.y1 <= v.y1 + tol && p.y2 >= v.y2 - tol)) { verticals.push(v); cellDividers.push(v) }\n }\n }\n return { horizontals, verticals, ...(cellDividers.length ? { cellDividers } : {}) }\n}\n\n/**\n * 채움 영역의 바깥 경계 선 (FILL_* 주석). 채움 경계는 칸 크기 단위라 해상도가 필요 없어 k×k(≈1pt) 블록으로 줄여 본다 —\n * 블록 휘도는 가장 밝은 픽셀(칸 사이 1~3px 흰 틈이 채움에 묻히지 않게). 방향마다 안쪽 연속 채움 길이를 한 번 누적해\n * 경계 마스크를 만들고, 찾은 선은 원래 픽셀 좌표로 되돌린다\n */\nfunction fillEdges(pageLuma: Uint8Array, width: number, height: number, scale: number): RulingLines {\n const k = Math.max(1, Math.round(scale))\n const w = Math.floor(width / k), h = Math.floor(height / k), n = w * h\n const luma = new Uint8Array(n)\n const hist = new Uint32Array(256)\n for (let y = 0; y < h; y++) {\n for (let x = 0; x < w; x++) {\n let mx = 0\n for (let by = 0; by < k; by++) {\n let i = (y * k + by) * width + x * k\n for (let bx = 0; bx < k; bx++, i++) if (pageLuma[i] > mx) mx = pageLuma[i]\n }\n luma[y * w + x] = mx\n hist[mx]++\n }\n }\n let bg = 255\n for (let v = 0; v < 256; v++) if (hist[v] > hist[bg]) bg = v\n const tint = new Uint8Array(n)\n for (let p = 0; p < n; p++) if (luma[p] < bg - FILL_DELTA) tint[p] = 1\n const depth = Math.max(2, Math.round(FILL_MIN_DEPTH_PT * scale / k))\n const minLen = Math.round(FILL_MIN_LENGTH_PT * scale / k)\n const solid: RunReader = (m, base, step, span) => solidRuns(m, base, step, span, minLen)\n const run = new Uint16Array(n)\n const mask = new Uint8Array(n)\n const out: RulingLines = { horizontals: [], verticals: [] }\n // dx,dy = 채움 안쪽 방향. 위 변(0,1)·아래 변(0,-1)·왼 변(1,0)·오른 변(-1,0)\n for (const [dx, dy] of [[0, 1], [0, -1], [1, 0], [-1, 0]] as const) {\n // run[p] = p 에서 안쪽 방향으로 이어지는 채움 길이 (안쪽 끝부터 거꾸로 누적)\n const step = dy * w + dx\n const ys = dy > 0 ? [h - 1, -1, -1] : [0, h, 1]\n const xs = dx > 0 ? [w - 1, -1, -1] : [0, w, 1]\n for (let y = ys[0]; y !== ys[1]; y += ys[2]) {\n for (let x = xs[0]; x !== xs[1]; x += xs[2]) {\n const p = y * w + x\n const nx = x + dx, ny = y + dy\n run[p] = tint[p] ? (nx >= 0 && nx < w && ny >= 0 && ny < h ? Math.min(65535, run[p + step] + 1) : 1) : 0\n }\n }\n mask.fill(0)\n for (let y = 0; y < h; y++) {\n for (let x = 0; x < w; x++) {\n const p = y * w + x\n if (run[p] < depth) continue\n const ox = x - dx, oy = y - dy // 바깥 이웃\n if (ox < 0 || oy < 0 || ox >= w || oy >= h || !tint[p - step]) mask[p] = 1\n }\n }\n const vertical = dx !== 0\n const flat = (s: PxSegment) => {\n // 경계 안쪽 depth/2 지점의 휘도가 고른지 — 사진·도안 탈락\n const d = depth >> 1\n const vals: number[] = []\n if (vertical) {\n const x = Math.round(s.x1) + dx * d\n for (let y = Math.round(s.y1); y <= s.y2; y++) if (x >= 0 && x < w) vals.push(luma[y * w + x])\n } else {\n const y = Math.round(s.y1) + dy * d\n for (let x = Math.round(s.x1); x <= s.x2; x++) if (y >= 0 && y < h) vals.push(luma[y * w + x])\n }\n if (!vals.length) return false\n const med = [...vals].sort((a, b) => a - b)[vals.length >> 1]\n return vals.filter(v => Math.abs(v - med) <= FILL_FLAT_TOL).length >= vals.length * FILL_FLAT_RATIO\n }\n for (const s of detectBands(mask, w, h, 1, vertical, solid)) {\n if (!flat(s)) continue\n const c = (v: number) => v * k + (k - 1) / 2\n ;(vertical ? out.verticals : out.horizontals).push({ x1: c(s.x1), y1: c(s.y1), x2: c(s.x2), y2: c(s.y2), thicknessPx: 1 })\n }\n }\n const div = new Uint8Array(n)\n for (let y = 0; y < h; y++) {\n for (let x = FILL_DIV_SIDE; x < w - FILL_DIV_SIDE; x++) {\n const p = y * w + x, l = p - FILL_DIV_SIDE, r = p + FILL_DIV_SIDE\n if (tint[l] && tint[r] && luma[p] >= Math.max(luma[l], luma[r]) + FILL_DIV_DELTA) div[p] = 1\n }\n }\n const c = (v: number) => v * k + (k - 1) / 2\n for (const s of detectBands(div, w, h, 1, true, solid)) out.verticals.push({ x1: c(s.x1), y1: c(s.y1), x2: c(s.x2), y2: c(s.y2), thicknessPx: 1 })\n return out\n}\n\n/**\n * 스캔라인 하나의 런 판독 — 마스크의 base + pos·step 픽셀(pos = 0..span-1). 가로 스캔은 step 1, 세로 스캔은 step = 쪽 폭.\n * 픽셀마다 접근 함수를 부르던 종전 모양은 괘선 감지 시간의 40% 가 그 호출이었다(쪽당 4패스 × 450만 픽셀)\n */\ntype RunReader = (mask: Uint8Array, base: number, step: number, span: number) => Array<{ lo: number; hi: number }>\n\n/** 스캔라인 하나의 실선 런 (GAP_TOL_PX 이하 끊김 허용, 최소 길이 이상) */\nfunction solidRuns(mask: Uint8Array, base: number, step: number, span: number, minLenPx: number): Array<{ lo: number; hi: number }> {\n const runs: Array<{ lo: number; hi: number }> = []\n let runStart = -1\n let gap = 0\n for (let pos = 0; pos <= span; pos++) {\n const on = pos < span && mask[base + pos * step] === 1\n if (on) {\n if (runStart < 0) runStart = pos\n gap = 0\n } else if (runStart >= 0) {\n if (++gap > GAP_TOL_PX || pos >= span) {\n const hi = pos - gap\n if (hi - runStart + 1 >= minLenPx) runs.push({ lo: runStart, hi })\n runStart = -1\n gap = 0\n }\n }\n }\n return runs\n}\n\n/** 스캔라인 하나의 점선 런 — 짧은 잉크 조각(≤ maxDash)이 간격 ≤ maxGap 으로 DOT_MIN_COUNT 개+ 이어진 사슬 (DOT_* 주석) */\nfunction dottedRuns(\n mask: Uint8Array,\n base: number,\n step: number,\n span: number,\n minLenPx: number,\n maxGap: number,\n maxDash: number,\n maxPeriod: number,\n): Array<{ lo: number; hi: number }> {\n const runs: Array<{ lo: number; hi: number }> = []\n // 사슬 조각의 [lo, hi] — 사슬이 끊길 때만 통계를 본다 (대부분 스캔라인은 사슬이 없다)\n const chain: number[] = []\n const median = (xs: number[]) => xs.sort((a, b) => a - b)[xs.length >> 1]\n const flush = () => {\n const cnt = chain.length >> 1\n if (cnt >= DOT_MIN_COUNT && chain[chain.length - 1] - chain[0] + 1 >= minLenPx) {\n const periods: number[] = [], dashes: number[] = [], gaps: number[] = []\n for (let k = 0; k < cnt; k++) {\n dashes.push(chain[2 * k + 1] - chain[2 * k] + 1)\n if (k) { periods.push(chain[2 * k] - chain[2 * k - 2]); gaps.push(chain[2 * k] - chain[2 * k - 1] - 1) }\n }\n if (median(periods) <= maxPeriod || median(dashes) >= 1.5 * median(gaps)) runs.push({ lo: chain[0], hi: chain[chain.length - 1] })\n }\n chain.length = 0\n }\n let s = -1\n for (let pos = 0; pos <= span; pos++) {\n const on = pos < span && mask[base + pos * step] === 1\n if (on) { if (s < 0) s = pos; continue }\n if (s < 0) continue\n const lo = s, hi = pos - 1\n s = -1\n if (hi - lo + 1 > maxDash) { flush(); continue }\n if (chain.length && lo - chain[chain.length - 1] - 1 > maxGap) flush()\n chain.push(lo, hi)\n }\n flush()\n return runs\n}\n\n/** 열린 밴드 — 인접 스캔라인의 런을 누적 */\ninterface Band {\n lo: number // 런 진행축 시작 (h: x1, v: y1)\n hi: number // 런 진행축 끝\n first: number // 직교축 시작 스캔라인 (h: yTop, v: xLeft)\n last: number // 직교축 마지막 스캔라인\n}\n\n/**\n * 스캔라인 런렝스 → 인접 라인 밴드 병합 → 두께 필터 → 선분 방출.\n * transpose=false 면 수평선(행 스캔), true 면 수직선(열 스캔).\n */\nfunction detectBands(\n ink: Uint8Array,\n width: number,\n height: number,\n maxThickPx: number,\n transpose: boolean,\n readRuns: RunReader,\n): PxSegment[] {\n const lines = transpose ? width : height // 스캔라인 개수 (직교축)\n const span = transpose ? height : width // 런 진행축 길이\n const step = transpose ? width : 1\n const baseOf = (line: number) => (transpose ? line : line * width)\n\n const out: PxSegment[] = []\n let open: Band[] = []\n\n /** 밴드 양측(±2px 스캔라인)의 잉크 비율 — 채운 색상바 안 흰 글자 틈새 슬리버 판별 */\n const surroundInkRatio = (b: Band, side: number): number => {\n if (side < 0 || side >= lines) return 0\n let dark = 0, total = 0\n const base = baseOf(side)\n for (let pos = b.lo; pos <= b.hi; pos += 3) {\n total++\n if (ink[base + pos * step] === 1) dark++\n }\n return total > 0 ? dark / total : 0\n }\n\n const emit = (b: Band) => {\n const thick = b.last - b.first + 1\n if (thick > maxThickPx) return // 색상바·텍스트 밴드\n if (\n surroundInkRatio(b, b.first - 2) >= SURROUND_INK_RATIO &&\n surroundInkRatio(b, b.last + 2) >= SURROUND_INK_RATIO\n ) return // 채운 영역 내부 슬리버\n const center = (b.first + b.last) / 2\n out.push(\n transpose\n ? { x1: center, y1: b.lo, x2: center, y2: b.hi, thicknessPx: thick }\n : { x1: b.lo, y1: center, x2: b.hi, y2: center, thicknessPx: thick },\n )\n }\n\n for (let line = 0; line < lines; line++) {\n // 1) 이 스캔라인의 런 (실선 또는 점선 사슬)\n const runs = readRuns(ink, baseOf(line), step, span)\n\n // 2) 열린 밴드와 병합 — 직전 스캔라인까지 이어졌고 범위가 겹치면 확장\n const next: Band[] = []\n const used = new Set<number>()\n for (const band of open) {\n if (band.last !== line - 1) { emit(band); continue } // 연속 끊김 → 확정\n let merged = false\n for (let r = 0; r < runs.length; r++) {\n if (used.has(r)) continue\n const run = runs[r]\n const overlap = Math.min(band.hi, run.hi) - Math.max(band.lo, run.lo) + 1\n const shorter = Math.min(band.hi - band.lo, run.hi - run.lo) + 1\n if (overlap >= shorter * BAND_OVERLAP_RATIO) {\n band.lo = Math.min(band.lo, run.lo)\n band.hi = Math.max(band.hi, run.hi)\n band.last = line\n next.push(band)\n used.add(r)\n merged = true\n break\n }\n }\n if (!merged) emit(band)\n }\n for (let r = 0; r < runs.length; r++) {\n if (!used.has(r)) next.push({ lo: runs[r].lo, hi: runs[r].hi, first: line, last: line })\n }\n open = next\n }\n for (const band of open) emit(band)\n return out\n}\n\n/**\n * 픽셀 선분 → PDF pt LineSegment (bottom-up, classifyAndAdd 규약:\n * 수평선 y1==y2·x1<x2, 수직선 x1==x2·y1<y2).\n */\nexport function rulingToPdfLines(\n ruling: RulingLines,\n scale: number,\n pdfHeight: number,\n): { horizontals: LineSegment[]; verticals: LineSegment[] } {\n const horizontals: LineSegment[] = ruling.horizontals.map(s => {\n const y = pdfHeight - (s.y1 + s.y2) / 2 / scale\n return {\n x1: Math.min(s.x1, s.x2) / scale,\n y1: y,\n x2: Math.max(s.x1, s.x2) / scale,\n y2: y,\n lineWidth: s.thicknessPx / scale,\n }\n })\n const verticals: LineSegment[] = ruling.verticals.map(s => {\n const x = (s.x1 + s.x2) / 2 / scale\n const yA = pdfHeight - Math.max(s.y1, s.y2) / scale\n const yB = pdfHeight - Math.min(s.y1, s.y2) / scale\n return { x1: x, y1: yA, x2: x, y2: yB, lineWidth: s.thicknessPx / scale }\n })\n return { horizontals, verticals }\n}\n","/**\n * 내장 텍스트 OCR 엔진 — PP-OCRv5 korean (det DBNet + rec SVTR/CTC) ONNX 추론.\n *\n * 파이프라인: 페이지 RGBA → det(선 검출) → 박스 픽셀 분석(대비·행 밴드) → 라인 crop\n * → rec 배치(CTC 인식) → 표기 후처리 → OcrItem[]\n * (좌표는 입력 픽셀 기준 top-left origin — 호출자가 PDF 좌표계로 변환).\n *\n * 전·후처리는 공식 inference.yml 스펙 그대로:\n * - det: BGR, 긴 변 960 리사이즈(32 배수), mean/std [0.485,0.456,0.406]/[0.229,0.224,0.225],\n * DBPostProcess thresh 0.3 / box_thresh 0.6 / unclip_ratio 1.5\n * - rec: BGR, 높이 48 고정 비율 리사이즈 + 우측 zero-pad(최소 폭 320), (x/255-0.5)/0.5,\n * CTC 디코드 (blank=0, 1..N=사전, N+1=공백), text_score 0.5.\n * 배치(recBatch>1)는 공식처럼 폭 비율로 정렬해 묶고 배치 최대 폭까지 zero-pad — 기본은 1\n * DB 후처리의 contour+minAreaRect 는 축정렬 connected-component bbox 로 근사\n * (공문서 스캔은 수평 텍스트가 지배적).\n *\n * 공식 파이프라인 밖의 보강 (근거는 bench/ocr-accuracy.mjs 코퍼스 실측, 각 모듈 주석):\n * - 키 큰 박스(세로로 쌓인 글자)는 행 밴드로 갈라 밴드마다 인식 (line-split.ts)\n * - 잉크 대비가 낮은 박스(배경 도안)는 인식하지 않음 — 환각 방지\n * - 사전 밖 공문서 기호(○ △)·둥근 따옴표·천 단위 숫자 공백 복원, 점류 조각 폐기 (postprocess.ts)\n * - 결과 좌표는 det 박스(unclip 여백) 대신 박스 안 잉크 외곽 — 텍스트층 아이템과 같은 기하\n *\n * 의존성(onnxruntime-node, sharp)은 optional — 미설치 시 create()가 명확한 에러.\n * 모델 미다운로드 시에도 즉시 실패 — 호출자가 ensureOcrModels() 먼저.\n */\n\nimport type { InferenceSession } from \"onnxruntime-node\"\nimport { createHash } from \"crypto\"\nimport { readFile, stat } from \"fs/promises\"\nimport { basename } from \"path\"\nimport { KordocError, OPTIONAL_DEP_INSTALL_HINT } from \"../utils.js\"\nimport {\n DEFAULT_OCR_MODELS,\n type OcrModelSelection,\n type OcrModelSource,\n ocrSelectionKey,\n ocrSourcePath,\n parseCharacterDict,\n} from \"./models.js\"\nimport { edgeTrim, leadingBullet, tallInkCount, grayCrop, inkBounds, inkStats, leaderRuns, leadingTriangle, splitRowBands } from \"./line-split.js\"\nimport { restoreGlyphs, trimInklessEnds } from \"./glyph-restore.js\"\nimport { isDotFragment, joinLeaderItems, restoreBulletItems, restoreSymbols } from \"./postprocess.js\"\nimport { bandBoxes, splitBoxAtCellRules, lineCrop, type Box, REC_HEIGHT } from \"./crop.js\"\nimport { disableOrtTelemetry } from \"./runtime-env.js\"\nimport { findOcrTextGaps, rememberOcrTextGaps } from \"./text-gaps.js\"\n\n/** OCR 인식 결과 한 줄 — 좌표는 입력 이미지 픽셀 (top-left origin, y down) */\nexport interface OcrItem {\n text: string\n x: number\n y: number\n w: number\n h: number\n /** CTC 평균 신뢰도 0~1 */\n confidence: number\n}\n\nexport interface OcrPageStats {\n droppedLowConf: number\n truncatedBoxes?: number\n /** 주면 저신뢰로 버린 줄을 여기에 모은다 (ocrLines \"all\") — 없으면 세기만 */\n dropped?: OcrItem[]\n}\n\n/**\n * 엔진 튜닝 — 기본값이 제품 동작. 벤치·실험만 덮어쓴다 (공개 API 아님).\n */\nexport interface OcrTuning {\n /** det 입력 긴 변 (공식 resize_long). 1920 보존 실험은 recall Δ median +0.04pp에\n * 속도 -25%로 기각 (2026-08-02) */\n detLongSide: number\n detThresh: number\n detBoxThresh: number\n detUnclip: number\n /** 인식 신뢰도 하한 (공식 drop_score) */\n textScore: number\n /** rec 배치 크기. 공식 rec_batch_num 은 6 이지만 onnxruntime CPU 에선 배치(최대 폭까지\n * zero-pad)가 오히려 느리다 — 코퍼스 16쪽 교대 실측 1: 0.57 · 4: 0.93 · 8: 0.87 s/page,\n * 정확도 차는 박스 내 CER 0.08pp (2026-09-23). 기본 1 */\n recBatch: number\n /** 키 큰 박스 행 밴드 분할 */\n splitTall: boolean\n /** 한 줄 박스에 걸린 이웃 줄 끝자락·상자 테두리를 지운 crop 도 인식 (line-split edgeTrim). 그림 영역만 읽을 때는 끈다 —\n * 쪽 배율·두 배 재인식 중 평균 신뢰도로 한쪽을 통째로 고르는데 지운 판이 한쪽 신뢰도만 올려 선택이 뒤집혔다(ODL 073) */\n trimEdges: boolean\n /** 이 대비(전경/배경 평균 휘도 차) 미만 박스는 글자가 아님 — 0 이면 끔 */\n minInkContrast: number\n /** 기호·따옴표 복원 + 점류 조각 폐기 */\n postprocess: boolean\n /** 결과 좌표를 det 박스(unclip 여백 포함) 대신 박스 안 잉크 외곽으로 */\n tightBoxes: boolean\n /** 목차 리더 점 무리를 빼고 앞뒤를 따로 인식 (line-split.ts leaderRuns) */\n splitLeaders: boolean\n}\n\nexport const DEFAULT_OCR_TUNING: Readonly<OcrTuning> = Object.freeze({\n detLongSide: 960,\n detThresh: 0.3,\n detBoxThresh: 0.6,\n detUnclip: 1.5,\n textScore: 0.5,\n recBatch: 1,\n splitTall: true,\n trimEdges: true,\n minInkContrast: 35,\n postprocess: true,\n tightBoxes: true,\n splitLeaders: true,\n})\n\nconst DET_MIN_SIZE = 3\nconst DET_MAX_BOXES = 3000\nconst REC_MIN_WIDTH = 320\n/** 배치 텐서 폭 합 상한 — 긴 줄 여러 개를 한 텐서로 묶어 메모리가 튀지 않게 */\nconst REC_BATCH_MAX_PIXELS = 48 * 16000\n/** 키 큰 박스 판정 (공식 파이프라인의 세로 판정 h/w ≥ 1.5 와 같은 문턱) */\nconst TALL_RATIO = 1.5\n/** 밴드로 갈라지지 않는 키 큰 박스 중 이 비율 이상은 90° 회전 글자 후보 */\nconst ROTATE_RATIO = 3\n/** 이웃 줄 끝자락을 지운 crop 이 원본 crop 을 이기려면 넘어야 할 신뢰도 차 (recognizePage 주석) */\nconst KEEP_MARGIN = 0.06\n/** 리더로 볼 최소 점 수 — 말줄임표 \"…\"(3점)보다 많이. 목차 쪽번호 박스가 무는 점은 코퍼스 실측 4~9개가 대부분 */\nconst LEADER_MIN_DOTS = 4\n/** 리더 앞 조각 crop 을 리더 안쪽으로 더 무는 폭 (박스 높이 배수, recognizePage 주석) */\nconst LEADER_CONTEXT = 0.5\n/** 박스 잉크 비율 하한 (engine recognizePage 주석) */\nconst MIN_INK_RATIO = 0.01\n/** 로컬 모델 파일 크기 상한 — PP-OCRv5 server det 가 88MB, 가장 큰 rec 도 수십 MB */\nconst MAX_LOCAL_MODEL_BYTES = 1024 * 1024 * 1024\n\n// det: BGR 채널 순서에 yml 기재 순서 그대로 적용 (mean[0]→B)\nconst DET_MEAN = [0.485, 0.456, 0.406]\nconst DET_STD = [0.229, 0.224, 0.225]\n\ntype SharpFactory = (\n input: Uint8Array | Buffer,\n options?: { raw?: { width: number; height: number; channels: number } },\n) => SharpChain\ninterface SharpChain {\n resize(w: number, h: number, opts?: { fit?: string }): SharpChain\n removeAlpha(): SharpChain\n raw(): { toBuffer(): Promise<Buffer> }\n}\n\n/** 인식 대상 한 줄 — rot 는 crop 회전(90=반시계, 270=시계). join: 리더로 가른 박스 번호, dotsBefore: 앞에 리더가 있었는지,\n * trimDots: crop 이 뒤 리더 점을 물었는지(결과 끝 점을 지운다) */\n/** keep: 인식 crop 에서 남길 박스 로컬 구간 — 밖(이웃 줄 끝자락·상자 테두리)은 배경으로 칠한다 (edgeTrim) */\ninterface LineJob { box: Box; rot: 0 | 90 | 270; group: number; join?: number; dotsBefore?: boolean; trimDots?: boolean; keep?: { x0: number; x1: number; y0: number; y1: number; bg: number } }\n\nexport class OcrEngine {\n private det: InferenceSession\n private rec: InferenceSession\n private dict: string[]\n private ort: typeof import(\"onnxruntime-node\")\n private sharp: SharpFactory\n\n private constructor(parts: {\n det: InferenceSession\n rec: InferenceSession\n dict: string[]\n ort: typeof import(\"onnxruntime-node\")\n sharp: SharpFactory\n }) {\n this.det = parts.det\n this.rec = parts.rec\n this.dict = parts.dict\n this.ort = parts.ort\n this.sharp = parts.sharp\n }\n\n static async create(models: OcrModelSelection = DEFAULT_OCR_MODELS): Promise<OcrEngine> {\n disableOrtTelemetry()\n const [ortMod, sharpModRaw] = await Promise.all([\n tryImport<typeof import(\"onnxruntime-node\")>(\"onnxruntime-node\", () => import(\"onnxruntime-node\")),\n tryImport<{ default?: SharpFactory } & SharpFactory>(\n \"sharp\",\n () => import(\"sharp\") as unknown as Promise<{ default?: SharpFactory } & SharpFactory>,\n ),\n ])\n const sharpAny = sharpModRaw as { default?: SharpFactory } | SharpFactory\n const sharpMod: SharpFactory =\n typeof sharpAny === \"function\" ? sharpAny : (sharpAny.default ?? (sharpAny as unknown as SharpFactory))\n\n const sessionOpts: import(\"onnxruntime-node\").InferenceSession.SessionOptions = {\n graphOptimizationLevel: \"all\",\n executionProviders: [\"cpu\"],\n logSeverityLevel: 3, // paddle2onnx 변환 잔여물 W 로그 폭주 억제\n }\n // 내장 모델은 캐시 경로에서 바로(ensureOcrModelsFor 가 검증), 로컬 파일은 한 번 읽은 바이트를 대조해 그 바이트로 연다 —\n // 대조 뒤 파일이 바뀌어도 검증하지 않은 바이트를 올리지 않는다. 로컬 파일을 모두 대조한 뒤에 세션을 만든다(하나가 틀리면 아무것도 안 올린다)\n const local = (s: OcrModelSource) => \"spec\" in s ? Promise.resolve(null) : verifiedBytes(s)\n const [detBytes, recBytes, dictBytes] = await Promise.all([local(models.det), local(models.rec), local(models.dict)])\n const session = (s: OcrModelSource, bytes: Uint8Array | null) =>\n bytes\n ? ortMod.InferenceSession.create(bytes, sessionOpts).catch(() => {\n throw new KordocError(`OCR 모델을 열지 못했습니다: ${basename(ocrSourcePath(s))} — ONNX 모델이 아니거나 손상됐습니다`)\n })\n : ortMod.InferenceSession.create(ocrSourcePath(s), sessionOpts)\n const [det, rec, dictYml] = await Promise.all([\n session(models.det, detBytes),\n session(models.rec, recBytes),\n dictBytes ? new TextDecoder().decode(dictBytes) : readFile(ocrSourcePath(models.dict), \"utf-8\"),\n ])\n const dict = parseCharacterDict(dictYml)\n if (dict.length === 0) {\n throw \"spec\" in models.dict\n ? new Error(\"OCR 사전 파싱 실패 — 모델 캐시를 삭제 후 재다운로드하세요\")\n : new KordocError(`OCR 사전 파싱 실패 — ${basename(models.dict.file.path)} 에 PostProcess.character_dict 목록이 없습니다`)\n }\n\n return new OcrEngine({ det, rec, dict, ort: ortMod, sharp: sharpMod })\n }\n\n /** onnxruntime-node 1.14+ InferenceSession.release() — 구버전은 무시 */\n async destroy(): Promise<void> {\n for (const s of [this.det, this.rec]) {\n const rel = (s as unknown as { release?: () => Promise<void> }).release\n if (typeof rel === \"function\") {\n try { await rel.call(s) } catch { /* ignore */ }\n }\n }\n }\n\n /**\n * 페이지 RGBA 픽셀 → 텍스트 라인 인식.\n * 반환 좌표는 입력 픽셀 기준. 라인은 위→아래, 좌→우 정렬.\n * @param stats 저신뢰(conf<0.5) 폐기 라인 카운트 출력 — 종전엔 무음 폐기라 관측 불가\n */\n async recognizePage(\n rgba: Uint8Array,\n width: number,\n height: number,\n stats?: OcrPageStats,\n tuning: Readonly<OcrTuning> = DEFAULT_OCR_TUNING,\n cellRules: Array<{ x1: number; y1: number; y2: number; thicknessPx: number }> = [],\n ): Promise<OcrItem[]> {\n if (width < DET_MIN_SIZE || height < DET_MIN_SIZE) return []\n const detected = await this.detect(rgba, width, height, tuning, stats)\n const boxes = cellRules.length ? detected.flatMap(b => splitBoxAtCellRules(b, cellRules)) : detected\n\n // 박스 픽셀 분석 → 인식 작업(라인) 목록. group = 한 결과로 합칠 후보 묶음(회전 후보)\n const jobs: LineJob[] = []\n /** 리더로 가른 검출 박스 — 조각 인식 결과를 모아 한 아이템으로 합친다 */\n const joins: Array<{ box: Box; parts: Array<{ x: number; text: string; dotsBefore: boolean; confidence: number }>; trailDots: boolean }> = []\n let group = 0\n for (const b of boxes) {\n const gray = grayCrop(rgba, width, b)\n const ink = inkStats(gray)\n if (tuning.minInkContrast > 0 && ink.contrast < tuning.minInkContrast) continue\n // 잉크가 박스의 1% 미만이면 가는 선·티끌이다 — 연한 배경 도안 띠를 가로지르는 파란 세로선이 대비 검사를\n // 통과시키고 인식기가 도안을 \"D D D … O\" 로 읽었다(ice-election-cases 표지, 잉크 0.4%). 가장 가는 글자(\"-\")도\n // 여백 포함 박스의 1.5% 안팎이라 남는다. 점 하나(\"·\")는 여기서 빠지지만 isDotFragment 가 어차피 버린다\n if (tuning.minInkContrast > 0 && ink.inkRatio < MIN_INK_RATIO) continue\n if (tuning.splitTall && b.h >= b.w * TALL_RATIO) {\n const bands = splitRowBands(gray, b.w, b.h, ink, 0.45)\n if (bands.length >= 2) {\n for (const sub of bandBoxes(b, bands, height)) jobs.push({ box: sub, rot: 0, group: group++ })\n continue\n }\n if (b.h >= b.w * ROTATE_RATIO) {\n for (const rot of [0, 90, 270] as const) jobs.push({ box: b, rot, group })\n group++\n continue\n }\n }\n // 한 줄 박스가 문 위아래 이웃 줄 글자 끝자락·좌우 끝 상자 테두리는 인식 crop 에서 배경으로 지운다 — 인식기가 받침·\"|\" 로\n // 읽는다 (line-split edgeTrim). 박스를 줄이면 인식 입력 배율이 바뀌어 가운뎃점이 \"•\" 로 커 보였다 — 박스·좌표는 그대로 둔다\n const trim = tuning.trimEdges ? edgeTrim(gray, b.w, b.h, ink) : null\n const keep = trim ? { ...trim, bg: median(gray) } : undefined\n // 목차 리더 점 무리는 인식하지 않고 앞뒤 글만 따로 인식한 뒤, 검출 박스 하나로 다시 합쳐 \"제목 … 쪽번호\" 아이템\n // 하나를 낸다 — 텍스트층도 목차 줄을 리더 글자까지 한 줄로 준다. 조각을 따로 두면 줄 기하가 바뀌어 뒤 단계가\n // 흔들렸다: 리더 자리를 비우면 속기록 1면 목차 줄이 2단 본문 줄로 잡혀 단 판정이 무너졌고(assembly-minutes-1179),\n // 리더를 따로 세우면 클러스터 표가 그것을 열로 삼았다(gwd-info-plan 목차). 리더 앞 조각은 점 한두 개까지 물려\n // 인식하고 물린 점은 결과 끝에서 지운다 — 끝 글자 바로 뒤에서 자르면 인식기가 오른쪽 맥락을 잃어 로마 숫자 Ⅰ 를\n // 1 로 읽었다(eval-rda \"전략목표 Ⅰ\"·\"성과목표 Ⅰ-1\", 박스 높이 0.5배 물림으로 Ⅰ·Ⅱ·Ⅲ 11줄 복원)\n const leaders = tuning.splitLeaders ? leaderRuns(gray, b.w, b.h, ink, LEADER_MIN_DOTS) : []\n if (leaders.length) {\n const join = joins.length\n joins.push({ box: b, parts: [], trailDots: false })\n let x0 = 0, dots = false\n for (const [a, c] of [...leaders, [b.w, b.w]]) {\n const bare = { x: b.x + x0, y: b.y, w: a - x0, h: b.h }\n const end = c > a ? Math.min(c, a + Math.round(b.h * LEADER_CONTEXT)) : a\n x0 = c\n if (bare.w >= DET_MIN_SIZE && inkStats(grayCrop(rgba, width, bare)).contrast >= Math.max(1, tuning.minInkContrast)) {\n jobs.push({ box: { ...bare, w: end - (bare.x - b.x) }, rot: 0, group: group++, join, dotsBefore: dots, trimDots: end > a })\n dots = false\n }\n if (c > a) dots = true\n }\n joins[join].trailDots = dots\n continue\n }\n // 지운 crop 은 원본 crop 과 한 그룹으로 둘 다 인식해 신뢰도가 높은 쪽을 쓴다 — 끝자락이 글자 획과 붙어 있거나\n // 인식기가 조각 없이도 흔들리는 줄(아이콘·로고 칸)에서 지우기가 오히려 틀리게 만들지 않게\n if (keep) jobs.push({ box: b, rot: 0, group, keep })\n jobs.push({ box: b, rot: 0, group: group++ })\n }\n\n const results = await this.recognizeJobs(rgba, width, jobs, tuning.recBatch)\n\n // 회전 후보 그룹은 최고 신뢰도 하나만\n const best = new Map<number, { job: LineJob; text: string; confidence: number; steps: number[]; stepPx: number }>()\n // 끝자락을 지운 crop 은 원본보다 신뢰도가 KEEP_MARGIN 넘게 높을 때만 — 받침 조각 오독은 원본 0.46~0.89 → 지운 쪽 0.99 로\n // 차이가 크고, 차이가 작은 쪽은 띄어쓰기·대시 길이 같은 잡음이었다(과학영재 안내문 \"gifted\" → \"gifed\" +0.015)\n const score = (job: LineJob, confidence: number) => confidence - (job.keep ? KEEP_MARGIN : 0)\n jobs.forEach((job, i) => {\n const r = results[i]\n if (!r) return\n const cur = best.get(job.group)\n if (!cur || score(job, r.confidence) > score(cur.job, cur.confidence)) best.set(job.group, { job, ...r })\n })\n\n let items: OcrItem[] = []\n for (const r of best.values()) {\n const { job, confidence, stepPx } = r\n const { text: read, steps } = tuning.postprocess && job.rot === 0 ? trimInklessEnds(rgba, width, job.box, r.text, r.steps, stepPx) : r\n const note: { ringLead?: boolean } = {}\n const raw = tuning.postprocess && job.rot === 0 ? restoreGlyphs(rgba, width, job.box, read, steps, stepPx, note) : read\n let text = tuning.postprocess ? restoreSymbols(raw.trim(), note.ringLead) : raw\n if (!text.trim()) continue\n // 사전 밖·작은 점으로 읽히거나 빠지는 글머리(◎ ● ▪ □) — 첫 글리프 모양으로 되살린다 (line-split.ts)\n if (tuning.postprocess && job.rot === 0 && !/^[◎●▪□■○ㅇ]/.test(text)) {\n const chars = [...read], k = chars.findIndex(c => c.trim())\n if (k >= 0 && steps.length === chars.length) {\n const g = grayCrop(rgba, width, job.box)\n const b = leadingBullet(g, job.box.w, job.box.h, inkStats(g), (steps[k] + 0.5) * stepPx)\n // ◎ 는 \"O\" 로 잘못 읽은 자리만 바꾼다(장식 아이콘에 끼워 넣지 않게). ●▪ 는 본문 줄(뒤 글 한글 4음절 이상)에서만 —\n // 표 칸의 큰 가운뎃점(\"·수학\"·\"·승합\", 글꼴에 따라 네모·원으로 그려짐)은 정답도 \"·\" 다\n const body = (text.match(/[가-힣]/g) ?? []).length >= 4\n const miss = /^[Oo0•·ㆍ∙‧○]/.test(text)\n if (b?.mark === \"\\u25ce\" && b.covers && /^[Oo0○]/.test(text)) text = b.mark + \" \" + text.slice(1).trimStart()\n // □ 는 인식에서 빠진 자리만(첫 글자 앞) — 모델은 사전에 □ 가 있어도 내지 않고 통째로 빠뜨린다\n else if (b?.mark === \"\\u25a1\") { if (!b.covers && body) text = b.mark + \" \" + text }\n else if (b && b.mark !== \"\\u25ce\" && body) text = b.covers ? (miss ? b.mark + \" \" + text.slice(1).trimStart() : text) : b.mark + \" \" + text\n }\n }\n // 숫자 앞 △·▲ 는 사전 밖이라 빈칸으로 사라진다 — 박스 맨 앞 글자 모양으로 되살린다 (line-split.ts)\n if (tuning.postprocess && /^\\d/.test(text) && job.rot === 0) {\n const g = grayCrop(rgba, width, job.box)\n const tri = leadingTriangle(g, job.box.w, job.box.h, inkStats(g))\n if (tri) text = tri + text\n }\n if (tuning.postprocess && isDotFragment(text)) continue\n if (confidence < tuning.textScore) {\n if (stats) {\n stats.droppedLowConf++\n stats.dropped?.push({ text, ...this.itemBox(rgba, width, job.box, tuning), confidence })\n }\n continue\n }\n if (job.join !== undefined) {\n if (job.trimDots) text = text.replace(/[\\s.:\\u00b7\\u2022\\u2024\\u2025\\u2026\\u2219\\u22c5\\u318d]+$/u, \"\")\n if (text) joins[job.join].parts.push({ x: job.box.x, text, dotsBefore: job.dotsBefore === true, confidence })\n continue\n }\n const item = { text, ...this.itemBox(rgba, width, job.box, tuning), confidence }\n if (job.rot === 0) rememberOcrTextGaps(item, findOcrTextGaps(rgba, width, job.box, read, text, steps, stepPx))\n items.push(item)\n }\n const leaderEnds = new Map<OcrItem, { lead: boolean; trail: boolean }>()\n for (const j of joins) {\n if (!j.parts.length) continue\n j.parts.sort((a, b) => a.x - b.x)\n let text = \"\"\n for (const p of j.parts) text += (p.dotsBefore ? (text ? \" \\u2026 \" : \"\\u2026\") : text ? \" \" : \"\") + p.text\n if (j.trailDots) text += \" \\u2026\"\n const item = { text, ...this.itemBox(rgba, width, j.box, tuning), confidence: Math.min(...j.parts.map(p => p.confidence)) }\n items.push(item)\n leaderEnds.set(item, { lead: j.parts[0].dotsBefore, trail: j.trailDots })\n }\n if (leaderEnds.size) items = joinLeaderItems(items, leaderEnds)\n if (tuning.postprocess) restoreBulletItems(items)\n items.sort((a, b) => (a.y - b.y) || (a.x - b.x))\n return items\n }\n\n /** 결과 좌표 — det 박스 그대로 또는 박스 안 잉크 외곽 (tightBoxes) */\n private itemBox(rgba: Uint8Array, width: number, b: Box, tuning: Readonly<OcrTuning>): Box {\n if (!tuning.tightBoxes) return b\n const gray = grayCrop(rgba, width, b)\n const t = inkBounds(gray, b.w, b.h, inkStats(gray))\n return { x: b.x + t.x0, y: b.y + t.y0, w: t.x1 - t.x0, h: t.y1 - t.y0 }\n }\n\n // ─── det ─────────────────────────────────────────────\n\n private async detect(\n rgba: Uint8Array,\n width: number,\n height: number,\n tuning: Readonly<OcrTuning>,\n stats?: OcrPageStats,\n ): Promise<Box[]> {\n const ratio = tuning.detLongSide / Math.max(width, height)\n const dw = Math.max(32, Math.round((width * ratio) / 32) * 32)\n const dh = Math.max(32, Math.round((height * ratio) / 32) * 32)\n\n const rgb = await this.sharp(rgba, { raw: { width, height, channels: 4 } })\n .resize(dw, dh, { fit: \"fill\" })\n .removeAlpha()\n .raw()\n .toBuffer()\n\n // HWC RGB → CHW BGR float32 정규화\n const plane = dw * dh\n const input = new Float32Array(3 * plane)\n for (let i = 0; i < plane; i++) {\n const r = rgb[i * 3] / 255\n const g = rgb[i * 3 + 1] / 255\n const b = rgb[i * 3 + 2] / 255\n input[i] = (b - DET_MEAN[0]) / DET_STD[0]\n input[plane + i] = (g - DET_MEAN[1]) / DET_STD[1]\n input[2 * plane + i] = (r - DET_MEAN[2]) / DET_STD[2]\n }\n\n const tensor = new this.ort.Tensor(\"float32\", input, [1, 3, dh, dw])\n const out = await this.det.run({ [this.det.inputNames[0]]: tensor })\n const probMap = out[this.det.outputNames[0]].data as Float32Array\n\n const rawBoxes = componentBoxes(probMap, dw, dh, tuning.detThresh, tuning.detBoxThresh)\n if (stats) stats.truncatedBoxes = Math.max(0, rawBoxes.length - DET_MAX_BOXES)\n const sx = width / dw\n const sy = height / dh\n const boxes: Box[] = []\n for (const rb of rawBoxes.slice(0, DET_MAX_BOXES)) {\n // unclip: DB 는 학습 시 텍스트 영역을 수축시키므로 검출 박스를 되팽창\n const bw = rb.x2 - rb.x1 + 1\n const bh = rb.y2 - rb.y1 + 1\n const delta = (bw * bh * tuning.detUnclip) / (2 * (bw + bh))\n const x1 = Math.max(0, Math.floor((rb.x1 - delta) * sx))\n const y1 = Math.max(0, Math.floor((rb.y1 - delta) * sy))\n const x2 = Math.min(width, Math.ceil((rb.x2 + 1 + delta) * sx))\n const y2 = Math.min(height, Math.ceil((rb.y2 + 1 + delta) * sy))\n if (x2 - x1 < DET_MIN_SIZE || y2 - y1 < DET_MIN_SIZE) continue\n boxes.push({ x: x1, y: y1, w: x2 - x1, h: y2 - y1 })\n }\n return boxes\n }\n\n // ─── rec ─────────────────────────────────────────────\n\n /** 라인 작업들을 폭 비율 순으로 배치 인식 — 결과는 jobs 순서 */\n private async recognizeJobs(\n rgba: Uint8Array,\n pageW: number,\n jobs: LineJob[],\n batchSize: number,\n ): Promise<Array<{ text: string; confidence: number; steps: number[]; stepPx: number } | null>> {\n const crops = jobs.map(j => lineCrop(rgba, pageW, j.box, j.rot, j.keep))\n const order = crops.map((_, i) => i).sort((a, b) => crops[a].w - crops[b].w)\n const results: Array<{ text: string; confidence: number; steps: number[]; stepPx: number } | null> = new Array(jobs.length).fill(null)\n const plane = REC_HEIGHT\n for (let s = 0; s < order.length;) {\n // 폭 오름차순이라 배치 마지막 원소가 최대 폭\n let e = s + 1\n while (e < order.length && e - s < Math.max(1, batchSize)\n && (e - s + 1) * Math.max(REC_MIN_WIDTH, crops[order[e]].w) * plane <= REC_BATCH_MAX_PIXELS) e++\n const idx = order.slice(s, e)\n const bw = Math.max(REC_MIN_WIDTH, crops[idx[idx.length - 1]].w)\n const n = idx.length\n const chw = 3 * REC_HEIGHT * bw\n const input = new Float32Array(n * chw) // pad 영역 0 (공식 zero-pad 와 동일)\n idx.forEach((ci, k) => {\n const c = crops[ci]\n const base = k * chw\n const p = REC_HEIGHT * bw\n for (let y = 0; y < REC_HEIGHT; y++) {\n for (let x = 0; x < c.w; x++) {\n const src = (y * c.w + x) * 3\n const dst = base + y * bw + x\n // HWC RGB → CHW BGR, (x/255-0.5)/0.5\n input[dst] = c.rgb[src + 2] / 127.5 - 1\n input[dst + p] = c.rgb[src + 1] / 127.5 - 1\n input[dst + 2 * p] = c.rgb[src] / 127.5 - 1\n }\n }\n })\n const tensor = new this.ort.Tensor(\"float32\", input, [n, 3, REC_HEIGHT, bw])\n const out = await this.rec.run({ [this.rec.inputNames[0]]: tensor })\n const logits = out[this.rec.outputNames[0]]\n const [, T, C] = logits.dims as number[]\n // CTC 클래스 = blank + 사전 + 공백 — 사전과 짝이 아닌 rec 모델은 글자를 엉뚱하게 옮긴다(로컬 모델 교체 시 실수)\n if (C !== this.dict.length + 2) throw new KordocError(`OCR rec 출력 클래스 ${C}개가 사전 ${this.dict.length}자 + 2 와 다릅니다 — 모델에 맞는 사전(ocrRecDict)을 지정하세요`)\n const data = logits.data as Float32Array\n idx.forEach((ci, k) => {\n const r = ctcDecode(data.subarray(k * T * C, (k + 1) * T * C), T, C, this.dict)\n // CTC 시점 하나가 덮는 원본 픽셀 폭 — crop 은 높이 48 로 줄인 뒤 bw 까지 오른쪽을 채웠다 (회전 crop 은 세로 축)\n const job = jobs[ci], src = job.rot === 0 ? job.box.w : job.box.h\n // 채운 자리(crop 폭 밖)에 찍힌 글자 — CTC 는 짧은 박스의 진짜 끝 글자도 마지막 시점에 찍어(\"54\" → 시점 36·39) 자리만으로는 못 가른다.\n // crop 안 키 큰 잉크 덩어리보다 키 큰 글자가 많을 때 남는 것만 지어낸 것으로 버린다 — 칸에 \"/\" 하나만 든 박스가 \"/)\"·\"(/:)\" 로\n // 읽혔다(여수 인원표). 점·쉼표류는 덩어리로 안 세니 그대로 둔다. \"(cid:9)\" 환각은 restoreSymbols 가 통째로 걷도록 손대지 않는다.\n // crop 안 글자가 두 자 이하인 작은 박스만 — 긴 낱말은 그림 성분이 글자 높이를 부풀려 소문자를 덩어리로 못 세었다(\"ToContent\" → \"ToCont\")\n let kept = r\n const last = (crops[ci].w * T) / bw + 1\n const chars = r ? [...r.text] : []\n if (r && job.rot === 0 && chars.length === r.steps.length && r.steps.some(t => t > last) && !r.text.includes(\"(cid:\")) {\n const tallCh = (c: string) => !/[\\s.,:;\\u00b7\\u2026'\"\\u2018-\\u201d`\\-_~]/.test(c)\n const g = grayCrop(rgba, pageW, job.box)\n const inside = chars.filter((c, i) => r.steps[i] <= last && tallCh(c)).length\n let budget = inside <= 2 ? tallInkCount(g, job.box.w, job.box.h, inkStats(g)) - inside : Infinity\n const keep = chars.map((c, i) => r.steps[i] <= last || !tallCh(c) || budget-- > 0)\n if (!keep.every(Boolean)) kept = { ...r, text: chars.filter((_, i) => keep[i]).join(\"\"), steps: r.steps.filter((_, i) => keep[i]) }\n }\n results[ci] = kept && { ...kept, stepPx: (bw / T) * (src / crops[ci].w) }\n })\n s = e\n }\n return results\n }\n}\n\n/** 회색조 중앙값 — 글줄 박스는 배경 픽셀이 다수라 배경 휘도가 된다 */\nfunction median(gray: Uint8Array): number {\n const hist = new Uint32Array(256)\n for (let i = 0; i < gray.length; i++) hist[gray[i]]++\n for (let v = 0, n = 0; v < 256; v++) if ((n += hist[v]) * 2 >= gray.length) return v\n return 255\n}\n\n/** CTC greedy 디코드 — 연속 중복 붕괴 → blank(0) 제거 → 사전 매핑 (테스트용 export) */\nexport function ctcDecode(\n data: Float32Array,\n T: number,\n C: number,\n dict: string[],\n): { text: string; confidence: number; steps: number[] } | null {\n let text = \"\"\n let confSum = 0\n let confCount = 0\n let prev = -1\n /** 글자(코드 포인트)마다 CTC 시점 구간 [첫, 끝] — 괄호 모양 판별이 글자 자리를 픽셀로 옮길 때 쓴다 */\n const runs: Array<[number, number]> = []\n let open: Array<[number, number]> = []\n for (let t = 0; t < T; t++) {\n const off = t * C\n let best = 0\n let bestV = data[off]\n for (let c = 1; c < C; c++) {\n const v = data[off + c]\n if (v > bestV) { bestV = v; best = c }\n }\n const repeat = best === prev\n prev = best\n if (repeat && best !== 0) { for (const r of open) r[1] = t; continue }\n open = []\n if (best === 0) continue\n // 모델 출력이 softmax 확률이 아니면 (>1) 해당 스텝만 정규화\n let p = bestV\n if (p > 1.0001 || p < 0) {\n let denom = 0\n for (let c = 0; c < C; c++) denom += Math.exp(data[off + c] - bestV)\n p = 1 / denom\n }\n confSum += p\n confCount++\n const tok = best >= 1 && best <= dict.length ? dict[best - 1] : best === dict.length + 1 ? \" \" : \"\"\n text += tok\n for (const _ of tok) { const r: [number, number] = [t, t]; runs.push(r); open.push(r) }\n }\n if (!text) return null\n return { text, confidence: confCount > 0 ? confSum / confCount : 0, steps: runs.map(([a, b]) => (a + b) / 2) }\n}\n\n/** 이진화 확률맵의 4-연결 성분 bbox (score = 성분 평균 확률, 테스트용 export) */\nexport function componentBoxes(\n prob: Float32Array,\n w: number,\n h: number,\n thresh: number = DEFAULT_OCR_TUNING.detThresh,\n boxThresh: number = DEFAULT_OCR_TUNING.detBoxThresh,\n): Array<{ x1: number; y1: number; x2: number; y2: number }> {\n const visited = new Uint8Array(w * h)\n const boxes: Array<{ x1: number; y1: number; x2: number; y2: number; score: number }> = []\n const stack: number[] = []\n\n for (let start = 0; start < w * h; start++) {\n if (visited[start] || prob[start] <= thresh) continue\n let x1 = start % w, x2 = x1, y1 = (start / w) | 0, y2 = y1\n let sum = 0\n let count = 0\n stack.length = 0\n stack.push(start)\n visited[start] = 1\n while (stack.length) {\n const p = stack.pop()!\n const px = p % w\n const py = (p / w) | 0\n sum += prob[p]\n count++\n if (px < x1) x1 = px\n if (px > x2) x2 = px\n if (py < y1) y1 = py\n if (py > y2) y2 = py\n // 4-이웃\n if (px > 0 && !visited[p - 1] && prob[p - 1] > thresh) { visited[p - 1] = 1; stack.push(p - 1) }\n if (px < w - 1 && !visited[p + 1] && prob[p + 1] > thresh) { visited[p + 1] = 1; stack.push(p + 1) }\n if (py > 0 && !visited[p - w] && prob[p - w] > thresh) { visited[p - w] = 1; stack.push(p - w) }\n if (py < h - 1 && !visited[p + w] && prob[p + w] > thresh) { visited[p + w] = 1; stack.push(p + w) }\n }\n if (x2 - x1 + 1 < DET_MIN_SIZE && y2 - y1 + 1 < DET_MIN_SIZE) continue\n boxes.push({ x1, y1, x2, y2, score: sum / count })\n }\n\n return boxes\n .filter(b => b.score >= boxThresh)\n .sort((a, b) => (a.y1 - b.y1) || (a.x1 - b.x1))\n}\n\nasync function tryImport<T>(name: string, loader: () => Promise<T>): Promise<T> {\n try {\n return await loader()\n } catch (e) {\n throw new Error(\n `내장 OCR 을 사용하려면 optional dependency '${name}' 이 필요합니다. ` +\n `\\`npm install ${name}\\` 후 다시 실행하세요.${OPTIONAL_DEP_INSTALL_HINT} 원인: ${(e as Error).message}`,\n )\n }\n}\n\n/** 로컬 모델 파일을 읽어 SHA-256 을 대조한다 — 다르면 로드하지 않는다 */\nasync function verifiedBytes(s: { file: { path: string; sha256: string } }): Promise<Uint8Array> {\n // 경로 전체는 내지 않는다(sanitizeError 와 같은 원칙) — 파일 이름과 원인만. errno(ENOENT) 를 그대로 쓰면 classifyError 가\n // 입력 문서가 없는 FILE_NOT_FOUND 로 분류한다\n const name = basename(s.file.path)\n const fail = (why: string) => new KordocError(`OCR 모델 파일을 읽지 못했습니다: ${name} (${why})`)\n const why = (e: unknown) => {\n const code = (e as NodeJS.ErrnoException).code\n return code === \"ENOENT\" ? \"파일이 없음\" : code === \"EACCES\" || code === \"EPERM\" ? \"권한 없음\" : \"읽기 실패\"\n }\n // 일반 파일만, 상한 안에서 — FIFO 는 readFile 이 끝나지 않고, 엉뚱한 큰 파일을 메모리에 다 올리지 않는다\n let st: Awaited<ReturnType<typeof stat>>\n try { st = await stat(s.file.path) } catch (e) { throw fail(why(e)) }\n if (!st.isFile()) throw fail(\"일반 파일이 아님\")\n if (st.size > MAX_LOCAL_MODEL_BYTES) throw fail(`${Math.round(st.size / 1048576)}MB — 모델 파일로 보기에 너무 큽니다`)\n let bytes: Buffer\n try { bytes = await readFile(s.file.path) } catch (e) { throw fail(why(e)) }\n const actual = createHash(\"sha256\").update(bytes).digest(\"hex\")\n if (actual !== s.file.sha256) {\n throw new KordocError(`OCR 모델 SHA256 mismatch: ${basename(s.file.path)} — expected ${s.file.sha256}, got ${actual} (로드 거부)`)\n }\n return bytes\n}\n\n// ─── 엔진 캐시 (watch/서버 장기 실행에서 세션 재사용) — 모델 선택마다 하나 ───\nconst engines = new Map<string, Promise<OcrEngine>>()\n\nexport function getOcrEngine(models: OcrModelSelection = DEFAULT_OCR_MODELS): Promise<OcrEngine> {\n const key = ocrSelectionKey(models)\n let p = engines.get(key)\n if (!p) {\n p = OcrEngine.create(models).catch(err => {\n engines.delete(key) // 실패는 캐시하지 않음 — 모델 설치 후 재시도 가능\n throw err\n })\n engines.set(key, p)\n }\n return p\n}\n","/**\n * 인식 결과 글리프 되살리기 — CTC 글자 자리(steps × stepPx, 박스 로컬 x)를 박스 픽셀로 옮겨 그 자리 잉크 조각 모양으로, 사전 밖이거나\n * 모델이 줄여·바꿔·빠뜨려 읽는 글자를 고친다(괄호 「」【】·여는 따옴표·원문자·로마 숫자·두 글자 사이에서 빠진 기호). 모양 판정은\n * line-split.ts, 여기는 글자열 배선만. 글자 배열은 CTC 글자와 1:1 로 두고 칸마다 바꿔 넣거나(빈 글·여러 글자 허용) 끝에 잇는다\n */\nimport { bracketFeatures, bracketShape, circledAt, dashRatio, discAt, gapGlyphs, grayCrop, inkStats, quoteHead, ringBullet, romanStems, serifOne, starRun } from \"./line-split.js\"\nimport type { InkStats } from \"./line-split.js\"\nimport type { Box } from \"./crop.js\"\n\n/** 무늬 글자 판정 — 배경(상자 중앙값)과 이만큼 넘게 달라야 잉크 */\nconst INKLESS_DELTA = 32\n\ninterface Ctx { chars: string[]; cx: (i: number) => number; gray: Uint8Array; w: number; h: number; ink: InkStats }\n\n/** text 는 CTC 글자와 1:1 이어야 한다 — 아니면 그대로 돌려준다. note.ringLead: 줄 머리 고리를 픽셀로 정했다(restoreSymbols 의 O → ○ 를 건너뛸 것) */\nexport function restoreGlyphs(rgba: Uint8Array, pageW: number, box: Box, text: string, steps: number[], stepPx: number, note: { ringLead?: boolean } = {}): string {\n const chars = [...text]\n if (chars.length !== steps.length) return text\n const gray = grayCrop(rgba, pageW, box)\n const c: Ctx = { chars, cx: i => (steps[i] + 0.5) * stepPx, gray, w: box.w, h: box.h, ink: inkStats(gray) }\n if (/[[\\]]/.test(text)) brackets(c)\n if (/['\"]/.test(text)) quotes(c)\n if (/(^|\\D)[1-9](\\D|$)/.test(text)) circled(c)\n if (text.includes(\"*\")) stars(c)\n if (/[\\u00b7\\u2022]/.test(text)) discs(c)\n if (/[-\\u2013\\u2014]/.test(text)) dashes(c)\n gaps(c)\n note.ringLead = ring(c)\n if (/[I1Ⅰ-Ⅲ]|^\\s*\\./.test(text)) roman(c)\n return chars.join(\"\")\n}\n\n/**\n * 줄 끝 무늬 글자 — 검출 상자가 제목 뒤 배경 무늬 띠까지 잡으면 CTC 가 무늬를 글자로 읽는다(선거 사례집 표지의 흰 무늬 띠\n * \"D①D①①①①①①①정치관계법\"). 줄 양 끝에서 이어지는 글자 중 제 자리(CTC 자리 앞뒤 한 칸, 줄 높이의 1/4 이상)에 배경과 뚜렷이\n * 다른 픽셀이 없는 글자를 떼어 낸다 — 배경은 상자 중앙값, 높이를 거의 다 덮는 세로 괘선 열은 잉크로 치지 않는다. 무늬는 배경과\n * 23 차이(232 → 255)라 떨어지고, 연한 회색 글씨·색 띠 위 흰 글씨(ODL 그림 \"READERS\")는 남는다. 남는 글자가 없으면 그대로 둔다\n */\nexport function trimInklessEnds(rgba: Uint8Array, pageW: number, box: Box, text: string, steps: number[], stepPx: number): { text: string; steps: number[] } {\n const chars = [...text], { w, h } = box\n // CTC 자리가 상자를 벗어난 줄(그림 속 짧은 글을 넓게 읽은 crop, 폭 92 에 자리 406)은 자리를 믿지 않는다\n if (chars.length !== steps.length || steps.some(s => (s + 0.5) * stepPx > w + stepPx)) return { text, steps }\n const gray = grayCrop(rgba, pageW, box)\n const hist = new Uint32Array(256)\n for (const v of gray) hist[v]++\n let bg = 0\n for (let n = 0; bg < 255 && (n += hist[bg]) < gray.length / 2;) bg++\n const cols = new Uint32Array(w)\n for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) if (Math.abs(gray[y * w + x] - bg) > INKLESS_DELTA) cols[x]++\n // 글자 자리 앞뒤 창 — 시간 칸 하나(작은 상자는 4~5px)로는 끝 글자 \"l\" 잉크를 놓친다\n const half = Math.max(stepPx, h * 0.25)\n const inkless = (i: number) => {\n const cx = (steps[i] + 0.5) * stepPx\n 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\n return true\n }\n // 문장부호는 떼지 않는다 — 작은 따옴표는 CTC 자리가 잉크에서 벗어나기 쉽다(국회도서관 보도자료 1° 기울임 제목 머리 \"“\")\n const blank = (i: number) => !chars[i].trim()\n const mark = (i: number) => /\\p{P}/u.test(chars[i])\n let a = 0, b = chars.length\n while (a < b && (blank(a) || !mark(a) && inkless(a))) a++\n while (b > a && (blank(b - 1) || !mark(b - 1) && inkless(b - 1))) b--\n const cut = (from: number, to: number) => chars.slice(from, to).some(c => c.trim())\n if (a === b || !cut(0, a) && !cut(b, chars.length)) return { text, steps }\n return { text: chars.slice(a, b).join(\"\"), steps: steps.slice(a, b) }\n}\n\n/**\n * 사전 밖 괄호 — 인식 사전에 「」·【】 가 없어 모델은 모두 [ ] 로 읽는다(코퍼스 정답 「」44·「」23·【】41·[]159 쌍 ↔ OCR [ 274).\n * [ ] 자리 잉크 조각이 속이 찬 조각이면 【】, 윗변 가로 획만이면 「, 아랫변 가로 획만이면 」 ([ ] 는 위아래 모두)\n */\nfunction brackets({ chars, cx, gray, w, h, ink }: Ctx) {\n for (let i = 0; i < chars.length; i++) {\n if (chars[i] !== \"[\" && chars[i] !== \"]\") continue\n const f = bracketFeatures(gray, w, h, ink, cx(i))\n const shape = f && bracketShape(f, chars[i] === \"]\")\n if (shape) chars[i] = shape\n }\n}\n\n/**\n * 여는 따옴표 — 곧은 ' \" 로 읽은 자리의 머리가 뚜렷이 아래(line-split quoteHead ≥ 0.47, 실측 닫는 따옴표 최대 0.46 바로 위)면 ‘ “ 로 정한다. smartQuotes 문맥 규칙은\n * 연도 생략을 늘 ’ 로 두는데 \"(‘11.3월 구성)\"처럼 여는 따옴표로 쓴 문서가 있다. 닫는 쪽은 글꼴마다 겹쳐(작은 글씨 여는 따옴표 0.23~0.45,\n * 닫는 0.28~0.46 — 코퍼스 61곳 실측) 문맥에 맡긴다\n */\nfunction quotes({ chars, cx, gray, w, h, ink }: Ctx) {\n // smartQuotes 는 남은 곧은 따옴표를 홀짝으로 여닫으니, 짝 계산에 드는 자리를 바꾸면 나머지가 어긋난다 — 짝에서 빠지는\n // 연도 생략('24)과 그 줄에 하나뿐인 곧은 따옴표만 본다. 짝을 기다리는 여는 따옴표 뒤는 닫는 자리다(모델이 여는 쪽만 ‘ 로 읽은 줄)\n const year = (i: number) => chars[i] === \"'\" && !/[\\p{L}\\p{N}]/u.test(chars[i - 1] ?? \"\") && /^\\d{2}(?!\\d)/.test(chars.slice(i + 1, i + 4).join(\"\"))\n const single = { \"'\": chars.filter((c, i) => c === \"'\" && !year(i)).length === 1, '\"': chars.filter(c => c === '\"').length === 1 }\n const open = { \"'\": false, '\"': false }\n for (let i = 0; i < chars.length; i++) {\n const c = chars[i]\n const kind = c === \"'\" || c === \"‘\" || c === \"’\" ? \"'\" : c === '\"' || c === \"“\" || c === \"”\" ? '\"' : null\n if (!kind) continue\n if (c !== kind) { open[kind] = c === \"‘\" || c === \"“\"; continue }\n if (open[kind]) { open[kind] = false; continue }\n if (!year(i) && !single[kind]) continue\n const r = quoteHead(gray, w, h, ink, cx(i))\n if (r !== null && r >= 0.47) { chars[i] = kind === \"'\" ? \"‘\" : \"“\"; open[kind] = true }\n }\n}\n\n/**\n * 대시 길이 — 붙임표를 \"–\"·\"—\" 로, 긴 줄표를 \"-\" 로 읽은 자리를 획 길이(line-split dashRatio)로 다시 정한다. 0.65~0.75배 사이는 그대로.\n * 긴 줄표로 늘리는 건 글자가 있는 줄의 머리 글머리(\"―상하수도요금\")만 — 숫자뿐인 쪽 번호 \"- 3 -\" 은 띠가 낮게 잡히고, 숫자 사이\n * \"511-01\" 은 스캔에서 번져 길게 잰다\n */\nfunction dashes({ chars, cx, gray, w, h, ink }: Ctx) {\n const head = chars.findIndex(c => c.trim()), letters = chars.some(c => /[가-힣A-Za-z]/.test(c))\n for (let i = 0; i < chars.length; i++) {\n if (!/^[-\\u2013\\u2014]$/.test(chars[i])) continue\n const r = dashRatio(gray, w, h, ink, cx(i))\n if (r === null) continue\n if (r < 0.65) chars[i] = \"-\"\n else if (r > 0.75 && i === head && letters) chars[i] = \"\\u2014\"\n }\n}\n\n/** 원문자 숫자 — 모델이 고리를 버리고 읽은 홀로 선 숫자 1~9(앞뒤가 숫자 아님)를 그 자리 고리 성분(line-split circledAt)으로 ①~⑨ */\nfunction circled({ chars, cx, gray, w, h, ink }: Ctx) {\n const at = chars.flatMap((c, i) => /^[1-9]$/.test(c) && !/\\d/.test(chars[i - 1] ?? \"\") && !/\\d/.test(chars[i + 1] ?? \"\") ? [i] : [])\n if (!at.length) return\n const hit = circledAt(gray, w, h, ink, at.map(cx))\n at.forEach((i, k) => { if (hit[k]) chars[i] = String.fromCharCode(0x245f + Number(chars[i])) })\n}\n\n/** 별표 겹침 — CTC 가 붙은 같은 글자를 하나로 합친 \"**\" → \"*\" 를 그 자리 별표 조각 수(line-split starRun)만큼 채운다 */\nfunction stars({ chars, cx, gray, w, h, ink }: Ctx) {\n for (let i = 0; i < chars.length; i++) {\n if (chars[i] !== \"*\" || chars[i - 1] === \"*\") continue\n let e = i\n while (chars[e + 1] === \"*\") e++\n const n = starRun(gray, w, h, ink, cx(i))\n if (n > e - i + 1 && n <= 4) chars[e] += \"*\".repeat(n - (e - i + 1))\n i = e\n }\n}\n\n/** 큰 속 찬 원 ● — 모델은 줄 중간 글머리 ● 를 가운뎃점 · · • 로 줄여 읽는다(목차 \"…171●부록●186\") */\nfunction discs({ chars, cx, gray, w, h, ink }: Ctx) {\n for (let i = 0; i < chars.length; i++) if (/^[\\u00b7\\u2022]$/.test(chars[i]) && discAt(gray, w, h, ink, cx(i))) chars[i] = \"\\u25cf\"\n}\n\n/**\n * 두 글자 사이에서 빠진 기호 — 빈 열로 떨어진 잉크 조각의 모양(line-split gapGlyphs)이 · . 。 ▲ 「 」 □ 인데 그 가까이(글자 간격 0.45)에\n * CTC 글자 자리가 없으면 앞 글자 뒤에 넣는다. 모델은 가운뎃점을 공백으로 읽기도 해(\"개발·보급\" → \"개발 보급\") 그 구간의 공백은\n * · 로 바꾸고, □ 는 0·O 로 읽기도 해(\"□□-□□□\" → \"00-000\") 그 구간의 한 글자가 그것이면 바꾼다\n */\nfunction gaps({ chars, cx, gray, w, h, ink }: Ctx) {\n const { glyphs, bandH } = gapGlyphs(gray, w, h, ink)\n if (!glyphs.length) return\n const live = chars.map((c, i) => i).filter(i => chars[i].trim())\n if (!live.length) return\n // 글자 간격 — 이웃한 한글 두 자의 CTC 자리 차 중앙값(없으면 글줄 띠 높이). 조각 가운데에서 0.45 간격 안에 글자 자리가 있으면 덮인 것\n // (작은 점은 CTC 자리가 조각 x 구간을 1~2px 비껴 조각 구간만 보면 이미 읽은 \"·\" 옆에 또 넣었다)\n const d: number[] = []\n for (let k = 0; k + 1 < live.length; k++) if (/[가-힣]$/.test(chars[live[k]]) && /^[가-힣]/.test(chars[live[k + 1]])) d.push(cx(live[k + 1]) - cx(live[k]))\n const pitch = d.length >= 2 ? d.sort((a, b) => a - b)[d.length >> 1] : bandH\n const nearTo = (g: { x0: number; x1: number }) => (i: number) => Math.abs(cx(i) - (g.x0 + g.x1) / 2) <= Math.max(pitch * 0.45, (g.x1 - g.x0) / 2 + 1)\n const loose = glyphs.filter(g => /^[\\u00b7.]/.test(g.mark) && !live.some(nearTo(g)))\n for (const g of glyphs.sort((a, b) => a.x0 - b.x0)) {\n const mid = (g.x0 + g.x1) / 2\n const near = nearTo(g)\n const cover = live.filter(near)\n if (g.mark === \"\\u00b7\\u0001\") {\n // 작은 점·글자에 붙은 점은 모델이 공백으로 읽은 자리만(덮임 판정 앞 — 공백 자리가 증거다) — 다른 점이 늘어서지 않고 앞뒤가 글자일 때\n const sp = chars.findIndex((c, i) => c === \" \" && near(i))\n const p = sp >= 0 ? chars.slice(0, sp).reverse().find(c => c.trim()) : undefined, q = sp >= 0 ? chars.slice(sp + 1).find(c => c.trim()) : undefined\n // 앞뒤 모두 한글 — 라틴 글은 소문자 i·j 의 점이 공백 옆에 붙어 걸렸다(\"Keeps open\" → \"Keeps·open\", ODL 106)\n if (p && q && /[가-힣]$/.test(p) && /^[가-힣]/.test(q) && !loose.some(o => o !== g && Math.abs((o.x0 + o.x1) / 2 - mid) <= pitch * 1.5)) chars[sp] = \"\\u00b7\"\n continue\n }\n if (cover.length) {\n if (g.mark.startsWith(\"\\u25a1\") && cover.length === 1 && /^[0Oo\\u25cb]$/.test(chars[cover[0]])) chars[cover[0]] = \"\\u25a1\"\n continue\n }\n if (g.mark === \"\\u25a1\\u0000\") continue\n // 앞뒤 글자 사이 — 줄 끝은 마침표만(문장 끝 \"…습니다.\")\n const before = live.filter(i => cx(i) < mid), after = live.filter(i => cx(i) > mid)\n // 줄 머리는 뒤가 한글인 。 · 「 ▲ 만(예산서 세부 항목 \"。사무실운영\" — □ 는 glyph 판정이 엄한 leadingBullet 몫)\n if (!before.length) {\n if (after.length && /^[\\u3002\\u00b7\\u300c\\u25b2]$/.test(g.mark) && /^[가-힣]/.test(chars[after[0]])) chars[after[0]] = g.mark + chars[after[0]]\n // 쪽번호 꼬리말 \"- 1 -\" 를 \"1 -\" 로 읽은 줄 — 뒤 막대가 읽혔으면 앞 막대도 붙인다\n else if (g.mark === \"-\" && /^[\\dⅰ-ⅹivx]+\\s*-$/.test(chars.join(\"\").trim())) chars[after[0]] = \"-\" + chars[after[0]]\n continue\n }\n if (!after.length && g.mark !== \".\") continue\n if (g.mark === \"/\" && !(/\\d$/.test(chars[before[before.length - 1]]) && /^\\d/.test(chars[after[0]]))) continue // 빗금은 숫자 사이만(분수)\n const prev = chars[before[before.length - 1]], next = after.length ? chars[after[0]] : \"\"\n if (g.mark === \"\\u00b7\" || g.mark === \".\") {\n // 목차 리더·말줄임(안 읽힌 점이 둘 이상 늘어섬, 옆 글자가 … · .)은 빠진 기호가 아니다. 가운뎃점은 쪽번호 앞 점도 아니다\n if (loose.some(o => o !== g && Math.abs((o.x0 + o.x1) / 2 - mid) <= pitch * 1.5)) continue\n if (/[\\u2026.\\u00b7]$/.test(prev) || /^[\\u2026.\\u00b7]/.test(next)) continue\n }\n if (g.mark === \"\\u00b7\") {\n if (/^\\d/.test(next)) continue\n const sp = chars.findIndex((c, i) => c === \" \" && near(i))\n if (sp >= 0) { chars[sp] = g.mark; continue }\n }\n chars[before[before.length - 1]] += g.mark\n }\n}\n\n/**\n * 줄 머리 고리 글머리 — 첫 글자가 ㅇ·O·o·0·○ 이고 뒤가 한글(여는 괄호 뒤 한글 포함)이면 그 자리 고리의 크기·채움(line-split ringBullet)으로 ○ / ㅇ / o 를 정한다.\n * 모델이 같은 도형 ○ 를 \"O\"(→ restoreSymbols 가 ○)·\"ㅇ\" 로 오락가락 읽고, 자모 글머리 ㅇ 도 \"O\" 로 읽어 ○ 가 됐다\n */\nfunction ring({ chars, cx, gray, w, h, ink }: Ctx): boolean {\n const k = chars.findIndex(c => c.trim())\n if (k < 0 || !/^[\\u3147Oo0\\u25cb]$/.test(chars[k])) return false\n // 뒤 글이 한글이거나 여는 괄호 뒤 한글(\"❍(정책) 민생성장\")\n const rest = chars.slice(k + 1).join(\"\").trimStart()\n if (!/^[(\\[「【<]?[가-힣]/.test(rest)) return false\n const r = ringBullet(gray, w, h, ink, cx(k))\n if (r) chars[k] = r\n return r !== null\n}\n\n/**\n * 로마 숫자 획 수 — 모델은 한 글리프 Ⅲ 을 \"II\"·\"I\" 로, Ⅱ 를 \"I\" 로 줄여 읽는다(장 제목 \"Ⅲ. 2026년 …\" → \"II. 2026년 …\").\n * 라틴 글자에 붙지 않은 I·Ⅰ~Ⅲ 토막마다 그 자리 세로 획 수(line-split romanStems)를 세어 읽은 수와 다르면 Ⅰ~Ⅲ 한 글자로 바꾼다\n * (토막 첫 칸에 넣고 나머지 칸은 비운다). 숫자를 빠뜨리고 \".\" 부터 읽은 줄은 점 앞 잉크로 채운다\n */\nfunction roman({ chars, cx, gray, w, h, ink }: Ctx) {\n const ROMAN = /^[IⅠ-Ⅲ]$/, LATIN = /[A-Za-z]$/\n // 숫자 1 로 읽은 Ⅰ — 앞뒤가 숫자·라틴 글자가 아닌 홀로 선 1 이 좌우 대칭 세리프면(line-split serifOne). \"토픽1\" → \"토픽Ⅰ\"\n for (let i = 0; i < chars.length; i++) {\n if (chars[i] !== \"1\" || /[\\dA-Za-z]$/.test(chars[i - 1] ?? \"\") || /^[\\dA-Za-z,]/.test(chars[i + 1] ?? \"\")) continue\n if (serifOne(gray, w, h, ink, cx(i))) chars[i] = \"Ⅰ\"\n }\n // 로마 숫자에 붙은 1 은 같은 토막으로 센다 (\"[표 Ⅱ-4]\" → \"[표I1-4]\")\n const inRoman = (i: number) => ROMAN.test(chars[i]) || (chars[i] === \"1\" && (ROMAN.test(chars[i - 1] ?? \"\") || ROMAN.test(chars[i + 1] ?? \"\")))\n // 숫자를 통째로 빠뜨리고 \".\" 부터 읽은 제목 줄(\".세입·세출 …\") — 점 앞 잉크가 세로 획이면 되살린다\n const k = chars.findIndex(c => c.trim())\n if (chars[k] === \".\" && chars.slice(k + 1).some(c => /[가-힣A-Za-z0-9]/.test(c))) {\n const n = romanStems(gray, w, h, ink, -Infinity, cx(k))\n if (n) chars[k] = String.fromCharCode(0x215f + n) + chars[k]\n }\n for (let s = 0; s < chars.length;) {\n if (!inRoman(s) || (s > 0 && LATIN.test(chars[s - 1]))) { s++; continue }\n let e = s\n while (e < chars.length && inRoman(e)) e++\n const tok = chars.slice(s, e).join(\"\")\n if (e < chars.length && /^[A-Za-z]/.test(chars[e])) { s = e; continue }\n let p = s - 1, q = e\n while (p >= 0 && !chars[p].trim()) p--\n while (q < chars.length && !chars[q].trim()) q++\n const n = romanStems(gray, w, h, ink, p >= 0 ? cx(p) : -Infinity, q < chars.length ? cx(q) : Infinity)\n const canon = n ? [[\"Ⅰ\", \"I\"], [\"Ⅱ\", \"II\"], [\"Ⅲ\", \"III\"]][n - 1] : null\n if (canon && !canon.includes(tok)) { chars[s] = canon[0]; for (let i = s + 1; i < e; i++) chars[i] = \"\" }\n s = e\n }\n}\n","/**\n * OCR 인식 문자열 후처리 — 한국 공문서 표기 관행 복원.\n *\n * PP-OCRv5 korean 인식기의 사전(11,945자)에는 공문서 글머리·표기 기호 일부가 없다\n * (○ U+25CB·△ U+25B3·◎·■·「」·【】 — 사전 실측). 모델은 사전 안의 닮은 글자로\n * 읽는다: ○ → 라틴 O(코퍼스 박스 단위 실측 145회)·o, △ → 증가 기호 ∆(29회).\n * 공문서에서 쓰이지 않는 쪽만 되돌린다 — 모호한 것(ㅇ 글머리는 실제 자모, 0 은\n * \"0원\" 같은 숫자)은 건드리지 않는다.\n *\n * 따옴표: 모델은 ‘ ’ 를 곧은 ' 로 읽는다(114회). 공문서 텍스트층은 둥근 따옴표가 압도적\n * (코퍼스 GT ‘’ 129 : ' 7 — 한컴 자동 고침) → 여닫음 문맥으로 둥근 따옴표를 복원한다.\n */\n\nconst HANGUL_START = /^[\\uac00-\\ud7a3]/\n\n/** 박스 텍스트 한 줄 후처리 (위치 독립 규칙) */\nexport function restoreSymbols(text: string, ringDecided = false): string {\n let s = text\n // ○ 글머리: 줄 머리 O/o + (공백) + 한글 — 줄 머리 고리를 픽셀로 이미 정했으면(glyph-restore ring) 건너뛴다\n if (!ringDecided) s = s.replace(/^([Oo])(\\s?)(?=[\\uac00-\\ud7a3])/, \"\\u25cb$2\")\n // ○○ 자리표시(○○시·○○○ 과장): 라틴 글자와 붙지 않은 O 2개 이상이 한글과 (공백 하나 사이로) 이웃\n s = s.replace(/(?<![A-Za-z])O{2,}(?![A-Za-z])/g, (m, i: number) =>\n /[\\uac00-\\ud7a3]$/.test(s.slice(Math.max(0, i - 2), i).trimEnd()) || /^\\s?[\\uac00-\\ud7a3]/.test(s.slice(i + m.length))\n ? \"\\u25cb\".repeat(m.length) : m)\n // △ 감액 표시: 사전 밖 → ∆(U+2206)·그리스 Δ(U+0394) 로 읽힘\n s = s.replace(/[\\u2206\\u0394]/g, \"\\u25b3\")\n // \"(cid:NN)\" 은 PDF 텍스트 추출기가 유니코드 없는 글리프에 쓰는 표기 — 인식기가 학습 자료에서 배워 티끌·가는\n // 막대 박스에 낸다(pcccr·rda-planfarm·yeosu 실측, 박스 5~16px). 인쇄된 글자일 수 없으니 지운다\n s = s.replace(/\\(cid:\\d*\\)/g, \"\")\n return smartQuotes(joinDigitGroups(s))\n}\n\n/**\n * 천 단위 숫자 안의 끼어든 공백 제거 — 모델이 쉼표 뒤에 공백을 넣는다(\"385, 426\"·\"4,802, 164\",\n * 예산서 표에서 수십 회 실측). 쉼표 앞이 1~3자리, 뒤가 정확히 3자리인 무리만 잇는다\n * (\"1, 2, 3\" 같은 목록·\"10, 20명\"은 뒤가 3자리가 아니라 그대로).\n */\nexport function joinDigitGroups(s: string): string {\n return s.replace(/(?<![\\d,])\\d{1,3}(?:, ?\\d{3})+(?![\\d])/g, m => m.replace(/, /g, \",\"))\n}\n\n/**\n * 곧은 따옴표 → 둥근 따옴표. 한 줄 안의 따옴표를 짝으로 보고 여닫음을 번갈아 준다 —\n * OCR 띄어쓰기가 불안정해(\"내에서'기록관리시스템'\") 앞 글자가 공백인지로는 여는 자리를\n * 가를 수 없다. 개수가 홀수면(줄바꿈에 걸친 인용) 첫 따옴표가 여는 자리(줄 머리·공백·\n * 여는 괄호 뒤)가 아닐 때 닫는 것부터. 연도 생략 '24 는 짝에서 빼고 ’.\n */\nexport function smartQuotes(s: string): string {\n if (!/['\"]/.test(s)) return s\n const chars = [...s]\n for (const q of [\"'\", '\"']) {\n const [open, close] = q === \"'\" ? [\"\\u2018\", \"\\u2019\"] : [\"\\u201c\", \"\\u201d\"]\n const pos: number[] = []\n for (let i = 0; i < chars.length; i++) {\n if (chars[i] !== q) continue\n const prev = i > 0 ? chars[i - 1] : \"\"\n if (q === \"'\" && !/[\\p{L}\\p{N}]/u.test(prev) && /^\\d{2}(?!\\d)/.test(chars.slice(i + 1, i + 4).join(\"\"))) {\n chars[i] = close // 연도 생략\n continue\n }\n pos.push(i)\n }\n if (pos.length === 0) continue\n const first = pos[0]\n const prev = first > 0 ? chars[first - 1] : \"\"\n const atOpen = prev === \"\" || /[\\s(\\[{<\\u300c\\u300e\\u3010\\u3008\\u300a\\u2018\\u201c·,:]/.test(prev)\n let isOpen = pos.length % 2 === 0 || atOpen\n for (const i of pos) { chars[i] = isOpen ? open : close; isOpen = !isOpen }\n }\n return chars.join(\"\")\n}\n\n/** 점류만으로 된 조각 — 목차 리더·잡티(\".\", \"..\", \"…\", \"·\"). 단독으로는 뜻이 없다 */\nexport function isDotFragment(text: string): boolean {\n return /^[\\s.\\u00b7\\u2024\\u2025\\u2026\\u2027\\u2219\\u22c5\\u318d]+$/.test(text)\n}\n\ntype Placed = { text: string; x: number; y: number; w: number; h: number; confidence: number }\n\n/**\n * 목차 한 줄 잇기. 텍스트층은 목차 줄을 리더 글자까지 한 아이템(\"제목 ······ 12\")으로 주는데, 검출기는 리더\n * 가운데를 비워 [제목 ···] [··· 12] 두 박스로 낸다. 그러면 줄마다 큰 틈이 생겨 클러스터 표 감지가 목차·머리\n * 영역을 표로 잡고, 그 뒤 2단 본문을 줄 단위로 섞어 읽었다(assembly-minutes-1179 1면 — 목차 쪽번호 하나를 더\n * 제대로 읽자 표 감지가 발화). 엔진이 박스 끝에서 확인한 리더 점(ends: lead = 박스가 점으로 시작, trail = 점으로\n * 끝남)이 마주 보는 같은 줄 가장 가까운 이웃과 \"제목 … 12\" 한 아이템으로 합친다.\n */\nexport function joinLeaderItems<T extends Placed>(items: T[], ends: Map<T, { lead: boolean; trail: boolean }>): T[] {\n const gone = new Set<T>()\n const sameLine = (a: T, b: T) => Math.abs(a.y + a.h / 2 - (b.y + b.h / 2)) <= Math.max(a.h, b.h) / 2\n const merge = (left: T, right: T): void => {\n const x2 = Math.max(left.x + left.w, right.x + right.w), y2 = Math.max(left.y + left.h, right.y + right.h)\n left.text = left.text.replace(/\\s*\\u2026$/, \"\") + \" \\u2026 \" + right.text.replace(/^\\u2026\\s*/, \"\")\n left.x = Math.min(left.x, right.x)\n left.y = Math.min(left.y, right.y)\n left.w = x2 - left.x\n left.h = y2 - left.y\n left.confidence = Math.min(left.confidence, right.confidence)\n gone.add(right)\n }\n const neighbor = (it: T, toLeft: boolean): T | null => {\n let best: T | null = null\n for (const o of items) {\n if (o === it || gone.has(o) || !sameLine(o, it)) continue\n if (toLeft ? o.x + o.w > it.x + 2 : o.x < it.x + it.w - 2) continue\n if (!best || (toLeft ? o.x + o.w > best.x + best.w : o.x < best.x)) best = o\n }\n return best\n }\n // 오른쪽부터 — 쪽번호 쪽(\"… 12\")을 왼쪽 이웃에 붙이고, 제목 쪽(\"제목 …\")은 오른쪽 이웃을 당겨 붙인다\n for (const it of [...items].sort((a, b) => b.x - a.x)) {\n if (gone.has(it) || !ends.get(it)?.lead) continue\n const left = neighbor(it, true)\n if (left) merge(left, it)\n }\n for (const it of [...items].sort((a, b) => a.x - b.x)) {\n if (gone.has(it) || !ends.get(it)?.trail) continue\n const right = neighbor(it, false)\n if (right) merge(it, right)\n }\n // 리더 점을 못 찾은 목차 줄 — 잡음·흐림에 점이 덜 잡히면 몇 줄만 [제목][쪽번호] 두 아이템으로 남고, 이어진 줄들 사이의\n // 그 두 줄이 클러스터 표를 불러 목차 전체를 칸에 섞었다(archives-record-duty 잡음 σ12: 29줄 중 3줄). 같은 쪽에서 리더로\n // 이은 줄 둘 이상과 오른쪽 끝(쪽번호 열)이 맞는 숫자 아이템은 쪽번호로 보고 같은 줄 왼쪽 이웃과 잇는다\n const toc = items.filter(it => !gone.has(it) && /\\s\\u2026\\s\\d{1,4}$/.test(it.text))\n if (toc.length >= 2) {\n for (const it of items) {\n if (gone.has(it) || toc.includes(it) || !/^[\\s.:\\u00b7\\u2026]*\\d{1,4}$/.test(it.text)) continue\n const x2 = it.x + it.w\n if (toc.filter(t => Math.abs(t.x + t.w - x2) <= Math.max(t.h, it.h)).length < 2) continue\n const left = neighbor(it, true)\n if (!left || toc.includes(left)) continue\n left.text = left.text.replace(/[\\s.:\\u00b7\\u2026]+$/, \"\")\n it.text = it.text.replace(/^[\\s.:\\u00b7\\u2026]+/, \"\")\n merge(left, it)\n }\n }\n return items.filter(it => !gone.has(it))\n}\n\n/**\n * 이웃 인지 ○ 복원 — 글머리 ○ 가 제 박스로 떨어져 \"O\" 한 글자로 읽힌 경우,\n * 같은 줄(세로 중심 차 < 높이 절반) 바로 오른쪽(글자 높이 3배 이내) 박스가 한글로 시작하면 ○.\n */\nexport function restoreBulletItems<T extends { text: string; x: number; y: number; w: number; h: number }>(items: T[]): void {\n for (const it of items) {\n if (it.text !== \"O\" && it.text !== \"o\") continue\n const cy = it.y + it.h / 2\n let next: T | null = null\n for (const o of items) {\n if (o === it || o.x < it.x + it.w * 0.5) continue\n if (Math.abs(o.y + o.h / 2 - cy) > Math.max(it.h, o.h) / 2) continue\n if (o.x - (it.x + it.w) > it.h * 3) continue\n if (!next || o.x < next.x) next = o\n }\n if (next && HANGUL_START.test(next.text.trimStart())) it.text = \"\\u25cb\"\n }\n}\n","/**\n * 인식 입력 준비 — 밴드 서브 박스 좌표, 라인 crop 리사이즈(회전 포함).\n * (engine.ts 에서 분리 — 엔진은 세션·배치·후처리 흐름만)\n */\n\nexport interface Box { x: number; y: number; w: number; h: number }\n\n/** rec 입력 높이 (공식 RecResizeImg image_shape [3, 48, 320]) */\nexport const REC_HEIGHT = 48\nconst REC_MAX_WIDTH = 3200\nconst MIN_SIZE = 3\n\n/**\n * 밴드 → 페이지 좌표 서브 박스. 위아래는 밴드 높이 20% 여유(이웃 밴드와의 간격 절반 이내),\n * 좌우는 괘선을 뺀 잉크 폭 + 밴드 높이 20% 여유(원 박스 안) — 칸 경계 세로선이 crop 에\n * 들어가 \"|국\" 으로 읽히지 않게\n */\nexport function bandBoxes(b: Box, bands: Array<{ y0: number; y1: number; x0: number; x1: number }>, pageH: number): Box[] {\n const out: Box[] = []\n for (let i = 0; i < bands.length; i++) {\n const { y0, y1, x0, x1 } = bands[i]\n const bh = y1 - y0\n const upGap = i > 0 ? (y0 - bands[i - 1].y1) / 2 : y0\n const downGap = i < bands.length - 1 ? (bands[i + 1].y0 - y1) / 2 : b.h - y1\n const top = Math.max(0, Math.round(b.y + y0 - Math.min(bh * 0.2, upGap)))\n const bot = Math.min(pageH, Math.round(b.y + y1 + Math.min(bh * 0.2, downGap)))\n const left = Math.max(b.x, Math.round(b.x + x0 - bh * 0.2))\n const right = Math.min(b.x + b.w, Math.round(b.x + x1 + bh * 0.2))\n if (bot - top >= MIN_SIZE && right - left >= MIN_SIZE) out.push({ x: left, y: top, w: right - left, h: bot - top })\n }\n return out\n}\n\n/**\n * 라인 crop → rec 입력 크기(높이 48, 비율 유지 폭) RGB. 회전은 crop 좌표계에서\n * (90 = 반시계 np.rot90, 270 = 시계). 리사이즈는 bilinear(반픽셀 중심, cv2 INTER_LINEAR 등가)\n * — 공식 전처리(cv2.resize 기본)와 같은 보간.\n */\n/** keep: 박스 로컬 [x0, x1)×[y0, y1) 밖은 배경 휘도 bg 로 칠한다 (rot 0 만 — 박스 끝에 걸린 이웃 줄 조각·상자 테두리 지우기) */\nexport function lineCrop(rgba: Uint8Array, pageW: number, box: Box, rot: 0 | 90 | 270, keep?: { x0: number; x1: number; y0: number; y1: number; bg: number }): { rgb: Uint8Array; w: number } {\n const srcW = rot === 0 ? box.w : box.h\n const srcH = rot === 0 ? box.h : box.w\n const rw = Math.min(REC_MAX_WIDTH, Math.max(16, Math.round((srcW * REC_HEIGHT) / srcH)))\n const rgb = new Uint8Array(rw * REC_HEIGHT * 3)\n // 회전 좌표 → 페이지 픽셀\n const at = (u: number, v: number): number => {\n let px: number, py: number\n if (rot === 0) { px = u; py = v }\n else if (rot === 90) { px = box.w - 1 - v; py = u }\n else { px = v; py = box.h - 1 - u }\n return ((box.y + py) * pageW + (box.x + px)) * 4\n }\n const fx = srcW / rw\n const fy = srcH / REC_HEIGHT\n for (let dy = 0; dy < REC_HEIGHT; dy++) {\n let sy = (dy + 0.5) * fy - 0.5\n if (sy < 0) sy = 0\n const y0 = Math.min(srcH - 1, Math.floor(sy))\n const y1 = Math.min(srcH - 1, y0 + 1)\n const wy = sy - y0\n for (let dx = 0; dx < rw; dx++) {\n let sx = (dx + 0.5) * fx - 0.5\n if (sx < 0) sx = 0\n const x0 = Math.min(srcW - 1, Math.floor(sx))\n const x1 = Math.min(srcW - 1, x0 + 1)\n const wx = sx - x0\n const i00 = at(x0, y0), i01 = at(x1, y0), i10 = at(x0, y1), i11 = at(x1, y1)\n const o = (dy * rw + dx) * 3\n if (keep && rot === 0 && (sx < keep.x0 || sx >= keep.x1 || sy < keep.y0 || sy >= keep.y1)) {\n rgb[o] = rgb[o + 1] = rgb[o + 2] = keep.bg\n continue\n }\n for (let c = 0; c < 3; c++) {\n const top = rgba[i00 + c] * (1 - wx) + rgba[i01 + c] * wx\n const bottom = rgba[i10 + c] * (1 - wx) + rgba[i11 + c] * wx\n rgb[o + c] = Math.round(top * (1 - wy) + bottom * wy)\n }\n }\n }\n return { rgb, w: rw }\n}\n\n\n/** Split a detected line at ruled cell boundaries before recognizing its text. */\nexport function splitBoxAtCellRules(b: Box, rules: Array<{ x1: number; y1: number; y2: number; thicknessPx: number }>): Box[] {\n const cuts = rules.filter(r => r.x1 > b.x + b.h && r.x1 < b.x + b.w - b.h\n && r.y1 <= b.y + b.h * 0.25 && r.y2 >= b.y + b.h * 0.75).sort((a, c) => a.x1 - c.x1)\n if (!cuts.length) return [b]\n const out: Box[] = []\n let left = b.x\n for (const r of cuts) {\n const edge = Math.floor(r.x1 - r.thicknessPx / 2 - 1)\n if (edge - left < b.h) continue\n out.push({ ...b, x: left, w: edge - left })\n left = Math.ceil(r.x1 + r.thicknessPx / 2 + 1)\n }\n if (left < b.x + b.w) out.push({ ...b, x: left, w: b.x + b.w - left })\n return out\n}\n","/**\n * 스캔 기울기 보정 — 투영 프로파일 방식.\n *\n * 스캔·사진 입력은 0.5~3° 기울기가 흔하다. det 은 축정렬 박스(connected-component bbox)\n * 라 기울어진 줄은 박스가 위아래 줄을 물고, 괘선 감지(ruling-lines.ts)는 축정렬 런렝스라\n * 기울면 표 괘선이 통째로 사라진다. 인식 전에 페이지를 바로 세운다.\n *\n * 추정: 페이지를 긴 변 ~1000px 로 줄여 잉크 픽셀을 모으고, 후보 각 θ 마다\n * y' = y·cosθ + x·sinθ 의 행 히스토그램 제곱합(줄이 수평일 때 가장 뾰족)을 비교 —\n * ±MAX 를 0.2° 간격으로 훑고 최댓값 주변을 0.02° 간격으로 다듬는다.\n * 적용 조건: |θ| ≥ MIN_APPLY_DEG 이고 0° 대비 뾰족함이 뚜렷할 때만 (클린 렌더는 무보정).\n */\n\n/** 추정 범위 (°) — 이보다 큰 회전은 기울기가 아니라 방향 문제 (범위 밖) */\nconst MAX_SKEW_DEG = 5\n/** 이보다 작은 기울기는 보정하지 않음 — 리샘플 흐림이 이득보다 크다 */\nconst MIN_APPLY_DEG = 0.25\n/** 0° 대비 투영 뾰족함 비율 하한 — 글자 적은 페이지·도형의 우연한 봉우리 거부 */\nconst MIN_GAIN = 1.03\nconst TARGET_LONG = 1000\n\nexport interface SkewEstimate {\n /** 페이지 기울기 (°, 양수 = 시계 방향으로 기움 → 같은 각만큼 반시계로 돌리면 바로 섬) */\n angle: number\n /** 최적각 투영 점수 / 0° 점수 */\n gain: number\n}\n\n/** 기울기 추정 (적용 여부와 무관한 원값) */\nexport function estimateSkew(rgba: Uint8Array, width: number, height: number): SkewEstimate {\n const step = Math.max(1, Math.ceil(Math.max(width, height) / TARGET_LONG))\n const sw = Math.floor(width / step), sh = Math.floor(height / step)\n // 축소 휘도 + 전역 문턱 (잉크 = 어두운 픽셀)\n const xs: number[] = [], ys: number[] = []\n for (let y = 0; y < sh; y++) {\n for (let x = 0; x < sw; x++) {\n const i = ((y * step) * width + x * step) * 4\n const l = (rgba[i] * 77 + rgba[i + 1] * 150 + rgba[i + 2] * 29) >> 8\n if (l < 128) { xs.push(x); ys.push(y) }\n }\n }\n if (xs.length < 200) return { angle: 0, gain: 1 }\n const diag = Math.ceil(Math.hypot(sw, sh))\n const hist = new Float64Array(2 * diag + 2)\n const score = (deg: number): number => {\n const t = (deg * Math.PI) / 180\n const c = Math.cos(t), s = Math.sin(t)\n hist.fill(0)\n for (let k = 0; k < xs.length; k++) hist[Math.round(ys[k] * c + xs[k] * s) + diag]++\n let sum = 0\n for (let i = 0; i < hist.length; i++) sum += hist[i] * hist[i]\n return sum\n }\n const s0 = score(0)\n let best = 0, bestS = s0\n for (let d = -MAX_SKEW_DEG; d <= MAX_SKEW_DEG + 1e-9; d += 0.2) {\n const v = score(d)\n if (v > bestS) { bestS = v; best = d }\n }\n const coarse = best\n for (let d = coarse - 0.2; d <= coarse + 0.2 + 1e-9; d += 0.02) {\n const v = score(d)\n if (v > bestS) { bestS = v; best = d }\n }\n // 투영 회전각 θ 에서 줄이 수평 → 페이지는 −θ 만큼 기운 것\n return { angle: +(-best).toFixed(2), gain: s0 > 0 ? bestS / s0 : 1 }\n}\n\n/**\n * 필요하면 페이지를 바로 세운 RGBA 를 돌려준다 (같은 크기, 중심 회전, 바깥은 흰색).\n * 보정하지 않으면 입력 그대로 + angle 0.\n */\nexport function deskewPage(rgba: Uint8Array, width: number, height: number): { rgba: Uint8Array; angle: number } {\n const est = estimateSkew(rgba, width, height)\n if (Math.abs(est.angle) < MIN_APPLY_DEG || est.gain < MIN_GAIN) return { rgba, angle: 0 }\n return { rgba: rotateRgba(rgba, width, height, est.angle), angle: est.angle }\n}\n\n/**\n * 중심 기준 회전 (deg, 양수 = 화면상 반시계), 같은 캔버스 크기, bilinear, 바깥 흰색.\n * 출력 픽셀 (x,y) 는 입력의 역회전 위치에서 표본.\n */\nexport function rotateRgba(rgba: Uint8Array, width: number, height: number, deg: number): Uint8Array {\n const out = new Uint8Array(rgba.length)\n const t = (deg * Math.PI) / 180\n const c = Math.cos(t), s = Math.sin(t)\n const cx = (width - 1) / 2, cy = (height - 1) / 2\n for (let y = 0; y < height; y++) {\n const dy = y - cy\n for (let x = 0; x < width; x++) {\n const dx = x - cx\n // 화면 좌표(y 아래)에서 반시계 deg 회전의 역변환\n const sx = c * dx - s * dy + cx\n const sy = s * dx + c * dy + cy\n const o = (y * width + x) * 4\n const x0 = Math.floor(sx), y0 = Math.floor(sy)\n if (x0 < 0 || y0 < 0 || x0 >= width - 1 || y0 >= height - 1) {\n out[o] = 255; out[o + 1] = 255; out[o + 2] = 255; out[o + 3] = 255\n continue\n }\n const wx = sx - x0, wy = sy - y0\n const i00 = (y0 * width + x0) * 4, i01 = i00 + 4, i10 = i00 + width * 4, i11 = i10 + 4\n for (let ch = 0; ch < 4; ch++) {\n const top = rgba[i00 + ch] * (1 - wx) + rgba[i01 + ch] * wx\n const bot = rgba[i10 + ch] * (1 - wx) + rgba[i11 + ch] * wx\n out[o + ch] = Math.round(top * (1 - wy) + bot * wy)\n }\n }\n }\n return out\n}\n","/**\n * PDF OCR 브릿지 — 대상 페이지를 pdfium 으로 래스터해 내장 엔진 또는\n * 사용자 OcrProvider 로 인식하고, 페이지별 IRBlock[] 을 돌려준다.\n *\n * - 내장 엔진: OcrItem 좌표를 PDF 포인트로 환산해 기존 블록 파이프라인\n * (extractPageBlocksWithLines — xy-cut 읽기순서·클러스터 표 감지)에 태운다.\n * 스캔 문서도 표 구조 복원이 가능한 이유.\n * - 사용자 프로바이더: 페이지 PNG → 텍스트 → 페이지당 paragraph 블록 (종전 계약).\n *\n * 에러 계약 (v4.1.0 리뷰 F6/F7):\n * - 환경 오류(의존성·모델 미설치, PDF 오픈 실패)는 throw — 호출자가 NEEDS_OCR 폴백.\n * - 페이지 단위 실패는 본문 오염 대신 warnings(OCR_FAILED) 로 기록하고 계속.\n */\n\nimport type { IRBlock, OcrLine, OcrPageLines, OcrProvider, ParseOptions, ParseWarning } from \"../types.js\"\nimport type { NormItem } from \"../pdf/text-line.js\"\nimport type { LineSegment } from \"../pdf/line-types.js\"\nimport { extractPageBlocksWithLines } from \"../pdf/page-blocks.js\"\nimport { UNRULED_REGION_TABLES } from \"../pdf/ocr-region-merge.js\"\nimport { detectRulingLines, rulingToPdfLines } from \"./ruling-lines.js\"\nimport { DEFAULT_OCR_TUNING, getOcrEngine, type OcrItem, type OcrPageStats, type OcrTuning } from \"./engine.js\"\nimport { deskewPage } from \"./deskew.js\"\nimport { copyOcrTextGaps } from \"./text-gaps.js\"\nimport { DEFAULT_OCR_MODELS, ensureOcrModelsFor, resolveOcrModels, type OcrModelSelection } from \"./models.js\"\nimport { validateOcrOptions } from \"./options.js\"\nimport { KordocError, OPTIONAL_DEP_INSTALL_HINT } from \"../utils.js\"\n\n/** OCR 렌더 스케일 (72dpi × 3 = 216dpi) — 10pt 본문이 rec 입력 높이(48px)에 근접 */\nconst OCR_RENDER_SCALE = 3\n/**\n * OCR 래스터 픽셀 상한 (쪽·이미지 공통) — 이보다 큰 쪽은 배율을 낮춰 이 안에 맞춘다. 쪽 크기를 가리지 않고 ×3 으로 그리면 628B PDF\n * (4000pt 정사각 쪽)가 12000² 래스터에 사본·괘선 마스크까지 2.38GB 를 먹었다(16000² PNG 1.84GB). A2(1191×1684pt)도 216dpi 로 18MP 라\n * 공문서 판형은 줄지 않는다\n */\nexport const MAX_OCR_PIXELS = 24_000_000\n/** 페이지 하나당 OCR 타임아웃 — det+rec 수십 라인 기준 넉넉히 */\nconst PAGE_TIMEOUT_MS = 120_000\n\nexport type OcrMode = \"builtin\" | OcrProvider\n\n/** 내장 엔진 설정 — 모델 선택·튜닝·줄 내보내기 (ParseOptions 의 ocrDet·ocrDetLongSide·ocrRec·ocrRecDict·ocrLines) */\nexport interface BuiltinOcrConfig {\n models: OcrModelSelection\n tuning: Readonly<OcrTuning>\n /** 줄 내보내기 — undefined 면 끔(종전과 같은 일만 한다) */\n lines?: \"kept\" | \"all\"\n}\n\nexport const DEFAULT_BUILTIN_OCR: Readonly<BuiltinOcrConfig> = Object.freeze({ models: DEFAULT_OCR_MODELS, tuning: DEFAULT_OCR_TUNING })\n\n/** ParseOptions → 내장 엔진 설정. 옵션이 없으면 기본 설정 객체 그대로. 잘못된 옵션은 throw — parse() 는 먼저 걸러 실패 결과로 낸다 */\nexport function builtinOcrConfig(o: ParseOptions | undefined): Readonly<BuiltinOcrConfig> {\n if (!o || (o.ocrDet === undefined && o.ocrDetLongSide === undefined && o.ocrRec === undefined && o.ocrRecDict === undefined && !o.ocrLines)) return DEFAULT_BUILTIN_OCR\n const bad = validateOcrOptions(o)\n if (bad) throw new KordocError(bad)\n return {\n models: resolveOcrModels(o),\n tuning: o.ocrDetLongSide === undefined ? DEFAULT_OCR_TUNING : { ...DEFAULT_OCR_TUNING, detLongSide: o.ocrDetLongSide },\n lines: o.ocrLines === \"all\" ? \"all\" : o.ocrLines ? \"kept\" : undefined,\n }\n}\n\n/** 다운로드를 알린 모델 — 진행 콜백은 받은 조각마다 오고 첫 콜백도 downloaded > 0 이라, 종전 조건(downloaded === 0)으로는 안내가 나가지 않았다 */\nconst announced = new Set<string>()\n\n/** 내장 모델 다운로드 안내 (stderr, 모델마다 한 번) */\nexport function reportModelDownload(p: { phase: string; downloaded: number; spec: { name: string; filename: string; sizeMb: number } }): void {\n if (p.phase !== \"download\" || announced.has(p.spec.filename)) return\n announced.add(p.spec.filename)\n process.stderr.write(`[kordoc-ocr] ${p.spec.name} 다운로드 중 (~${p.spec.sizeMb}MB)...\\n`)\n}\n\n/** OcrItem(래스터 px) → 결과 줄 */\nexport function toOcrLine(it: OcrItem, dropped = false): OcrLine {\n return { text: it.text, box: { x: it.x, y: it.y, w: it.w, h: it.h }, confidence: it.confidence, ...(dropped ? { dropped: true as const } : {}) }\n}\n\n/** 쪽 줄 묶음 — 블록에 쓴 줄 + (all) 버린 줄을 위→아래, 왼→오른 순으로 */\nexport function pageLines(pageNumber: number, width: number, height: number, scale: number, deskewAngle: number,\n kept: OcrItem[], dropped: OcrItem[] = [], regionsOnly = false): OcrPageLines {\n const lines = [...kept.map(it => toOcrLine(it)), ...dropped.map(it => toOcrLine(it, true))]\n .sort((a, b) => (a.box.y - b.box.y) || (a.box.x - b.box.x))\n return { pageNumber, width, height, scale, deskewAngle, ...(regionsOnly ? { regionsOnly: true as const } : {}), lines }\n}\n\n/** pdfjs 연산자 목록 (그래픽만 쓴다 — 괘선·채움·그림 영역) */\nexport type PageOps = { fnArray: Uint32Array | number[]; argsArray: unknown[][] }\n\n/**\n * 대상 페이지들을 OCR 해 페이지별 블록 맵 반환.\n * @param buffer 원본 PDF (pdfjs 가 detach 하기 전에 clone 해 둔 것)\n * @param targets 1-based 페이지 번호 집합\n * @param vectorOps 벡터 글자 쪽(글자를 곡선으로 그린 쪽)의 그래픽(vector-glyphs.ts ocrVectorOps) — 그 쪽은 표 구조를\n * 래스터 괘선 감지 대신 이것(실제 괘선)으로 복원한다. 스캔 쪽은 벡터가 없으니 없다\n */\nexport async function runPdfOcr(\n buffer: ArrayBuffer,\n targets: Set<number>,\n mode: OcrMode,\n warnings: ParseWarning[],\n onProgress?: (current: number, total: number) => void,\n detectTables = true,\n vectorOps?: Map<number, PageOps>,\n imageRegions?: Map<number, Array<{ x1: number; y1: number; x2: number; y2: number }>>,\n config: Readonly<BuiltinOcrConfig> = DEFAULT_BUILTIN_OCR,\n /** config.lines 일 때 쪽별 OCR 줄을 여기에 모은다 */\n linesOut?: Map<number, OcrPageLines>,\n): Promise<Map<number, IRBlock[]>> {\n const result = new Map<number, IRBlock[]>()\n if (targets.size === 0) return result\n\n // 환경 준비 — 실패는 그대로 throw (호출자가 NEEDS_OCR 폴백)\n const pdfiumMod = await tryImport<typeof import(\"@hyzyla/pdfium\")>(\n \"@hyzyla/pdfium\",\n () => import(\"@hyzyla/pdfium\"),\n )\n if (mode === \"builtin\") await ensureOcrModelsFor(config.models, reportModelDownload)\n const engine = mode === \"builtin\" ? await getOcrEngine(config.models) : null\n\n const pdfium = await pdfiumMod.PDFiumLibrary.init()\n const doc = await pdfium.loadDocument(new Uint8Array(buffer))\n try {\n let done = 0\n // 대상 쪽만 연다 — doc.pages() 는 모든 쪽을 불러오고, 쪽은 render() 가 끝에서만 닫아 대상 밖 쪽이 문서를 닫을 때까지 남았다\n // (changwon 328쪽 문서의 한 쪽 OCR: 6ms·26MB → 전 쪽 순회 970ms·330MB). pdfium 쪽 번호는 0-based — 대외 계약(1-based)으로 환산\n const count = doc.getPageCount()\n for (const pageNo of [...targets].filter(p => p >= 1 && p <= count).sort((a, b) => a - b)) {\n const page = doc.getPage(pageNo - 1)\n onProgress?.(++done, targets.size)\n try {\n const { blocks, lines } = await withTimeout(\n ocrOnePage(page, pageNo, mode, engine, warnings, detectTables, vectorOps?.get(pageNo), imageRegions?.get(pageNo), config),\n PAGE_TIMEOUT_MS,\n `OCR 페이지 ${pageNo} 타임아웃 (${PAGE_TIMEOUT_MS / 1000}초)`,\n )\n result.set(pageNo, blocks)\n // 줄은 블록과 함께 받은 쪽만 — 타임아웃으로 버린 쪽이 뒤늦게 끝나도 섞이지 않는다\n if (lines) linesOut?.set(pageNo, lines)\n } catch (e) {\n warnings.push({\n page: pageNo,\n message: `페이지 ${pageNo} OCR 실패: ${e instanceof Error ? e.message : String(e)}`,\n code: \"OCR_FAILED\",\n })\n }\n }\n } finally {\n doc.destroy()\n pdfium.destroy()\n }\n return result\n}\n\nexport async function ocrOnePage(\n page: import(\"@hyzyla/pdfium\").PDFiumPage,\n pageNo: number,\n mode: OcrMode,\n engine: Awaited<ReturnType<typeof getOcrEngine>> | null,\n warnings: ParseWarning[],\n detectTables: boolean,\n vectorOps?: PageOps,\n regions?: Array<{ x1: number; y1: number; x2: number; y2: number }>,\n config: Readonly<BuiltinOcrConfig> = DEFAULT_BUILTIN_OCR,\n): Promise<{ blocks: IRBlock[]; lines?: OcrPageLines }> {\n const { originalWidth: pdfW, originalHeight: pdfH } = page.getOriginalSize()\n const renderScale = Math.min(OCR_RENDER_SCALE, Math.sqrt(MAX_OCR_PIXELS / Math.max(1, pdfW * pdfH)))\n if (renderScale < OCR_RENDER_SCALE) {\n warnings.push({ page: pageNo, code: \"PARTIAL_PARSE\", message: `OCR 래스터 픽셀 상한으로 렌더 해상도를 축소했습니다 (작은 글자 인식 결손 가능)` })\n }\n // 그림 영역만 읽는 쪽은 두 배로 한 번 그려 영역 다시 읽기(closer)에 쓰고, 쪽 배율 래스터는 2×2 평균으로 줄여 만든다\n // (pdfium 은 같은 쪽을 두 번 그리면 wasm 서명 오류가 난다)\n const closer = regions && mode === \"builtin\" && renderScale * 2 <= Math.sqrt(MAX_OCR_PIXELS / Math.max(1, pdfW * pdfH))\n const rendered = await page.render({\n scale: closer ? renderScale * 2 : renderScale,\n render: async ({ data }) => data,\n })\n const hiRgba = closer ? bgraToRgba(rendered.data) : null\n const { rgba, width: rw, height: rh } = hiRgba\n ? halve(hiRgba, rendered.width, rendered.height)\n : { rgba: bgraToRgba(rendered.data), width: rendered.width, height: rendered.height }\n\n if (mode === \"builtin\") {\n // 스캔 기울기 보정 — 인식과 괘선 감지가 같은(바로 선) 래스터를 본다. 클린 렌더는 무보정.\n // 벡터 글자·그림 영역 쪽은 PDF 원본 좌표를 유지한다. 회전하면 벡터 괘선·영역 경계 및\n // 원본 래스터를 잘라 읽는 closerReads와 좌표계가 달라져 줄 선택·병합이 어긋난다.\n const { rgba: upright, angle } = vectorOps || regions ? { rgba, angle: 0 } : deskewPage(rgba, rw, rh)\n const stats: OcrPageStats = { droppedLowConf: 0, ...(config.lines === \"all\" ? { dropped: [] } : {}) }\n const ruling = vectorOps ? undefined : detectRulingLines(upright, rw, rh, rh / pdfH)\n const tuning = regions ? regionTuning(config.tuning) : config.tuning\n const items = await engine!.recognizePage(upright, rw, rh, stats, tuning, ruling?.cellDividers)\n if (stats.droppedLowConf > 0) {\n warnings.push({\n page: pageNo,\n message: `페이지 ${pageNo}: 저신뢰 OCR 라인 ${stats.droppedLowConf}개 폐기 (인식 결손 가능)`,\n code: \"OCR_LOW_CONF\",\n })\n }\n if (stats.truncatedBoxes) {\n warnings.push({ page: pageNo, message: `페이지 ${pageNo}: OCR 검출 상자가 너무 많아 ${stats.truncatedBoxes}개는 인식하지 않음 (일부 글 결손)`, code: \"PARTIAL_PARSE\" })\n }\n const scale = rh / pdfH\n /** 블록과 함께 돌려줄 쪽 줄 (config.lines 일 때만) */\n const withLines = (blocks: IRBlock[], kept: OcrItem[], regionsOnly = false) =>\n config.lines ? { blocks, lines: pageLines(pageNo, rw, rh, scale, angle, kept, stats.dropped, regionsOnly) } : { blocks }\n // 텍스트층이 있는 쪽의 그림 영역만 읽을 때는 영역마다 그 안 글줄로 따로 블록을 만든다 — 쪽 전체로 묶으면 본문 줄과 그림 글이\n // 한 문단으로 합쳐져 영역 판정(mergeOcrImageRegions)에서 빠진다\n if (regions) {\n const inside = (it: OcrItem, r: { x1: number; y1: number; x2: number; y2: number }) => {\n const cx = (it.x + it.w / 2) / scale, cy = pdfH - (it.y + it.h / 2) / scale\n return cx >= r.x1 && cx <= r.x2 && cy >= r.y1 && cy <= r.y2\n }\n const reads = hiRgba ? await closerReads(hiRgba, rw * 2, rh * 2, pdfH, scale * 2, regions, engine!, tuning) : []\n /** 영역마다 쓴 줄 — 겹친 두 영역이 같은 줄을 쓰면 한 번만 */\n const used = new Set<OcrItem>()\n /** 두 배 래스터로 다시 읽은 쪽을 쓴 영역 — 그 영역의 쪽 배율 인식(버린 줄 포함)은 쓰지 않았다 */\n const reread = new Set<number>()\n const regionBlocks = regions.flatMap((r, k) => {\n let own = items.filter(it => inside(it, r))\n // 그림 속 글은 작다(차트 눈금·범례 6~8pt) — 영역만 두 배로 다시 읽어 글자 수로 가중한 평균 신뢰도가 높은 쪽을 쓴다\n const near = reads[k]\n if (near?.length && meanConfidence(near) > meanConfidence(own)) { own = near; reread.add(k) }\n if (!own.length) return []\n for (const it of own) used.add(it)\n const blocks = ocrItemsToBlocks(own, pageNo, pdfW, pdfH, scale, ruling && rulingToPdfLines(ruling, scale, pdfH), detectTables, undefined, true)\n // 영역 안 세로 괘선이 둘 미만이면 그 영역의 OCR 표는 글 정렬만으로 묶은 것이다 — 그림 영역 병합이 표로 받지 않는다\n const x1 = r.x1 * scale - 2, x2 = r.x2 * scale + 2, y1 = (pdfH - r.y2) * scale - 2, y2 = (pdfH - r.y1) * scale + 2\n const columnRules = ruling?.verticals.filter(s => Math.min(s.x1, s.x2) >= x1 && Math.max(s.x1, s.x2) <= x2 && Math.min(s.y1, s.y2) >= y1 && Math.max(s.y1, s.y2) <= y2).length ?? 0\n if (columnRules < 2) for (const b of blocks) if (b.type === \"table\") UNRULED_REGION_TABLES.add(b)\n return blocks\n })\n // 그림 영역 쪽의 버린 줄은 쪽 전체 인식에서 나온 것이라 영역 밖 줄도 섞인다 — 쪽 배율 인식을 쓴 영역 안 것만\n if (stats.dropped) stats.dropped = stats.dropped.filter(it => regions.some((r, k) => !reread.has(k) && inside(it, r)))\n return withLines(regionBlocks, [...used], true)\n }\n // 벡터 글자 쪽: 표 구조는 그 쪽의 실제 괘선으로 (rhwp cairo 13쌍 46표 exact: 래스터 괘선 14 → 실제 괘선 20)\n if (vectorOps) return withLines(ocrItemsToBlocks(items, pageNo, pdfW, pdfH, scale, undefined, detectTables, vectorOps), items)\n // 래스터에서 표 괘선 감지 — 스캔본 병합셀 서식도 선 기반 표 파이프라인을 탄다\n const extraLines = rulingToPdfLines(ruling!, scale, pdfH)\n return withLines(ocrItemsToBlocks(items, pageNo, pdfW, pdfH, scale, extraLines, detectTables), items)\n }\n\n // 사용자 프로바이더 — PNG 인코딩 후 호출, 페이지당 paragraph (종전 계약)\n type SharpPngFactory = (\n input: Uint8Array,\n opts: { raw: { width: number; height: number; channels: number } },\n ) => { png(): { toBuffer(): Promise<Buffer> } }\n const sharpModRaw = await tryImport<{ default?: SharpPngFactory } & SharpPngFactory>(\n \"sharp\",\n () => import(\"sharp\") as unknown as Promise<{ default?: SharpPngFactory } & SharpPngFactory>,\n )\n const sharpAny = sharpModRaw as { default?: SharpPngFactory } | SharpPngFactory\n const sharp: SharpPngFactory =\n typeof sharpAny === \"function\" ? sharpAny : (sharpAny.default ?? (sharpAny as unknown as SharpPngFactory))\n const png = await sharp(rgba, { raw: { width: rw, height: rh, channels: 4 } })\n .png()\n .toBuffer()\n\n const text = await mode(new Uint8Array(png), pageNo, \"image/png\")\n if (!text.trim()) {\n warnings.push({ page: pageNo, message: `페이지 ${pageNo} OCR 결과 없음`, code: \"OCR_FAILED\" })\n return { blocks: [] }\n }\n return { blocks: [{ type: \"paragraph\", text: text.trim(), pageNumber: pageNo }] }\n}\n\n/** 2×2 평균 축소 (RGBA) */\nfunction halve(src: Uint8Array, w: number, h: number): { rgba: Uint8Array; width: number; height: number } {\n const W = w >> 1, H = h >> 1\n const out = new Uint8Array(W * H * 4)\n for (let y = 0; y < H; y++) for (let x = 0; x < W; x++) {\n const a = ((2 * y) * w + 2 * x) * 4, b = a + w * 4, o = (y * W + x) * 4\n for (let c = 0; c < 4; c++) out[o + c] = (src[a + c] + src[a + 4 + c] + src[b + c] + src[b + 4 + c] + 2) >> 2\n }\n return { rgba: out, width: W, height: H }\n}\n\n/** 그림 영역만 읽을 때 — 끝자락 지우기를 끈다 (engine OcrTuning.trimEdges 주석) */\nconst REGION_TUNING: Readonly<OcrTuning> = Object.freeze({ ...DEFAULT_OCR_TUNING, trimEdges: false })\nconst regionTuning = (t: Readonly<OcrTuning>): Readonly<OcrTuning> => t === DEFAULT_OCR_TUNING ? REGION_TUNING : { ...t, trimEdges: false }\n\nfunction meanConfidence(items: OcrItem[]): number {\n let n = 0, sum = 0\n for (const it of items) { const len = [...it.text].length; n += len; sum += it.confidence * len }\n return n ? sum / n : 0\n}\n\n/** 그림 영역마다 두 배 래스터에서 잘라 다시 인식한 글줄 — 좌표는 쪽 배율 래스터 기준으로 되돌린다 */\nasync function closerReads(\n rgba: Uint8Array, rw: number, rh: number, pdfH: number, hi: number,\n regions: Array<{ x1: number; y1: number; x2: number; y2: number }>,\n engine: NonNullable<Awaited<ReturnType<typeof getOcrEngine>>>,\n tuning: Readonly<OcrTuning> = REGION_TUNING,\n): Promise<Array<OcrItem[] | null>> {\n const out: Array<OcrItem[] | null> = []\n for (const r of regions) {\n const x0 = Math.max(0, Math.floor(r.x1 * hi)), y0 = Math.max(0, Math.floor((pdfH - r.y2) * hi))\n const cw = Math.min(rw, Math.ceil(r.x2 * hi)) - x0, ch = Math.min(rh, Math.ceil((pdfH - r.y1) * hi)) - y0\n if (cw < 16 || ch < 16) { out.push(null); continue }\n const crop = new Uint8Array(cw * ch * 4)\n for (let y = 0; y < ch; y++) crop.set(rgba.subarray(((y0 + y) * rw + x0) * 4, ((y0 + y) * rw + x0 + cw) * 4), y * cw * 4)\n const items = await engine.recognizePage(crop, cw, ch, undefined, tuning)\n out.push(items.map(it => {\n const placed = { ...it, x: (it.x + x0) / 2, y: (it.y + y0) / 2, w: it.w / 2, h: it.h / 2 }\n copyOcrTextGaps(it, placed, 0.5, x0 / 2)\n return placed\n }))\n }\n return out\n}\n\n/**\n * OcrItem(렌더 픽셀, top-left origin) → NormItem(PDF pt, bottom-up baseline)\n * 으로 환산해 기존 블록 파이프라인에 태운다. 괘선은 래스터 감지 결과(extraLines)로\n * 공급 — 없으면 클러스터 감지기 몫이다. 벡터 글자 쪽은 그 쪽 그래픽 연산자 목록(opList)을\n * 넘겨 실제 괘선을 쓴다.\n * (이미지 직접 입력 경로 image-ocr.ts 와 공유)\n */\n/**\n * OCR 박스를 줄로 묶는다(박스 번호 묶음). 같은 줄 조각도 글자 크기(큰 대표 줄)·숫자 하강부·기울어진 감열지 때문에 박스 아래 끝이\n * 몇 pt 씩 어긋나, 박스마다 아래 끝을 기준선으로 넘기면 블록 파이프라인의 줄 묶음(3pt)이 한 줄을 여럿으로 쪼개고 위쪽 y 순으로\n * 뒤집는다(#141 영수증: \"탄현점 / 이마트\", 품목 바코드·단가·금액이 다른 행). 중심 y 차이 ≤ 작은 박스 높이의 절반이면 한 줄,\n * 가로로 크게 겹치는 박스는 같은 줄에 넣지 않는다(큰 로고 박스가 위아래 두 줄을 잇지 않게).\n * 한 줄 조각은 높이가 엇비슷하다 — 줄 높이 중앙값과 두 배 넘게 다른 박스는 인식 신뢰도 0.8 미만 박스(아이콘·로고·QR 잡음)가 낄 때\n * 묶지 않는다. 높이는 줄 높이 중앙값과 잰다(줄의 작은 숫자 박스 하나와 재면 두 행에 걸친 칸 박스가 제 행을 놓친다, changwon-plan2026\n * \"3/8\"). 신뢰도가 높으면 높이 차가 커도 같은 줄 — 기울어진 스캔의 긴 글줄 박스는 기울기만큼 키가 크고 체크 표 \"√\" 옆 라벨·두 줄이 한\n * 박스로 붙은 칸도 2~2.7배다(열화 서식 OCR 다섯 쪽 CER 상승). 그림 영역에서는 높이의 8배 넘게 떨어진 조각은 세로 중심이 작은 박스\n * 높이의 0.35 안일 때만 같은 줄(차트 범례와 먼 축 눈금 — 쪽 전체 OCR 의 서식 먼 칸은 쪽이 기운 만큼 어긋난다). 긴 글줄(폭이 높이의\n * 7배 이상 — 서식 라벨 \"주민등록번호\" 6배는 아니다, 숫자가 30% 넘는 금액 칸도 아니다 — 예산 표 행의 항목명과 \"경정4,200,000원\")은 줄 안\n * 가장 가까운 긴 글줄과 높이의 5배 넘게 떨어지면 묶지 않는다(범례 라벨 사이 3~4배는 한 줄). 짧은 조각(숫자·라벨 칸)은 멀어도 묶는다\n * (영수증 금액 칸).\n * 두 단 글의 좌우 줄은 높이가 엇비슷해도 다른 줄이다(ODL 141 두 단 카드 인포그래픽: 좌우 카드 줄 틈 7~60배 — 4.21.3 에서 한 줄로\n * 묶여 단 분리가 깨지고 가짜 표가 됐다, NID 0.96 → 0.44).\n */\n/** 긴 글줄 상자 — 폭이 높이의 7배 이상이고 숫자가 글자의 30% 미만(금액 칸 \"경정4,200,000원\"·\"210mm×297mm\" 은 표 칸이다) */\nconst proseBox = (b: OcrItem) => {\n const t = b.text.replace(/\\s/g, \"\")\n return b.w >= b.h * 7 && (t.match(/\\d/g)?.length ?? 0) < t.length * 0.3\n}\n\nexport function groupOcrLines(items: OcrItem[], figureRegion = false): number[][] {\n const order = items.map((_, i) => i).sort((a, b) => (items[a].y + items[a].h / 2) - (items[b].y + items[b].h / 2))\n const accepts = (l: number[], i: number): boolean => {\n const it = items[i], c = it.y + it.h / 2\n const lc = l.reduce((s, j) => s + items[j].y + items[j].h / 2, 0) / l.length\n const minH = Math.min(it.h, ...l.map(j => items[j].h))\n if (Math.abs(c - lc) > minH / 2) return false\n const hs = l.map(j => items[j].h).sort((a, b) => a - b), med = hs[hs.length >> 1]\n const weak = it.confidence < 0.8 || l.some(j => items[j].confidence < 0.8)\n if (weak && Math.max(it.h, med) > Math.min(it.h, med) * 2) return false\n let nearLong = 0, gap = Infinity\n for (const j of l) {\n const o = items[j]\n // 박스는 잉크 외곽에 여백이 붙어 이웃 낱말과 조금 겹친다(이마트·탄현점 32px) — 좁은 쪽 폭의 절반 넘게 겹칠 때만 다른 줄\n const ov = Math.min(it.x + it.w, o.x + o.w) - Math.max(it.x, o.x)\n if (ov >= Math.min(it.w, o.w) / 2) return false\n gap = Math.min(gap, Math.max(0, -ov))\n if (proseBox(it) && proseBox(o)) nearLong = Math.min(nearLong || Infinity, Math.max(0, -ov) / Math.min(it.h, o.h))\n }\n // 그림 영역에서 높이의 8배 넘게 떨어진 조각은 세로로 거의 같은 줄이어야 한다 — 영수증 먼 칸은 중심 어긋남이 작은 박스 높이의 0.22 안, 원그래프\n // 값과 범례(ODL 148 \"18.9%\"·\"Project Manager\")는 0.31, 차트 범례 라벨과 멀리 왼쪽 축 눈금은 0.37~0.47 이라 반쯤 어긋난 채 묶여\n // 기준선이 옮겨지고 읽기 순서가 바뀌었다(ODL 128·140·057)\n if (figureRegion && gap > 8 * minH && Math.abs(c - lc) > minH * 0.35) return false\n // 긴 글줄은 줄 안 가장 가까운 긴 글줄과 높이의 5배 안이어야 한다 — 사이에 짧은 상자(카드 번호)가 끼어도 다리가 되지 않는다\n return nearLong <= 5\n }\n const lines: number[][] = []\n for (const i of order) {\n const line = lines.find(l => accepts(l, i))\n if (line) line.push(i)\n else lines.push([i])\n }\n // 홀로 남은 박스는 줄이 다 선 뒤 한 번 더 넣어 본다 — 먼저 만난 줄에 먼 긴 글줄만 있어 갈린 범례 라벨(ODL 059 \"Construction | Waste\")\n for (let k = 0; k < lines.length; k++) {\n if (lines[k].length !== 1) continue\n const i = lines[k][0]\n const target = lines.find((l, m) => m !== k && l.length > 0 && accepts(l, i))\n if (target) { target.push(i); lines[k] = [] }\n }\n return lines.filter(l => l.length > 0)\n}\n\nexport function ocrItemsToBlocks(\n items: OcrItem[],\n pageNumber: number,\n pdfW: number,\n pdfH: number,\n scale: number,\n extraLines?: { horizontals: LineSegment[]; verticals: LineSegment[] },\n detectTables = true,\n opList: PageOps = { fnArray: [], argsArray: [] },\n figureRegion = false,\n): IRBlock[] {\n // 줄마다 아래 끝을 중앙값으로 맞추고, 겹친 이웃 박스는 겹친 구간 가운데서 갈라 낱말 경계(따로 검출된 상자)를 공백으로 남긴다\n const bottoms = items.map(it => it.y + it.h)\n const xs = items.map(it => ({ x: it.x, r: it.x + it.w, space: false }))\n for (const line of groupOcrLines(items, figureRegion)) {\n if (line.length < 2) continue\n const sorted = line.map(j => items[j].y + items[j].h).sort((a, b) => a - b)\n for (const j of line) bottoms[j] = sorted[Math.floor(sorted.length / 2)]\n const byX = [...line].sort((a, b) => items[a].x - items[b].x)\n for (let k = 1; k < byX.length; k++) {\n const L = xs[byX[k - 1]], R = xs[byX[k]]\n if (R.x >= L.r) continue\n const mid = (R.x + L.r) / 2, half = Math.min(items[byX[k - 1]].h, items[byX[k]].h) * 0.15\n L.r = Math.max(L.x + 1, mid - half)\n R.x = Math.min(R.r - 1, mid + half)\n R.space = true\n }\n }\n const norm: NormItem[] = items.map((it, i) => {\n // 그림 영역은 기준선을 줄 중앙값으로 옮겨도 박스 위 끝은 그대로 — 높이째 옮기면 키 큰 칸 박스가 윗행 칸과 더 겹쳐 그 칸에 붙었다(ODL 110\n // 그림 속 표 \"14\"·\"15\" → \"1415\"). 쪽 전체 OCR 은 높이째 옮긴다 — 위 끝을 지키자 스캔 쪽 셋의 CER 이 올랐다(topik 1.50 → 1.88%).\n // 글자 크기는 원래 박스 높이로 잰다\n const h = (figureRegion ? Math.max(1, bottoms[i] - it.y) : it.h) / scale\n const normalized: NormItem = {\n text: it.text,\n x: Math.round(xs[i].x / scale),\n // NormItem.y 는 pdfjs transform[5] = 베이스라인 (bottom-up) — 잉크 하단으로 근사\n y: Math.round(pdfH - bottoms[i] / scale),\n w: Math.round((xs[i].r - xs[i].x) / scale),\n h: Math.round(h),\n // 박스는 잉크 외곽(engine tightBoxes) — 한글 줄 잉크 높이 ≈ 0.9em\n fontSize: Math.max(1, Math.round(it.h / scale / 0.9)),\n fontName: \"ocr\",\n isHidden: false,\n ...(xs[i].space ? { hasSpaceBefore: true } : {}),\n }\n copyOcrTextGaps(it, normalized, 1 / scale)\n return normalized\n })\n return extractPageBlocksWithLines(norm, pageNumber, opList, pdfW, pdfH, extraLines, detectTables)\n}\n\nasync function tryImport<T>(name: string, loader: () => Promise<T>): Promise<T> {\n try {\n return await loader()\n } catch (e) {\n throw new Error(\n `OCR 을 사용하려면 optional dependency '${name}' 이 필요합니다. ` +\n `\\`npm install ${name}\\` 후 다시 실행하세요.${OPTIONAL_DEP_INSTALL_HINT} 원인: ${(e as Error).message}`,\n )\n }\n}\n\nasync function withTimeout<T>(promise: Promise<T>, ms: number, msg: string): Promise<T> {\n // 타임아웃 패배 후 원 promise 의 사후 reject 가 unhandled rejection 이 되지 않게 흡수\n promise.catch(() => {})\n let timer: NodeJS.Timeout | undefined\n try {\n return await Promise.race([\n promise,\n new Promise<never>((_, reject) => {\n timer = setTimeout(() => reject(new Error(msg)), ms)\n }),\n ])\n } finally {\n if (timer) clearTimeout(timer)\n }\n}\n\n/** pdfium BGRA → RGBA */\nfunction bgraToRgba(bgra: Uint8Array): Uint8Array {\n const out = new Uint8Array(bgra.length)\n for (let i = 0; i < bgra.length; i += 4) {\n out[i] = bgra[i + 2]\n out[i + 1] = bgra[i + 1]\n out[i + 2] = bgra[i]\n out[i + 3] = bgra[i + 3]\n }\n return out\n}\n"],"mappings":"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","names":["c","median","dashes","gaps","prev","why","tryImport"]}