kordoc 4.16.0 → 4.16.2

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 (145) hide show
  1. package/README.md +344 -1135
  2. package/dist/{-VQNLLLPE.js → -4G6MA4X6.js} +11 -11
  3. package/dist/chunk-3IB7LBHC.js +57 -0
  4. package/dist/chunk-3IB7LBHC.js.map +1 -0
  5. package/dist/{chunk-6LMKQM7I.js → chunk-4VODJKFL.js} +3 -3
  6. package/dist/{chunk-UDX7TSOC.js → chunk-5KS5PWJR.js} +2 -2
  7. package/dist/chunk-62W7QCEX.cjs +57 -0
  8. package/dist/chunk-62W7QCEX.cjs.map +1 -0
  9. package/dist/{chunk-NNYF2AGC.js → chunk-7KNGSQU7.js} +4 -4
  10. package/dist/chunk-7KNGSQU7.js.map +1 -0
  11. package/dist/{chunk-DYENR3PX.js → chunk-AUWGDP4G.js} +2 -2
  12. package/dist/{chunk-H6KMTYYZ.cjs → chunk-CEIATRDW.cjs} +3 -3
  13. package/dist/{chunk-H6KMTYYZ.cjs.map → chunk-CEIATRDW.cjs.map} +1 -1
  14. package/dist/{chunk-N6TPODYL.js → chunk-DES2LGFZ.js} +2 -2
  15. package/dist/{chunk-PQRP4NSF.js → chunk-F4NRLLY4.js} +2 -2
  16. package/dist/{chunk-B5NEGP65.cjs → chunk-FKUDAIJI.cjs} +692 -33
  17. package/dist/chunk-FKUDAIJI.cjs.map +1 -0
  18. package/dist/{chunk-XWA625AN.js → chunk-FSDJLAOW.js} +3 -1
  19. package/dist/chunk-FSDJLAOW.js.map +1 -0
  20. package/dist/{chunk-PIMB2YNO.js → chunk-GO5IFOUF.js} +2 -2
  21. package/dist/{chunk-476OYHGZ.js → chunk-IQGIT2VX.js} +37 -7
  22. package/dist/chunk-IQGIT2VX.js.map +1 -0
  23. package/dist/chunk-J3IF6KGW.js +59 -0
  24. package/dist/chunk-J3IF6KGW.js.map +1 -0
  25. package/dist/{chunk-C75WE52C.js → chunk-JELRWAZC.js} +2 -2
  26. package/dist/{chunk-46DZF3RK.js → chunk-JFLAM3MM.js} +2 -2
  27. package/dist/{chunk-A3QDRMVD.js → chunk-KIOQ4N37.js} +2 -2
  28. package/dist/{chunk-TGD7UFHD.js → chunk-LEIAEWZ7.js} +4 -4
  29. package/dist/chunk-LEIAEWZ7.js.map +1 -0
  30. package/dist/{chunk-GOQBON77.js → chunk-MLIS253L.js} +2 -2
  31. package/dist/{chunk-JB5X5NNL.js → chunk-OXLPVQYK.js} +680 -21
  32. package/dist/chunk-OXLPVQYK.js.map +1 -0
  33. package/dist/{chunk-RC4C7Q33.js → chunk-P6ZDXTV6.js} +2 -2
  34. package/dist/{chunk-CB5XDCSL.js → chunk-QOCS7W7R.js} +5034 -4569
  35. package/dist/chunk-QOCS7W7R.js.map +1 -0
  36. package/dist/{chunk-3JWMDL3S.js → chunk-S2PL227Q.js} +2 -2
  37. package/dist/{chunk-HIVK5V27.js → chunk-SFEQYKYJ.js} +133 -110
  38. package/dist/chunk-SFEQYKYJ.js.map +1 -0
  39. package/dist/{chunk-NWODPYBD.cjs → chunk-STM3ORY7.cjs} +2 -2
  40. package/dist/{chunk-NWODPYBD.cjs.map → chunk-STM3ORY7.cjs.map} +1 -1
  41. package/dist/{chunk-43FKGDDO.js → chunk-TXUKTOG2.js} +213 -30
  42. package/dist/chunk-TXUKTOG2.js.map +1 -0
  43. package/dist/{chunk-XHOZB2WP.js → chunk-UTP2WCHS.js} +681 -22
  44. package/dist/chunk-UTP2WCHS.js.map +1 -0
  45. package/dist/{chunk-MH6MAL64.cjs → chunk-WDQTFAXY.cjs} +217 -114
  46. package/dist/chunk-WDQTFAXY.cjs.map +1 -0
  47. package/dist/{chunk-DRY3L5KQ.cjs → chunk-XTFN5WKS.cjs} +7 -7
  48. package/dist/chunk-XTFN5WKS.cjs.map +1 -0
  49. package/dist/chunks-NUNIKS2J.js +10 -0
  50. package/dist/cli.js +33 -30
  51. package/dist/cli.js.map +1 -1
  52. package/dist/{formula-FNQYHMIY.js → formula-EPKLPDU7.js} +3 -3
  53. package/dist/{formula-5775PTP2.js → formula-NARUOXRX.js} +4 -4
  54. package/dist/{formula-VEFKYXPB.cjs → formula-WIMV5TUX.cjs} +9 -9
  55. package/dist/{formula-VEFKYXPB.cjs.map → formula-WIMV5TUX.cjs.map} +1 -1
  56. package/dist/{image-ocr-PYZU4TO7.js → image-ocr-3WJ4QH3T.js} +8 -8
  57. package/dist/{image-ocr-YA2MQBRP.js → image-ocr-5BUJQ4TK.js} +6 -6
  58. package/dist/{image-ocr-6JZR734G.cjs → image-ocr-AXWXR6JV.cjs} +15 -15
  59. package/dist/{image-ocr-6JZR734G.cjs.map → image-ocr-AXWXR6JV.cjs.map} +1 -1
  60. package/dist/index.cjs +1900 -1406
  61. package/dist/index.cjs.map +1 -1
  62. package/dist/index.d.cts +24 -6
  63. package/dist/index.d.ts +24 -6
  64. package/dist/index.js +1282 -788
  65. package/dist/index.js.map +1 -1
  66. package/dist/mcp.js +34 -28
  67. package/dist/mcp.js.map +1 -1
  68. package/dist/{model-bundle-O6J7ORVC.js → model-bundle-IMBW7D2Z.js} +5 -5
  69. package/dist/{models-YMOKZGCI.js → models-SY75IBOE.js} +5 -5
  70. package/dist/{parser-YAQV7FBO.cjs → parser-2LU4EVU3.cjs} +360 -219
  71. package/dist/parser-2LU4EVU3.cjs.map +1 -0
  72. package/dist/{parser-3B6I2XL7.js → parser-N6476XAC.js} +225 -84
  73. package/dist/parser-N6476XAC.js.map +1 -0
  74. package/dist/{parser-PXATL5DM.js → parser-ZQM3664Z.js} +222 -81
  75. package/dist/parser-ZQM3664Z.js.map +1 -0
  76. package/dist/pdf-ocr-6DEE23IP.js +15 -0
  77. package/dist/pdf-ocr-GHEJVPX2.cjs +16 -0
  78. package/dist/{pdf-ocr-T2JXQATA.cjs.map → pdf-ocr-GHEJVPX2.cjs.map} +1 -1
  79. package/dist/pdf-ocr-MQTWODVU.js +18 -0
  80. package/dist/{profile-io-ZTV2MKRT.js → profile-io-GJPKCYPT.js} +3 -3
  81. package/dist/{rasterize-MMUZ537K.js → rasterize-UOALJQDS.js} +3 -3
  82. package/dist/{redact-doc-S42XGV5D.js → redact-doc-22SYW3KH.js} +11 -11
  83. package/dist/{render-WCWPZKJ7.js → render-MJ4H3R23.js} +7 -7
  84. package/dist/seal-XWMRUTI4.js +10 -0
  85. package/dist/{setup-D35LKZCH.js → setup-2URYVUEO.js} +3 -3
  86. package/dist/visual-USPS4QRC.js +20 -0
  87. package/dist/{watch-AX64CIMN.js → watch-ALXZGTOB.js} +12 -12
  88. package/package.json +1 -1
  89. package/dist/chunk-43FKGDDO.js.map +0 -1
  90. package/dist/chunk-476OYHGZ.js.map +0 -1
  91. package/dist/chunk-B5NEGP65.cjs.map +0 -1
  92. package/dist/chunk-CB5XDCSL.js.map +0 -1
  93. package/dist/chunk-DRY3L5KQ.cjs.map +0 -1
  94. package/dist/chunk-HIVK5V27.js.map +0 -1
  95. package/dist/chunk-JB5X5NNL.js.map +0 -1
  96. package/dist/chunk-MH6MAL64.cjs.map +0 -1
  97. package/dist/chunk-NNYF2AGC.js.map +0 -1
  98. package/dist/chunk-QJERJZJ6.js +0 -11
  99. package/dist/chunk-QJERJZJ6.js.map +0 -1
  100. package/dist/chunk-TGD7UFHD.js.map +0 -1
  101. package/dist/chunk-UQ6TZDW6.js +0 -13
  102. package/dist/chunk-UQ6TZDW6.js.map +0 -1
  103. package/dist/chunk-XHOZB2WP.js.map +0 -1
  104. package/dist/chunk-XWA625AN.js.map +0 -1
  105. package/dist/chunk-ZY5K3LPM.cjs +0 -11
  106. package/dist/chunk-ZY5K3LPM.cjs.map +0 -1
  107. package/dist/chunks-JD25E5PM.js +0 -10
  108. package/dist/parser-3B6I2XL7.js.map +0 -1
  109. package/dist/parser-PXATL5DM.js.map +0 -1
  110. package/dist/parser-YAQV7FBO.cjs.map +0 -1
  111. package/dist/pdf-ocr-2TTO5PX2.js +0 -15
  112. package/dist/pdf-ocr-T2JXQATA.cjs +0 -16
  113. package/dist/pdf-ocr-WF2NQA6X.js +0 -18
  114. package/dist/seal-WHK4GOHR.js +0 -10
  115. package/dist/visual-WL4TDE5B.js +0 -20
  116. /package/dist/{-VQNLLLPE.js.map → -4G6MA4X6.js.map} +0 -0
  117. /package/dist/{chunk-6LMKQM7I.js.map → chunk-4VODJKFL.js.map} +0 -0
  118. /package/dist/{chunk-UDX7TSOC.js.map → chunk-5KS5PWJR.js.map} +0 -0
  119. /package/dist/{chunk-DYENR3PX.js.map → chunk-AUWGDP4G.js.map} +0 -0
  120. /package/dist/{chunk-N6TPODYL.js.map → chunk-DES2LGFZ.js.map} +0 -0
  121. /package/dist/{chunk-PQRP4NSF.js.map → chunk-F4NRLLY4.js.map} +0 -0
  122. /package/dist/{chunk-PIMB2YNO.js.map → chunk-GO5IFOUF.js.map} +0 -0
  123. /package/dist/{chunk-C75WE52C.js.map → chunk-JELRWAZC.js.map} +0 -0
  124. /package/dist/{chunk-46DZF3RK.js.map → chunk-JFLAM3MM.js.map} +0 -0
  125. /package/dist/{chunk-A3QDRMVD.js.map → chunk-KIOQ4N37.js.map} +0 -0
  126. /package/dist/{chunk-GOQBON77.js.map → chunk-MLIS253L.js.map} +0 -0
  127. /package/dist/{chunk-RC4C7Q33.js.map → chunk-P6ZDXTV6.js.map} +0 -0
  128. /package/dist/{chunk-3JWMDL3S.js.map → chunk-S2PL227Q.js.map} +0 -0
  129. /package/dist/{chunks-JD25E5PM.js.map → chunks-NUNIKS2J.js.map} +0 -0
  130. /package/dist/{formula-FNQYHMIY.js.map → formula-EPKLPDU7.js.map} +0 -0
  131. /package/dist/{formula-5775PTP2.js.map → formula-NARUOXRX.js.map} +0 -0
  132. /package/dist/{image-ocr-PYZU4TO7.js.map → image-ocr-3WJ4QH3T.js.map} +0 -0
  133. /package/dist/{image-ocr-YA2MQBRP.js.map → image-ocr-5BUJQ4TK.js.map} +0 -0
  134. /package/dist/{model-bundle-O6J7ORVC.js.map → model-bundle-IMBW7D2Z.js.map} +0 -0
  135. /package/dist/{models-YMOKZGCI.js.map → models-SY75IBOE.js.map} +0 -0
  136. /package/dist/{pdf-ocr-2TTO5PX2.js.map → pdf-ocr-6DEE23IP.js.map} +0 -0
  137. /package/dist/{pdf-ocr-WF2NQA6X.js.map → pdf-ocr-MQTWODVU.js.map} +0 -0
  138. /package/dist/{profile-io-ZTV2MKRT.js.map → profile-io-GJPKCYPT.js.map} +0 -0
  139. /package/dist/{rasterize-MMUZ537K.js.map → rasterize-UOALJQDS.js.map} +0 -0
  140. /package/dist/{redact-doc-S42XGV5D.js.map → redact-doc-22SYW3KH.js.map} +0 -0
  141. /package/dist/{render-WCWPZKJ7.js.map → render-MJ4H3R23.js.map} +0 -0
  142. /package/dist/{seal-WHK4GOHR.js.map → seal-XWMRUTI4.js.map} +0 -0
  143. /package/dist/{setup-D35LKZCH.js.map → setup-2URYVUEO.js.map} +0 -0
  144. /package/dist/{visual-WL4TDE5B.js.map → visual-USPS4QRC.js.map} +0 -0
  145. /package/dist/{watch-AX64CIMN.js.map → watch-ALXZGTOB.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/ocr/ruling-lines.ts","../src/ocr/engine.ts","../src/ocr/line-split.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/** 픽셀 공간(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 horizontals.push(...fillH)\n const edgeH = [...ruleH, ...fillH]\n verticals.push(...fill.verticals.filter(a =>\n inkV.some(p => Math.abs(p.x1 - a.x1) <= tol && p.y1 <= a.y2 + tol && p.y2 >= a.y1 - tol) ||\n 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)))\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 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 { readFile } from \"fs/promises\"\nimport { join } from \"path\"\nimport { OPTIONAL_DEP_INSTALL_HINT } from \"../utils.js\"\nimport {\n OCR_DET_MODEL,\n OCR_REC_MODEL,\n OCR_REC_DICT,\n getOcrModelsDir,\n parseCharacterDict,\n} from \"./models.js\"\nimport { grayCrop, inkBounds, inkStats, leaderRuns, leadingTriangle, splitRowBands } from \"./line-split.js\"\nimport { isDotFragment, joinLeaderItems, restoreBulletItems, restoreSymbols } from \"./postprocess.js\"\nimport { bandBoxes, splitBoxAtCellRules, lineCrop, type Box, REC_HEIGHT } from \"./crop.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}\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 /** 이 대비(전경/배경 평균 휘도 차) 미만 박스는 글자가 아님 — 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 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/** 리더로 볼 최소 점 수 — 말줄임표 \"…\"(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\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 이 뒤 리더 점을 물었는지(결과 끝 점을 지운다) */\ninterface LineJob { box: Box; rot: 0 | 90 | 270; group: number; join?: number; dotsBefore?: boolean; trimDots?: boolean }\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(): Promise<OcrEngine> {\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 dir = getOcrModelsDir()\n const sessionOpts: import(\"onnxruntime-node\").InferenceSession.SessionOptions = {\n graphOptimizationLevel: \"all\",\n executionProviders: [\"cpu\"],\n logSeverityLevel: 3, // paddle2onnx 변환 잔여물 W 로그 폭주 억제\n }\n const [det, rec, dictYml] = await Promise.all([\n ortMod.InferenceSession.create(join(dir, OCR_DET_MODEL.filename), sessionOpts),\n ortMod.InferenceSession.create(join(dir, OCR_REC_MODEL.filename), sessionOpts),\n readFile(join(dir, OCR_REC_DICT.filename), \"utf-8\"),\n ])\n const dict = parseCharacterDict(dictYml)\n if (dict.length === 0) throw new Error(\"OCR 사전 파싱 실패 — 모델 캐시를 삭제 후 재다운로드하세요\")\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 // 목차 리더 점 무리는 인식하지 않고 앞뒤 글만 따로 인식한 뒤, 검출 박스 하나로 다시 합쳐 \"제목 … 쪽번호\" 아이템\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 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 }>()\n jobs.forEach((job, i) => {\n const r = results[i]\n if (!r) return\n const cur = best.get(job.group)\n if (!cur || r.confidence > cur.confidence) best.set(job.group, { job, ...r })\n })\n\n let items: OcrItem[] = []\n for (const { job, text: raw, confidence } of best.values()) {\n let text = tuning.postprocess ? restoreSymbols(raw.trim()) : raw\n if (!text.trim()) continue\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) { if (stats) stats.droppedLowConf++; continue }\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 items.push({ text, ...this.itemBox(rgba, width, job.box, tuning), confidence })\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 } | null>> {\n const crops = jobs.map(j => lineCrop(rgba, pageW, j.box, j.rot))\n const order = crops.map((_, i) => i).sort((a, b) => crops[a].w - crops[b].w)\n const results: Array<{ text: string; confidence: 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 const data = logits.data as Float32Array\n idx.forEach((ci, k) => {\n results[ci] = ctcDecode(data.subarray(k * T * C, (k + 1) * T * C), T, C, this.dict)\n })\n s = e\n }\n return results\n }\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 } | null {\n let text = \"\"\n let confSum = 0\n let confCount = 0\n let prev = -1\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 (best === 0 || repeat) 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 if (best >= 1 && best <= dict.length) text += dict[best - 1]\n else if (best === dict.length + 1) text += \" \"\n }\n if (!text) return null\n return { text, confidence: confCount > 0 ? confSum / confCount : 0 }\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// ─── 엔진 싱글턴 (watch/서버 장기 실행에서 세션 재사용) ───\nlet enginePromise: Promise<OcrEngine> | null = null\n\nexport function getOcrEngine(): Promise<OcrEngine> {\n if (!enginePromise) {\n enginePromise = OcrEngine.create().catch(err => {\n enginePromise = null // 실패는 캐시하지 않음 — 모델 설치 후 재시도 가능\n throw err\n })\n }\n return enginePromise\n}\n","/**\n * 검출 박스 픽셀 분석 — 행 밴드 분할 + 잉크 대비.\n *\n * DB 검출기는 줄 간격보다 글자 간격이 넓으면 글자를 세로로 잇는다: 표 머리의 세로쓰기\n * (\"국/균/도/시\", \"사회복지과\"), 균등배분 목차의 같은 열 글자들이 키 큰 박스 하나가 되고,\n * 인식기(높이 48 고정)는 이를 짓눌러 빈 문자열이나 저신뢰 쓰레기를 낸다(실측: 코퍼스\n * 80쪽에서 GT 글자 0.55% 가 이런 박스와 함께 폐기). 박스 안 행 투영의 빈 띠로 밴드를\n * 갈라 밴드마다 따로 인식하면 글자가 자기 위치(행)를 되찾는다.\n *\n * 잉크 대비는 배경 무늬(연한 바탕의 흰 도안)를 글자로 읽는 환각을 거른다 — 진짜 글자는\n * 전경/배경 평균 휘도 차가 크다.\n */\n\n/** RGBA 페이지에서 박스 영역 휘도(BT.601 정수 근사) 추출 */\nexport function grayCrop(\n rgba: Uint8Array,\n pageW: number,\n box: { x: number; y: number; w: number; h: number },\n): Uint8Array {\n const out = new Uint8Array(box.w * box.h)\n for (let y = 0; y < box.h; y++) {\n let si = ((box.y + y) * pageW + box.x) * 4\n let di = y * box.w\n for (let x = 0; x < box.w; x++, si += 4, di++) {\n out[di] = (rgba[si] * 77 + rgba[si + 1] * 150 + rgba[si + 2] * 29) >> 8\n }\n }\n return out\n}\n\nexport interface InkStats {\n /** Otsu 임계값 */\n threshold: number\n /** 잉크 = 소수 클래스. true 면 어두운 쪽이 잉크(일반 문서) */\n darkInk: boolean\n /** 두 클래스 평균 휘도 차 (0~255) — 글자 대비 */\n contrast: number\n /** 잉크 픽셀 비율 */\n inkRatio: number\n}\n\n/** Otsu 이진화 — 잉크 극성(소수 클래스)과 대비 */\nexport function inkStats(gray: Uint8Array): InkStats {\n const hist = new Uint32Array(256)\n for (let i = 0; i < gray.length; i++) hist[gray[i]]++\n const total = gray.length\n let sumAll = 0\n for (let v = 0; v < 256; v++) sumAll += v * hist[v]\n let wB = 0, sumB = 0, best = -1, thr = 127\n for (let t = 0; t < 256; t++) {\n wB += hist[t]\n if (wB === 0) continue\n const wF = total - wB\n if (wF === 0) break\n sumB += t * hist[t]\n const mB = sumB / wB\n const mF = (sumAll - sumB) / wF\n const between = wB * wF * (mB - mF) * (mB - mF)\n if (between > best) { best = between; thr = t }\n }\n let nDark = 0, sDark = 0\n for (let v = 0; v <= thr; v++) { nDark += hist[v]; sDark += v * hist[v] }\n const nLight = total - nDark\n const sLight = sumAll - sDark\n if (nDark === 0 || nLight === 0) return { threshold: thr, darkInk: true, contrast: 0, inkRatio: 0 }\n const darkInk = nDark <= nLight\n return {\n threshold: thr,\n darkInk,\n contrast: sLight / nLight - sDark / nDark,\n inkRatio: (darkInk ? nDark : nLight) / total,\n }\n}\n\n/**\n * 행 투영의 빈 띠로 밴드 분할. 반환: 밴드별 [y0, y1) 행 구간과 괘선을 뺀 잉크 [x0, x1) 열 구간\n * (박스 로컬). 분할 불가면 길이 1.\n * @param minBandRatio 밴드 최소 높이 = 이 비율 × 글자 크기. 이보다 얇은 조각(한 글자 안의\n * 획 띠 \"을\"의 ㅇ/ㅡ/ㄹ, 밑줄, 박스 끝에 걸린 이웃 글자 조각)은 간격이 좁은 쪽 이웃 밴드에\n * 흡수된다. 글자 크기는 밴드별 잉크 가로 폭의 중앙값 — 박스 폭은 unclip 여백만큼 글자보다\n * 넓고(◎ 40px 에 박스 92px) 박스 전체 잉크 폭은 옆 열 글자 조각에 끌려가서, 둘 다 쓰면\n * 제대로 된 글자 밴드까지 합쳐진다(함평 목차 실측). 획 조각도 가로 폭은 글자 폭(ㅡ)이라\n * 중앙값이 흔들리지 않는다\n */\nexport function splitRowBands(\n gray: Uint8Array,\n w: number,\n h: number,\n ink: InkStats,\n minBandRatio: number,\n): Array<{ y0: number; y1: number; x0: number; x1: number }> {\n const isInk = (v: number) => (ink.darkInk ? v <= ink.threshold : v > ink.threshold)\n // 박스를 관통하는 괘선 — 세로선(행 85%+ 에 잉크인 열)은 투영에서 빼고, 가로선(남은 폭\n // 85%+ 가 잉크인 행)은 빈 행(구분자)으로 본다. 표 칸 사이 세로쓰기 라벨(\"국/균/도/시\")\n // 박스는 칸 경계 세로선을 끼고 잡혀 빈 행이 하나도 없다(부천 예산서 실측)\n const colInk = new Uint32Array(w)\n for (let y = 0; y < h; y++) {\n const off = y * w\n for (let x = 0; x < w; x++) if (isInk(gray[off + x])) colInk[x]++\n }\n const ruleCol = new Uint8Array(w)\n let liveW = 0\n for (let x = 0; x < w; x++) { if (colInk[x] >= h * 0.85) ruleCol[x] = 1; else liveW++ }\n const rowInk = new Uint32Array(h)\n for (let y = 0; y < h; y++) {\n let n = 0\n const off = y * w\n for (let x = 0; x < w; x++) if (!ruleCol[x] && isInk(gray[off + x])) n++\n rowInk[y] = n >= liveW * 0.85 ? 0 : n\n }\n // 안티앨리어싱·잡티 허용: 폭의 2% (최소 1px) 이하면 빈 행\n const blank = Math.max(1, Math.floor(w * 0.02))\n let bands: Array<[number, number]> = []\n let start = -1\n for (let y = 0; y <= h; y++) {\n const on = y < h && rowInk[y] > blank\n if (on && start < 0) start = y\n else if (!on && start >= 0) { bands.push([start, y]); start = -1 }\n }\n const inkCols = (y0: number, y1: number): [number, number] => {\n let x0 = w, x1 = -1\n for (let y = y0; y < y1; y++) {\n const off = y * w\n for (let x = 0; x < w; x++) if (!ruleCol[x] && isInk(gray[off + x])) { if (x < x0) x0 = x; if (x > x1) x1 = x }\n }\n return x1 >= x0 ? [x0, x1 + 1] : [0, w]\n }\n if (bands.length <= 1) return [{ y0: bands[0]?.[0] ?? 0, y1: bands[0]?.[1] ?? h, x0: 0, x1: w }]\n const widths = bands.map(([y0, y1]) => { const [a, b] = inkCols(y0, y1); return b - a }).sort((a, b) => a - b)\n const charSize = widths[widths.length >> 1]\n const minBand = Math.max(3, minBandRatio * charSize)\n // 글자 안 빈 행(“업”의 어/ㅂ 사이 2px) — 간격이 글자 크기 15% 이하이고 합쳐도 한 글자\n // 높이(1.3배) 안이면 한 밴드. 세로쓰기의 글자 사이 간격은 이보다 넓고, 두 글자를 합치면\n // 높이 상한에 걸린다\n for (let i = 0; i + 1 < bands.length;) {\n const gap = bands[i + 1][0] - bands[i][1]\n if (gap <= Math.max(2, charSize * 0.15) && bands[i + 1][1] - bands[i][0] <= charSize * 1.3) {\n bands.splice(i, 2, [bands[i][0], bands[i + 1][1]])\n } else i++\n }\n // 박스 위아래 끝에 걸린 얇은 조각 = 이웃 줄 글자의 끝자락 — 합치지 않고 버린다\n while (bands.length > 1 && bands[0][0] <= 1 && bands[0][1] - bands[0][0] < minBand) bands.shift()\n while (bands.length > 1 && bands[bands.length - 1][1] >= h - 1 && bands[bands.length - 1][1] - bands[bands.length - 1][0] < minBand) bands.pop()\n // 얇은 밴드 흡수 — 간격이 더 좁은 쪽 이웃과 합침 (가장 얇은 것부터 반복)\n for (;;) {\n let idx = -1, minH = Infinity\n for (let i = 0; i < bands.length; i++) {\n const bh = bands[i][1] - bands[i][0]\n if (bh < minBand && bh < minH) { minH = bh; idx = i }\n }\n if (idx < 0 || bands.length === 1) break\n const gapPrev = idx > 0 ? bands[idx][0] - bands[idx - 1][1] : Infinity\n const gapNext = idx < bands.length - 1 ? bands[idx + 1][0] - bands[idx][1] : Infinity\n const j = gapPrev <= gapNext ? idx - 1 : idx + 1\n const a = Math.min(idx, j), b = Math.max(idx, j)\n bands = [...bands.slice(0, a), [bands[a][0], bands[b][1]], ...bands.slice(b + 1)]\n }\n if (bands.length === 1) return [{ y0: bands[0][0], y1: bands[0][1], x0: 0, x1: w }]\n return bands.map(([y0, y1]) => { const [x0, x1] = inkCols(y0, y1); return { y0, y1, x0, x1 } })\n}\n\ntype Comp = { x0: number; x1: number; y0: number; y1: number; id: number }\n\n/**\n * 글자 잉크 연결 성분(8-이웃) — 성분마다 bbox, label 은 픽셀별 성분 번호(0 = 배경). 박스를 관통하는 칸 경계\n * 괘선 성분(박스 높이·폭의 85%+, inkBounds 와 같은 기준)은 글자가 아니라 뺀다 — 괘선이 가장 큰 성분이 되면\n * 글자 높이가 부풀어 숫자들이 \"점\"으로 잡혔다(goesan-budget-2013 두 칸을 문 박스 \"8,000 │ 1,000\")\n */\nfunction components(gray: Uint8Array, w: number, h: number, ink: InkStats): { comps: Comp[]; label: Int32Array } {\n const isInk = (v: number) => (ink.darkInk ? v <= ink.threshold : v > ink.threshold)\n const comps: Comp[] = []\n const label = new Int32Array(w * h)\n const stack: number[] = []\n for (let p0 = 0; p0 < w * h; p0++) {\n if (label[p0] || !isInk(gray[p0])) continue\n const c = { x0: w, x1: 0, y0: h, y1: 0, id: comps.length + 1 }\n label[p0] = c.id\n stack.push(p0)\n while (stack.length) {\n const p = stack.pop()!\n const x = p % w, y = (p / w) | 0\n if (x < c.x0) c.x0 = x\n if (x + 1 > c.x1) c.x1 = x + 1\n if (y < c.y0) c.y0 = y\n if (y + 1 > c.y1) c.y1 = y + 1\n for (let dy = -1; dy <= 1; dy++) for (let dx = -1; dx <= 1; dx++) {\n const nx = x + dx, ny = y + dy\n if (nx < 0 || ny < 0 || nx >= w || ny >= h) continue\n const q = ny * w + nx\n if (!label[q] && isInk(gray[q])) { label[q] = c.id; stack.push(q) }\n }\n }\n if (c.y1 - c.y0 < h * 0.85 && c.x1 - c.x0 < w * 0.85) comps.push(c)\n }\n return { comps, label }\n}\n\n/**\n * 박스 맨 앞 글자가 삼각형(△ 감액·▲ 증가 표시)인지. 인식 사전에 △·▲ 가 없어 모델은 숫자 앞 삼각형을\n * 빈칸으로 버린다 — 예산서 \"△400,352\" → \"400,352\" (코퍼스 GT △ 81개 중 OCR 30개, 나머지 부호 소실).\n * 가장 왼쪽 성분(글자 높이 40%+)이 밑변(아래 두 줄이 폭 70%+)·좁은 꼭짓점(위 20% 줄이 폭 40% 이하·가운데)·\n * 양옆 빗변(높이 60% 줄의 잉크가 폭 30% 안쪽과 70% 바깥 양쪽)을 가지면 삼각형, 그 줄 가운데가 비면 △ 차면 ▲.\n * 숫자(4·1·2·7 등)는 밑변이나 꼭짓점·빗변 조건 중 하나에서 걸린다.\n */\nexport function leadingTriangle(gray: Uint8Array, w: number, h: number, ink: InkStats): \"\\u25b3\" | \"\\u25b2\" | null {\n const { comps, label } = components(gray, w, h, ink)\n let charH = 0\n for (const c of comps) charH = Math.max(charH, c.y1 - c.y0)\n const first = comps.filter(c => c.y1 - c.y0 >= charH * 0.4).sort((a, b) => a.x0 - b.x0)[0]\n if (!first || charH < 8) return null\n const cw = first.x1 - first.x0, ch = first.y1 - first.y0\n if (cw < ch * 0.8 || cw > ch * 1.8) return null\n const rowSpan = (y: number): { n: number; lo: number; hi: number; runs: number } => {\n let n = 0, lo = -1, hi = -1, runs = 0, prev = false\n for (let x = first.x0; x < first.x1; x++) {\n const on = label[y * w + x] === first.id\n if (on) { n++; if (lo < 0) lo = x - first.x0; hi = x - first.x0; if (!prev) runs++ }\n prev = on\n }\n return { n, lo, hi, runs }\n }\n const base = Math.max(rowSpan(first.y1 - 1).n, rowSpan(first.y1 - 2).n)\n if (base < cw * 0.7) return null\n for (let y = first.y0; y < first.y0 + Math.max(1, Math.round(ch * 0.2)); y++) {\n const r = rowSpan(y)\n if (r.n === 0) continue\n if (r.hi - r.lo + 1 > cw * 0.4 || (r.lo + r.hi) / 2 < cw * 0.25 || (r.lo + r.hi) / 2 > cw * 0.75) return null\n }\n const mid = rowSpan(first.y0 + Math.round(ch * 0.6))\n if (mid.lo < 0 || mid.lo > cw * 0.3 || mid.hi < cw * 0.7) return null\n return mid.runs >= 2 ? \"\\u25b3\" : \"\\u25b2\"\n}\n\n/**\n * 목차 리더 점(\"·········\")의 가로 구간들 (박스 로컬 [x0, x1)). 검출기는 쪽번호를 앞쪽 리더 점과 한 박스로\n * 묶는데, 인식기는 점 무리 뒤 숫자를 망친다 — \"·····5\"→\"…55\", \"·····141\"→\"…11\", \"·····203\"→\"03\"\n * (changwon-plan2026 목차 실측, 코퍼스 80쪽의 리더 섞인 박스 192개). 점 무리를 빼고 앞뒤 글만 따로 인식한다.\n * 잉크 연결 성분(8-이웃)을 x 가 겹치는 것끼리 묶은 글자 덩어리 단위로 본다 — 한글 한 글자는 여러 성분(\"소\" = ㅅ+ㅗ)\n * 이라 성분 하나로 글자 높이를 재면 굵은 리더 점(9px)이 점으로 안 잡혔다(yeosu 목차). 점: 글자 높이(가장 큰 덩어리\n * 높이) 30% 이하의 작은 덩어리이고 중심이 글자 띠(점 크기를 넘는 성분들의 세로 범위) 안 — 박스 위아래 여백에 걸친\n * 점선 괘선 조각은 띠 밖이다(changwon 정원표 칸 실측). 무리: x 순서로 연달아 놓인 점들이 세로 중심 한 줄(±20%)·\n * 간격 글자 높이 이하로 minDots 개+. 리더는 같은 줄 글자를 잇는다 — 무리 앞이나 뒤 글자 높이 2배 안에 점 높이를\n * 세로로 품는 글자 성분이 있어야 한다. 두 행 사이 점선 괘선을 문 박스(위아래 행 글자가 띠를 넓힘)는 여기서 걸린다.\n * 리더는 같은 글리프를 고른 간격으로 찍은 것이라 점 크기(최대/최소 2.5배 이내)·주기(1.8배 이내)가 고르다 — 작은\n * 글꼴의 가는 획이 이진화로 쪼갠 조각(\"gifted.kaist.ac.kr\", pen-cyberbridge 흐름도)은 여기서 걸린다. 박스 끝에 닿아\n * 잘린 점은 통계에서 빼고, 값이 5개 이상이면 양 끝값도 뺀다.\n */\nexport function leaderRuns(\n gray: Uint8Array,\n w: number,\n h: number,\n ink: InkStats,\n minDots: number,\n): Array<[number, number]> {\n const comps = components(gray, w, h, ink).comps.sort((a, b) => a.x0 - b.x0)\n // x 구간이 겹치는 성분끼리 한 덩어리\n const blobs: Array<{ x0: number; x1: number; y0: number; y1: number }> = []\n for (const c of comps) {\n const last = blobs[blobs.length - 1]\n if (last && c.x0 < last.x1) { last.x1 = Math.max(last.x1, c.x1); last.y0 = Math.min(last.y0, c.y0); last.y1 = Math.max(last.y1, c.y1) }\n else blobs.push({ x0: c.x0, x1: c.x1, y0: c.y0, y1: c.y1 })\n }\n let charH = 0\n for (const b of blobs) charH = Math.max(charH, b.y1 - b.y0)\n if (charH < 8) return []\n const small = (c: { x0: number; x1: number; y0: number; y1: number }) => c.x1 - c.x0 <= charH * 0.3 && c.y1 - c.y0 <= charH * 0.3\n let bandTop = h, bandBot = 0\n for (const c of comps) if (!small(c)) { bandTop = Math.min(bandTop, c.y0); bandBot = Math.max(bandBot, c.y1) }\n const cy = (c: { y0: number; y1: number }) => (c.y0 + c.y1) / 2\n const isDot = (b: { x0: number; x1: number; y0: number; y1: number }) => small(b) && cy(b) >= bandTop && cy(b) <= bandBot\n const runs: Array<[number, number]> = []\n for (let i = 0; i < blobs.length;) {\n if (!isDot(blobs[i])) { i++; continue }\n let j = i\n while (j + 1 < blobs.length && isDot(blobs[j + 1]) && blobs[j + 1].x0 - blobs[j].x1 <= charH\n && Math.abs(cy(blobs[j + 1]) - cy(blobs[i])) <= charH * 0.2) j++\n const x0 = blobs[i].x0, x1 = blobs[j].x1, y = cy(blobs[i])\n const onLine = comps.some(c => !small(c) && c.y0 <= y && c.y1 >= y\n && ((c.x1 <= x0 + 2 && c.x1 >= x0 - 2 * charH) || (c.x0 >= x1 - 2 && c.x0 <= x1 + 2 * charH)))\n const run = blobs.slice(i, j + 1).filter(b => b.x0 > 0 && b.x1 < w)\n const spread = (xs: number[]) => {\n const v = [...xs].sort((a, b) => a - b).slice(xs.length >= 5 ? 1 : 0, xs.length >= 5 ? -1 : undefined)\n return v[v.length - 1] / Math.max(1, v[0])\n }\n const even = run.length >= 2 && spread(run.map(b => b.x1 - b.x0)) <= 2.5 && spread(run.map(b => b.y1 - b.y0)) <= 2.5\n && spread(run.slice(1).map((b, k) => b.x0 - run[k].x0)) <= 1.8\n if (j - i + 1 >= minDots && onLine && even) runs.push([x0, x1])\n i = j + 1\n }\n return runs\n}\n\n/**\n * 박스 안 잉크 외곽 (박스 로컬, [x0,x1)·[y0,y1)) — 관통 괘선(행/열 85%+ 잉크)의 픽셀은 뺀다.\n * det 박스는 unclip 여백만큼 글자보다 커서(본문 10pt 에 박스 높이 ≈ 1.5em) 이를 그대로\n * 좌표로 넘기면 글자 크기·줄 기준선·칸 배정이 텍스트층 아이템과 어긋난다. 표 칸 숫자 박스는\n * 칸 경계 괘선을 물고 잡히므로(고산 예산서 실측) 괘선 픽셀을 빼지 않으면 외곽이 박스 전체가\n * 된다. 잉크가 없으면 박스 전체.\n */\nexport function inkBounds(\n gray: Uint8Array,\n w: number,\n h: number,\n ink: InkStats,\n): { x0: number; y0: number; x1: number; y1: number } {\n const isInk = (v: number) => (ink.darkInk ? v <= ink.threshold : v > ink.threshold)\n const colInk = new Uint32Array(w)\n const rowInk = new Uint32Array(h)\n for (let y = 0; y < h; y++) {\n const off = y * w\n for (let x = 0; x < w; x++) if (isInk(gray[off + x])) { colInk[x]++; rowInk[y]++ }\n }\n const ruleCol = new Uint8Array(w), ruleRow = new Uint8Array(h)\n for (let x = 0; x < w; x++) if (colInk[x] >= h * 0.85) ruleCol[x] = 1\n for (let y = 0; y < h; y++) if (rowInk[y] >= w * 0.85) ruleRow[y] = 1\n let x0 = w, x1 = -1, y0 = h, y1 = -1\n for (let y = 0; y < h; y++) {\n if (ruleRow[y]) continue\n const off = y * w\n for (let x = 0; x < w; x++) {\n if (ruleCol[x] || !isInk(gray[off + x])) continue\n if (x < x0) x0 = x\n if (x > x1) x1 = x\n if (y < y0) y0 = y\n if (y > y1) y1 = y\n }\n }\n if (x1 < x0 || y1 < y0) return { x0: 0, y0: 0, x1: w, y1: h }\n return { x0, y0, x1: x1 + 1, y1: y1 + 1 }\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): string {\n let s = text\n // ○ 글머리: 줄 머리 O/o + (공백) + 한글\n 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 */\nexport function lineCrop(rgba: Uint8Array, pageW: number, box: Box, rot: 0 | 90 | 270): { 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 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, OcrProvider, 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 { detectRulingLines, rulingToPdfLines } from \"./ruling-lines.js\"\nimport { getOcrEngine, type OcrItem, type OcrPageStats } from \"./engine.js\"\nimport { deskewPage } from \"./deskew.js\"\nimport { ensureOcrModels } from \"./models.js\"\nimport { 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/** 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): 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\") {\n await ensureOcrModels(p => {\n if (p.phase === \"download\" && p.downloaded === 0) {\n process.stderr.write(`[kordoc-ocr] ${p.spec.name} 다운로드 중 (~${p.spec.sizeMb}MB)...\\n`)\n }\n })\n }\n const engine = mode === \"builtin\" ? await getOcrEngine() : 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 = await withTimeout(\n ocrOnePage(page, pageNo, mode, engine, warnings, detectTables, vectorOps?.get(pageNo), imageRegions?.get(pageNo)),\n PAGE_TIMEOUT_MS,\n `OCR 페이지 ${pageNo} 타임아웃 (${PAGE_TIMEOUT_MS / 1000}초)`,\n )\n result.set(pageNo, blocks)\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\nasync 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): Promise<IRBlock[]> {\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 // 벡터 글자 쪽은 보정하지 않는다 — 글자 좌표가 돌면 그 쪽 벡터 괘선과 어긋난다\n const upright = vectorOps ? rgba : deskewPage(rgba, rw, rh).rgba\n const stats: OcrPageStats = { droppedLowConf: 0 }\n const ruling = vectorOps ? undefined : detectRulingLines(upright, rw, rh, rh / pdfH)\n const items = await engine!.recognizePage(upright, rw, rh, stats, undefined, 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 // 텍스트층이 있는 쪽의 그림 영역만 읽을 때는 영역마다 그 안 글줄로 따로 블록을 만든다 — 쪽 전체로 묶으면 본문 줄과 그림 글이\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!) : []\n return 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\n return own.length ? ocrItemsToBlocks(own, pageNo, pdfW, pdfH, scale, ruling && rulingToPdfLines(ruling, scale, pdfH), detectTables) : []\n })\n }\n // 벡터 글자 쪽: 표 구조는 그 쪽의 실제 괘선으로 (rhwp cairo 13쌍 46표 exact: 래스터 괘선 14 → 실제 괘선 20)\n if (vectorOps) return ocrItemsToBlocks(items, pageNo, pdfW, pdfH, scale, undefined, detectTables, vectorOps)\n // 래스터에서 표 괘선 감지 — 스캔본 병합셀 서식도 선 기반 표 파이프라인을 탄다\n const extraLines = rulingToPdfLines(ruling!, scale, pdfH)\n return ocrItemsToBlocks(items, pageNo, pdfW, pdfH, scale, extraLines, detectTables)\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 []\n }\n return [{ 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\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): 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)\n out.push(items.map(it => ({ ...it, x: (it.x + x0) / 2, y: (it.y + y0) / 2, w: it.w / 2, h: it.h / 2 })))\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 */\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): IRBlock[] {\n const norm: NormItem[] = items.map(it => {\n const h = it.h / scale\n return {\n text: it.text,\n x: Math.round(it.x / scale),\n // NormItem.y 는 pdfjs transform[5] = 베이스라인 (bottom-up) — 잉크 하단으로 근사\n y: Math.round(pdfH - (it.y + it.h) / scale),\n w: Math.round(it.w / scale),\n h: Math.round(h),\n // 박스는 잉크 외곽(engine tightBoxes) — 한글 줄 잉크 높이 ≈ 0.9em\n fontSize: Math.max(1, Math.round(h / 0.9)),\n fontName: \"ocr\",\n isHidden: false,\n }\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":["prev","join","tryImport"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/hwpx/gongmun-surface.ts"],"sourcesContent":["/**\n * 공문서 옵션 표면 SSOT (v4.0.4, 인벤토리 영역1-1) — CLI(commander)와 MCP(zod)가\n * 각자 손배선하던 GongmunOptions 조립을 한 곳으로 모은다.\n *\n * 실측 드리프트의 뿌리는 두 가지였다:\n * 1. 값 집합(열거·중첩 키 목록·범위)이 세 곳(gongmun.ts 타입 / commander 검증 /\n * zod shape)에 각자 사경 — line_spacing이 MCP에 빠지고 sizes.bodyTitle이\n * zod에 누락되는 식. → 여기의 상수에서 양쪽이 파생한다.\n * 2. 조립 의미론(cover 우선순위, 조건부 대입)이 두 벌 복붙. → buildGongmunOptions\n * 하나로 통일 — 옵션 신설 시 이 파일과 표면별 플래그/파라미터 선언만 만지면 된다.\n *\n * 표면 문법(플래그 이름·설명문·kv 파싱)은 각 표면의 것 — 여기는 값과 의미만 담는다.\n */\n\nimport type { GongmunOptions, GongmunPreset } from \"./gongmun.js\"\n\n// ─── 값 집합 (CLI 검증·MCP zod 공통 파생원) ──────────────\n\nexport const BODY_FONTS = [\"myeongjo\", \"gothic\"] as const\nexport const H2_MARKERS = [\"band\", \"roman\", \"box\", \"number\", \"none\"] as const\nexport const BULLET2_CHARS = [\"ㅇ\", \"○\"] as const\n\n/** 요소별 글꼴 오버라이드 역할 키 (GongmunOptions.fonts) */\nexport const FONT_ROLE_KEYS = [\"body\", \"heading\", \"ref\", \"table\"] as const\n/** 개조식 요소별 크기 키 (gaejosik.ts GaejosikSizeOverrides와 정합 — 드리프트 시 컴파일 에러) */\nexport const SIZE_KEYS = [\n \"dae\", \"cham\", \"chapter\", \"coverTitle\", \"coverSub\",\n \"tocLabel\", \"tocRoman\", \"tocItem\", \"table\", \"bodyTitle\",\n] as const\n/** 단계별 위계 타이포 키 (GongmunOptions.levels[depth]) */\nexport const LEVEL_STYLE_KEYS = [\"font\", \"pt\", \"bold\"] as const\n/** 기안문 두문 키 (별지 제1호서식) */\nexport const DOC_HEAD_KEYS = [\"org\", \"slogan\", \"to\", \"via\", \"title\"] as const\n/** 기안문 결문 키 */\nexport const DOC_FOOT_KEYS = [\n \"sender\", \"drafter\", \"reviewer\", \"approver\", \"cooperator\", \"recipients\", \"docNum\", \"receive\",\n \"zip\", \"address\", \"site\", \"phone\", \"fax\", \"email\", \"disclosure\",\n] as const\n/** 보고서 표지 문서정보표 키 */\nexport const DOC_INFO_KEYS = [\"docNum\", \"date\", \"disclosure\", \"policyNo\"] as const\n/** 공고문 두문·결문 키 */\nexport const NOTICE_HEAD_KEYS = [\"no\", \"date\", \"sender\"] as const\n/** 보도자료 담당 표 키 */\nexport const PRESS_CONTACT_KEYS = [\"dept\", \"manager\", \"phone\"] as const\n\n/** 수치 범위 — MCP zod와 문서화가 공유 (라이브러리 검증은 gongmun.ts 방어선) */\nexport const BODY_PT_RANGE = { min: 6, max: 40 } as const\nexport const LINE_SPACING_RANGE = { min: 50, max: 300 } as const\nexport const SIZE_PT_RANGE = { min: 6, max: 60 } as const\nexport const APPROVAL_MAX = 6\n\n// SIZE_KEYS ⊆ GaejosikSizeOverrides 컴파일 타임 잠금 — gaejosik.ts에 키가 늘면\n// 여기(와 이를 파생하는 MCP zod·CLI 안내문)도 갱신을 강제한다\ntype SizeKey = (typeof SIZE_KEYS)[number]\ntype _SizeKeysMatch = SizeKey extends keyof NonNullable<GongmunOptions[\"sizes\"]>\n ? (keyof NonNullable<GongmunOptions[\"sizes\"]> extends SizeKey ? true : never)\n : never\nconst _sizeKeysLock: _SizeKeysMatch = true\nvoid _sizeKeysLock\n\n// ─── 조립 (두 표면 공통 의미론) ──────────────────────────\n\n/**\n * 표면 중립 입력 — CLI는 플래그·kv 파싱 결과를, MCP는 zod 통과 파라미터를 이 모양으로\n * 넘긴다. 모든 필드가 \"명시된 것만 정의\"(미지정 undefined) 규약 — --no-x 기본값 같은\n * 표면 고유 사정은 어댑터가 undefined로 정돈해서 넘겨야 한다.\n */\nexport interface GongmunSurfaceInput {\n preset: GongmunPreset\n font?: (typeof BODY_FONTS)[number]\n bodyPt?: number\n lineSpacing?: number\n org?: string\n date?: string\n cover?: boolean\n toc?: boolean\n approval?: string[]\n pageNumbers?: boolean\n endMark?: boolean\n bodyTitleBox?: boolean\n h2Marker?: (typeof H2_MARKERS)[number]\n /** 띠 제목 번호칸 채움색·글자색 (#RRGGBB) */\n bandColor?: string\n bandTextColor?: string\n fonts?: NonNullable<GongmunOptions[\"fonts\"]>\n sizes?: NonNullable<GongmunOptions[\"sizes\"]>\n levels?: NonNullable<GongmunOptions[\"levels\"]>\n bullet2?: (typeof BULLET2_CHARS)[number]\n suppressSingle?: boolean\n docHead?: NonNullable<GongmunOptions[\"docHead\"]>\n docFoot?: NonNullable<GongmunOptions[\"docFoot\"]>\n reportInfo?: string\n noticeHead?: NonNullable<GongmunOptions[\"noticeHead\"]>\n press?: NonNullable<GongmunOptions[\"press\"]>\n /** 보고서 요약 박스 (v5) */\n summary?: string\n /** 보고서 표지 문서정보표 (v5) */\n docInfo?: NonNullable<GongmunOptions[\"docInfo\"]>\n /** 표지 부서명 (v5 — cover와 함께) */\n dept?: string\n /** 표지 우상단 취급 표시 — \"대외주의\" 등 (업무보고 프리셋, cover와 함께) */\n coverLabel?: string\n}\n\n/**\n * 중립 입력 → GongmunOptions. 종전 CLI(cli.ts:535~612)·MCP(mcp.ts:792~814) 복붙\n * 두 벌의 의미론을 그대로 보존한 단일 조립:\n * - cover: false(끄기)가 최우선 → org/date 지정 시 객체(=켜짐) → true(강제 켜기)\n * → 미지정이면 프리셋 기본(resolveGongmun)\n * - 나머지는 \"명시된 것만 대입\" — 프리셋 기본값을 undefined로 덮지 않는다\n */\nexport function buildGongmunOptions(input: GongmunSurfaceInput): GongmunOptions {\n const g: GongmunOptions = { preset: input.preset }\n if (input.font) g.bodyFont = input.font\n // NaN도 대입 — CLI의 비숫자 플래그(--pt abc)가 라이브러리 방어선(assertFiniteRange)\n // 에서 명시적 에러로 죽는 기존 경로 보존 (truthiness로 거르면 무증상 폐기가 된다)\n if (input.bodyPt !== undefined) g.bodyPt = input.bodyPt\n if (input.lineSpacing !== undefined) g.lineSpacing = input.lineSpacing\n if (input.cover === false) {\n g.cover = false\n } else if (input.org || input.date || input.dept || input.coverLabel) {\n g.cover = {\n ...(input.org ? { org: input.org } : {}), ...(input.date ? { date: input.date } : {}),\n ...(input.dept ? { dept: input.dept } : {}), ...(input.coverLabel ? { label: input.coverLabel } : {}),\n }\n } else if (input.cover === true) {\n g.cover = true\n }\n if (input.toc !== undefined) g.toc = input.toc\n if (input.approval && input.approval.length > 0) g.approval = input.approval\n if (input.pageNumbers !== undefined) g.pageNumbers = input.pageNumbers\n if (input.endMark !== undefined) g.endMark = input.endMark\n if (input.bodyTitleBox !== undefined) g.bodyTitleBox = input.bodyTitleBox\n if (input.h2Marker) g.h2Marker = input.h2Marker\n if (input.bandColor) g.bandColor = input.bandColor\n if (input.bandTextColor) g.bandTextColor = input.bandTextColor\n if (input.fonts) g.fonts = input.fonts\n if (input.sizes) g.sizes = input.sizes\n if (input.levels && Object.keys(input.levels).length > 0) g.levels = input.levels\n if (input.bullet2) g.bullet2 = input.bullet2\n if (input.suppressSingle !== undefined) g.suppressSingle = input.suppressSingle\n if (input.docHead) g.docHead = input.docHead\n if (input.docFoot) g.docFoot = input.docFoot\n if (input.reportInfo) g.reportInfo = input.reportInfo\n if (input.noticeHead) g.noticeHead = input.noticeHead\n if (input.press) g.press = input.press\n if (input.summary) g.summary = input.summary\n if (input.docInfo) g.docInfo = input.docInfo\n return g\n}\n\n/**\n * `--levels` 문법 파싱 — `0=HY견고딕/17/bold,1=한컴돋움/15/bold,2=휴먼명조/14`.\n * 항목은 쉼표, depth 뒤 `=`, 속성은 `/` 구분: 숫자=pt, bold·b=굵게, plain=굵게 해제, 그 외=글꼴명.\n * 값 범위·depth 검증은 라이브러리 방어선(validateGongmunOptions)이 맡는다.\n */\nexport function parseLevelsSpec(spec: string): NonNullable<GongmunOptions[\"levels\"]> {\n const out: NonNullable<GongmunOptions[\"levels\"]> = {}\n for (const item of spec.split(\",\").map((s) => s.trim()).filter(Boolean)) {\n const eq = item.indexOf(\"=\")\n if (eq <= 0) throw new Error(`--levels: '${item}' — 'depth=글꼴/pt/bold' 꼴이어야 함`)\n const depth = item.slice(0, eq).trim()\n const st: NonNullable<GongmunOptions[\"levels\"]>[string] = {}\n for (const part of item.slice(eq + 1).split(\"/\").map((s) => s.trim()).filter(Boolean)) {\n if (/^\\d+(\\.\\d+)?$/.test(part)) st.pt = Number(part)\n else if (/^(bold|b)$/i.test(part)) st.bold = true\n else if (/^plain$/i.test(part)) st.bold = false\n else st.font = part\n }\n out[depth] = st\n }\n return out\n}\n\n/** levels 의 글꼴명을 unknownFontWarnings 입력(역할 → 글꼴명)으로 — 역할 이름은 `levels.<depth>` */\nexport function levelFontRecord(levels: NonNullable<GongmunOptions[\"levels\"]>): Record<string, string | undefined> {\n return Object.fromEntries(Object.entries(levels).map(([d, st]) => [`levels.${d}`, st.font]))\n}\n"],"mappings":"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","names":[]}
@@ -1,11 +0,0 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true});// src/types.ts
2
- var HEADING_RATIO_H1 = 1.5;
3
- var HEADING_RATIO_H2 = 1.3;
4
- var HEADING_RATIO_H3 = 1.15;
5
-
6
-
7
-
8
-
9
-
10
- exports.HEADING_RATIO_H1 = HEADING_RATIO_H1; exports.HEADING_RATIO_H2 = HEADING_RATIO_H2; exports.HEADING_RATIO_H3 = HEADING_RATIO_H3;
11
- //# sourceMappingURL=chunk-ZY5K3LPM.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/Users/mong-e/workspace/kordoc/dist/chunk-ZY5K3LPM.cjs","../src/types.ts"],"names":[],"mappings":"AAAA;ACslBO,IAAM,iBAAA,EAAmB,GAAA;AACzB,IAAM,iBAAA,EAAmB,GAAA;AACzB,IAAM,iBAAA,EAAmB,IAAA;ADplBhC;AACA;AACE;AACA;AACA;AACF,sIAAC","file":"/Users/mong-e/workspace/kordoc/dist/chunk-ZY5K3LPM.cjs","sourcesContent":[null,"/** kordoc 공통 타입 정의 */\n\n// ─── 중간 표현 (Intermediate Representation) ─────────\n\nexport interface CellContext {\n text: string\n colSpan: number\n rowSpan: number\n /** HWP5 셀 열 주소 (0-based) — 병합 테이블 배치용 */\n colAddr?: number\n /** HWP5 셀 행 주소 (0-based) — 병합 테이블 배치용 */\n rowAddr?: number\n}\n\n/** 블록 타입 — v2.0에서 heading, list, image, separator 추가 */\nexport type IRBlockType = \"paragraph\" | \"table\" | \"heading\" | \"list\" | \"image\" | \"separator\"\n\n/** 인라인 강조 run-span — 문단 텍스트를 서식 단위로 쪼갠 조각 (텍스트 연결 = block.text) */\nexport interface IRSpan {\n text: string\n bold?: boolean\n italic?: boolean\n strike?: boolean\n /** 밑줄 — 개정문 추가·변경 표시 등. GFM 문법이 없어 <u>…</u> 인라인 HTML 로 방출 */\n underline?: boolean\n code?: boolean\n /**\n * 미기입 누름틀의 안내문(HWPX CLICK_HERE, 수정 안 됨). 한컴은 화면에만 흐리게 보이고 인쇄하지 않는다.\n * 블록 `text` 에는 남기고(양식 채우기·패치가 원문 자리와 맞대도록) 마크다운에서는 뺀다 (v4.14.3)\n */\n placeholder?: boolean\n}\n\nexport interface IRBlock {\n type: IRBlockType\n /** 블록 글. HWPX·HWP5·HWP3 은 원문의 리터럴 `$` 를 `\\$` 로 담고 `$…$`·`$$…$$` 는 수식 스팬에만 쓴다 (v4.14.3) */\n text?: string\n table?: IRTable\n /** 헤딩 레벨 (1-6), type=\"heading\"일 때 사용 */\n level?: number\n /** 원본 페이지 번호 (1-based) */\n pageNumber?: number\n /** 바운딩 박스 — PDF에서만 제공 */\n bbox?: BoundingBox\n /** 텍스트 스타일 정보 (선택) */\n style?: InlineStyle\n /** 리스트 타입, type=\"list\"일 때 사용 */\n listType?: \"ordered\" | \"unordered\"\n /** 중첩 리스트 아이템 */\n children?: IRBlock[]\n /** 하이퍼링크 URL */\n href?: string\n /** 각주/미주 텍스트 (인라인 삽입용) */\n footnoteText?: string\n /** 이미지 데이터 (type=\"image\"일 때) */\n imageData?: ImageData\n /** 인라인 강조 run-span (선택 — kordoc 생성 hwpx 왕복 채널 + 외래 실속성 볼드/이탤릭).\n * 존재하면 마크다운 변환이 **·*·` 마커를 재방출한다 */\n spans?: IRSpan[]\n /** 인용문 문단 (선택 — kordoc 생성 hwpx 왕복 채널) — 마크다운 변환이 \"> \" 접두 재방출 */\n quote?: boolean\n /**\n * 문단 들여쓰기(HWPUNIT, 선택) — HWPX paraPr `<hh:margin>` 자식요소형 hc:left\n * (+양수 hc:intent 첫줄분). 마크다운 방출엔 쓰지 않는 관찰 슬롯 — gongmun 리스트\n * depth 재유도·양식 분석 등 소비자 몫 (v4.0.4)\n */\n indent?: number\n /**\n * gongmun 리스트 단계(1~7, 선택) — indent를 levelIndent 단위로 역산한 소비 결과\n * (v4.0.5). md 리스트 문법과 충돌하는 부호('- '·'1) ') 문단에만 채워지며,\n * 마크다운 변환이 2칸/단계 선행 공백을 방출해 재생성 시 depth가 복원된다\n */\n listDepth?: number\n}\n\n/** 추출된 이미지 바이너리 데이터 */\nexport interface ImageData {\n /** 이미지 바이너리 */\n data: Uint8Array\n /** MIME 타입 (image/png, image/jpeg, image/gif, image/bmp, image/wmf, image/emf) */\n mimeType: string\n /** 원본 파일명 (있는 경우) */\n filename?: string\n}\n\n/** 바운딩 박스 — PDF 포인트 단위 (72pt = 1인치) */\nexport interface BoundingBox {\n page: number\n x: number\n y: number\n width: number\n height: number\n}\n\n/** 인라인 텍스트 스타일 */\nexport interface InlineStyle {\n bold?: boolean\n italic?: boolean\n /** 취소선 — 법령 개정문 등의 삭제 표시. 판정은 취소선 모양 whitelist (비트만 믿으면 오탐) */\n strike?: boolean\n /** 밑줄 — 판정은 밑줄 종류 BOTTOM 한정 (NONE 이 기본 잡음, 코퍼스 실측) */\n underline?: boolean\n fontSize?: number\n fontName?: string\n}\n\n// ─── 표 분류 계약 (#76) — 분류기 내부 점수 구조(TableSignals)는 노출하지 않는다 ───\n\nexport type TableClassificationKind = \"semantic-table\" | \"non-tabular-layout\" | \"uncertain\"\n\nexport type TableClassificationReason =\n | \"repeated-row-schema\"\n | \"grid-regularity\"\n | \"high-active-density\"\n | \"column-type-consistency\"\n | \"nested-structure-wrapper\"\n | \"span-irregularity\"\n | \"spacer-bands\"\n | \"extreme-sparsity\"\n | \"diagram-context-keyword\"\n | \"low-evidence\"\n | \"ambiguous-scores\"\n\n/** 휴리스틱 분류 결과 — confidence 는 확률이 아니라 두 점수의 격차 */\nexport interface TableClassificationSummary {\n kind: TableClassificationKind\n confidence: number\n semanticScore: number\n nonTabularScore: number\n reasons: TableClassificationReason[]\n}\n\nexport interface IRTable {\n rows: number\n cols: number\n cells: IRCell[][]\n /** PDF에서 행 경계가 확인된 1열 데이터 표. 목록성 1열 틀의 평탄화와 구분한다. */\n renderAsTable?: boolean\n /** 첫 행을 헤더로 렌더링할지 여부 (현재: rows > 1이면 true — 의미적 감지가 아닌 레이아웃 힌트) */\n hasHeader: boolean\n /** opt-in 분류(`ParseOptions.classifyTables`) 결과 — #76 */\n classification?: TableClassificationSummary\n /** 원본 표 식별자(HWPX `hp:tbl id`) — 렌더 region(`RenderRegion.sourceId`)과의 조인 키 */\n sourceId?: string\n /** 렌더 인프라가 준 페이지 로컬 pt 조각(다중 페이지 표는 여럿) — extractTables 가 채운다 */\n regions?: BoundingBox[]\n /** 표 캡션 (예: \"표 1. 부서별 예산\") — v3.0 */\n caption?: string\n /**\n * 캡션 내부 블록 콘텐츠 — v4.2.8 (#55).\n * 캡션 안 중첩표·문단을 구조 그대로, 원문 순서대로 보존한다 (IRCell.blocks와 같은 계약).\n * 표가 있을 때만 채워지며, caption은 하위 호환용 평탄화 문자열로 계속 제공된다.\n */\n captionBlocks?: IRBlock[]\n}\n\nexport interface IRCell {\n text: string\n colSpan: number\n rowSpan: number\n /**\n * 셀 내부 블록 콘텐츠 — v3.0.\n * 중첩 표·이미지 등 구조 콘텐츠(또는 왕복 채널 span 문단)가 있는 셀에만 채워지며,\n * 그 안의 문단·표·이미지를 문서(원문) 순서대로 보존한다. 다중 문단뿐인 평문 셀은\n * blocks 없이 text 평탄화(문단을 `\\n`으로 결합)로만 제공된다 (blocks 무게 억제, #57).\n * 표와 텍스트가 한 줄에 번갈아 놓인 셀도 배치 순서를 따른다 (v4.2.3, #49).\n * blocks가 있으면 text는 blocks의 평탄화 텍스트(하위 호환용)다.\n */\n blocks?: IRBlock[]\n /** 제목 셀 여부 (HWP5 width_ref bit2 / HWPX header 속성) — v3.0 */\n isHeader?: boolean\n}\n\n// ─── 메타데이터 ─────────────────────────────────────\n\n/** 문서 메타데이터 — 각 포맷에서 추출 가능한 필드만 채워짐 */\nexport interface DocumentMetadata {\n /** 문서 제목 */\n title?: string\n /** 작성자 */\n author?: string\n /** 작성 프로그램 (예: \"한글 2020\", \"Adobe Acrobat\") */\n creator?: string\n /** 생성일시 (ISO 8601) */\n createdAt?: string\n /** 수정일시 (ISO 8601) */\n modifiedAt?: string\n /** 페이지/섹션 수 — pageMode=\"layout\"이면 실제 페이지 수, \"section\"이면 섹션 수 */\n pageCount?: number\n /**\n * 페이지 경계 신뢰도 (#66) — \"layout\": 조판 정보 기반 실제 페이지\n * (한컴 저장본 HWP/HWPX·PDF·COM), \"section\": 섹션 단위 근사\n * (조판 캐시 없는 생성 파일). HWP/HWPX/PDF에서만 설정된다.\n */\n pageMode?: \"layout\" | \"section\"\n /** 문서 포맷 버전 (예: HWP \"5.1.0.1\") */\n version?: string\n /** 설명 */\n description?: string\n /** 키워드 */\n keywords?: string[]\n}\n\n// ─── 파싱 옵션 ──────────────────────────────────────\n\n/** 파싱 옵션 — parse() 함수에 전달 */\nexport interface ParseOptions {\n /**\n * 파싱할 페이지/섹션 범위 (1-based).\n * - 배열: [1, 2, 3]\n * - 문자열: \"1-3\", \"1,3,5-7\"\n *\n * PDF: 정확한 페이지 단위. HWP/HWPX: 섹션 단위 근사치.\n */\n pages?: number[] | string\n /** 이미지 기반 PDF OCR (선택).\n * - 지정 안 함(기본): 내장 모델이 이미 캐시에 있으면(`kordoc models` 로 받았거나 앞서 `ocr: true` 로 받은 경우) 텍스트층이\n * 없는 쪽(스캔·글자를 곡선으로 그린 쪽)만 자동 인식한다. 모델이 없으면 다운로드하지 않고 종전처럼 NEEDS_OCR 경고만.\n * - `false`: 끈다.\n * - `true`: 내장 엔진(PP-OCRv5 korean, ~18MB 자동 다운로드)으로 OCR 필요 판정\n * 페이지만 인식 (스캔 페이지·글꼴 매핑 깨진 페이지). 정상 페이지는 파싱 결과 유지.\n * - `\"force\"`: 전 페이지를 내장 엔진으로 강제 OCR.\n * - 함수: 사용자 제공 OcrProvider (Claude Vision·Tesseract 등) — 판정은 `true`와 동일. */\n ocr?: boolean | \"force\" | OcrProvider\n /** 진행률 콜백 — current: 현재 페이지/섹션, total: 전체 수 */\n onProgress?: (current: number, total: number) => void\n /** PDF 머리글/바닥글 자동 제거 */\n removeHeaderFooter?: boolean\n /** 평문 Markdown — 그림 자리 표시·링크 URL·밑줄(`<u>`)·굵게(`**`) 표기를 빼고 글만 (제목·목록·표 구조는 유지).\n * 이미지 바이트를 따로 저장하지 않는 색인·RAG 용. 기본 false. `blocks` IR 은 그대로 */\n plain?: boolean\n /** 모든 표를 HTML 로 — 파이프 표도 HTML 표로 옮기고, 표마다 태그를 한 줄씩 들여써 낸다(BeautifulSoup prettify 모양, 첫 행 `<th>`).\n * HTML 표만 다루는 소비자·채점기용. 기본 false(병합·중첩 없는 표는 GFM 파이프 표) */\n htmlTables?: boolean\n /** 표 오른쪽 끝의 빈 열(서식 문서의 입력란) 보존 (#47).\n * 기본 false: 마크다운 가독성을 위해 후행 빈 열을 트림.\n * 양식 인식 경로(parse_form·fill)는 내부적으로 항상 켠다. */\n keepTrailingEmptyCols?: boolean\n /** 구조 파싱 뒤 표를 의미표/레이아웃/불확실로 분류해 `IRTable.classification` 에 붙인다 (#76).\n * 기본 false — 기본 parse 출력 불변. 중첩표·셀 blocks·캡션 blocks 까지 재귀, 원문 순서는 바꾸지 않는다. */\n classifyTables?: boolean\n /** 빈 문단(텍스트 없는 hp:p) 보존 (#57). 기본 false: 종전대로 빈 문단 제거.\n * 켜면 본문은 `text: \"\"` paragraph 블록으로, 표 셀은 빈 줄로 순서대로 보존해\n * \"원문 문단 수 = 줄 수\" 대응을 유지한다 (행 줄맞춤 서식 문서용).\n * 개체(표·이미지·글상자)만 있는 문단은 개체 출력이 따로 있어 대상이 아니다.\n * 현재 HWPX 경로 적용. */\n keepEmptyParagraphs?: boolean\n /** 미기입 누름틀 안내문도 마크다운에 낸다 (#92). 기본 false: 종전대로 뺀다(한컴은 화면에만 흐리게 보이고 인쇄하지 않는 글).\n * 빈 서식 문서에서 \"이 칸에 무엇을 적나\"(예: \"학교명 기재 금지\")가 안내문뿐일 때 켠다. HWPX·HWP5 */\n includeFieldPlaceholders?: boolean\n /** 비밀번호로 보호된 문서의 열기 암호.\n * HWPX(ODF AES-256-CBC)·HWP3(DES) 지원. 한컴 DRM(문서 보안)은 별개라 해당 없음. */\n password?: string\n /** 원본 파일 경로 (DRM COM fallback에 필요, 내부 전용) */\n filePath?: string\n /**\n * PDF 수식 OCR 활성화 (기본 false).\n *\n * 활성화 시 각 PDF 페이지를 이미지로 렌더링 → YOLOv8 기반 수식 영역 검출 →\n * TrOCR 기반 LaTeX 인식. 감지된 수식은 `$...$` (inline) / `$$...$$` (display) 로\n * 블록 텍스트에 삽입된다.\n *\n * 필수 optional 의존성: `onnxruntime-node`, `@huggingface/transformers`,\n * `@hyzyla/pdfium`, `sharp`. 미설치 시 parse 에 실패하지 않고 **경고만** 남기고\n * 수식 인식은 skip 한다 (일반 텍스트 추출은 정상 동작).\n *\n * 모델(~155MB) 은 첫 사용 시 HuggingFace 에서 자동 다운로드 되어\n * `~/.cache/kordoc/models/pix2text/` 에 SHA-256 검증과 함께 저장된다.\n */\n formulaOcr?: boolean\n /**\n * 레이아웃 표 페이지 반복 헤더(러닝 헤더) 정리 휴리스틱 활성화 (기본 false).\n *\n * 구형 HWP5 문서가 페이지마다 재삽입한 짧은 번호매김 러닝 헤더\n * (\"2. 과제 구축 내용\")를 최초 1회만 남기고 이후 중복을 제거한다.\n * 다만 페이지 위치 정보가 없는 HWP5 특성상 이 휴리스틱은 정당하게 반복되는\n * 번호매김 문단(예: 붙임/별지별 재번호 \"1. 목적\")도 삭제할 수 있어 opt-in 으로\n * 둔다. 활성 시 제거가 발생하면 HIDDEN_TEXT_FILTERED 경고를 남긴다.\n */\n dedupeRunningHeaders?: boolean\n /**\n * 추출된 이미지를 마크다운에 base64 data URI 로 인라인 (기본 false).\n *\n * 활성화 시 `![image](image_001.bmp)` 참조를 `![image](data:image/png;base64,...)` 로\n * 치환한다. 임베드된 BMP 는 PNG 로 무손실 압축 후 인라인하여 용량을 크게 줄인다.\n * 별도 이미지 파일 없이 자체 완결형 마크다운이 되어 MCP/AI 에이전트 소비에 적합하다.\n * (현재 HWP5 경로 지원)\n */\n inlineImages?: boolean\n /**\n * 이미지 바이트 추출 (기본 true). false 면 결과에 이미지 바이트를 싣지 않는다.\n * `images` 는 비고, 블록의 `imageData` 도 떼며, HWP5 `inlineImages` 도 무시한다.\n * 그림 자리 표시(`![image](…)`)는 마크다운·블록에 그대로 남아 위치는 알 수 있다.\n * PDF 는 PNG 인코딩을 건너뛴다. 거르는 기준은 같되 메모리 보호용 128MB 누적 상한은 걸리지\n * 않아, 그 상한 뒤의 그림도 자리 표시가 남는다.\n *\n * 검색 색인처럼 글자만 필요한 호출자용이다. 그림이 많은 문서는 base64 로 불어난 이미지가\n * JSON 출력의 대부분을 차지했다 (PDF 실측 200MB → 9.5MB, CPU 시간 30% 감소). 본문 글자와\n * OCR 은 이 옵션과 무관하다.\n */\n images?: boolean\n /**\n * PDF 표 감지 활성화 (기본 true). false 면 선 기반 그리드·클러스터 표 감지를 모두 끄고\n * 자연 읽기순 텍스트만 뽑는다 (#64).\n *\n * 시각적 테두리 박스(시험지 안내문·보기 상자)가 표로 잡히면 주변 본문이 셀 매핑으로\n * 끌려 들어가 문항 순서가 뒤집히는데, 표가 애초에 필요 없는 텍스트 중심 사용처에는\n * 우회 수단이 없었다. 표가 실제로 있는 문서에 켜면 표 구조가 문단으로 흩어지므로\n * 문서 종류를 아는 호출자만 쓰는 opt-out 이다. PDF 전용 — 다른 포맷은 무시.\n */\n tables?: boolean\n}\n\n// ─── 파싱 경고 ──────────────────────────────────────\n\n/** 파싱 중 스킵/실패한 요소 보고 */\nexport interface ParseWarning {\n /** 관련 페이지 번호 (알 수 있는 경우) */\n page?: number\n /** 경고 메시지 */\n message: string\n /** 구조화된 경고 코드 */\n code: WarningCode\n}\n\nexport type WarningCode =\n | \"SKIPPED_IMAGE\"\n | \"SKIPPED_OLE\"\n | \"TRUNCATED_TABLE\"\n | \"OCR_FALLBACK\"\n | \"UNSUPPORTED_ELEMENT\"\n | \"BROKEN_ZIP_RECOVERY\"\n | \"HIDDEN_TEXT_FILTERED\"\n | \"MALFORMED_XML\"\n | \"PARTIAL_PARSE\"\n | \"LENIENT_CFB_RECOVERY\"\n | \"NEEDS_OCR\"\n | \"OCR_FAILED\"\n | \"OCR_APPLIED\"\n | \"OCR_LOW_CONF\"\n | \"COM_EMPTY\"\n | \"DRM_COM_FALLBACK\"\n /** pages 옵션 요청됐으나 조판 캐시가 없어 섹션 단위 근사로 적용됨 (#66) */\n | \"PAGE_BOUNDARY_APPROXIMATE\"\n\n/** 문서 구조 (헤딩 트리) */\nexport interface OutlineItem {\n level: number\n text: string\n pageNumber?: number\n}\n\n// ─── 에러 코드 ──────────────────────────────────────\n\n/** 구조화된 에러 코드 — 프로그래밍적 에러 핸들링용 */\nexport type ErrorCode =\n | \"EMPTY_INPUT\"\n | \"UNSUPPORTED_FORMAT\"\n | \"ENCRYPTED\"\n | \"DRM_PROTECTED\"\n | \"CORRUPTED\"\n | \"DECOMPRESSION_BOMB\"\n | \"ZIP_BOMB\"\n | \"IMAGE_BASED_PDF\"\n | \"NO_SECTIONS\"\n | \"PARSE_ERROR\"\n | \"MISSING_DEPENDENCY\"\n /** 결과 직렬화가 런타임 문자열 한계를 넘음 — 이미지 다량 문서의 JSON 출력 (#65) */\n | \"OUTPUT_TOO_LARGE\"\n /** 입력 경로가 존재하지 않음(ENOENT) — 문서 파싱 이전 단계의 실패 */\n | \"FILE_NOT_FOUND\"\n\n// ─── 파싱 결과 (discriminated union) ────────────────\n\n/** 감지된 파일 형식. pptx는 감지만 지원하며 파싱 시 UNSUPPORTED_FORMAT을 반환한다. */\nexport type FileType = \"hwpx\" | \"hwp\" | \"hwp3\" | \"hwpml\" | \"pdf\" | \"xlsx\" | \"xls\" | \"docx\" | \"pptx\" | \"image\" | \"unknown\"\n\ninterface ParseResultBase {\n fileType: FileType\n /** 페이지/섹션 수 — PDF: 실제 페이지 수, HWP/HWPX: 섹션 수, XLSX: 시트 수 */\n pageCount?: number\n /** 이미지 기반 PDF 여부 (텍스트 추출 불가) */\n isImageBased?: boolean\n}\n\nexport interface ParseSuccess extends ParseResultBase {\n success: true\n /** 추출된 마크다운 텍스트 */\n markdown: string\n /**\n * 중간 표현 블록 (구조화된 데이터 접근용).\n * 블록은 문서(원문) 읽기 순서를 따른다 — 한 문단 안에 글자취급(treatAsChar)\n * 표와 텍스트가 섞여 있어도 배치 순서대로 방출된다 (v4.2.3, #50).\n */\n blocks: IRBlock[]\n /** 문서 메타데이터 */\n metadata?: DocumentMetadata\n /** 문서 구조 (헤딩 트리) — v2.0 */\n outline?: OutlineItem[]\n /** 파싱 중 발생한 경고 — v2.0 */\n warnings?: ParseWarning[]\n /** 추출된 이미지 목록 — 마크다운에서 파일명으로 참조됨 */\n images?: ExtractedImage[]\n /**\n * 페이지별 마크다운 (#68) — `blocks` 의 `pageNumber` 로 갈라 페이지마다 다시\n * 마크다운을 만든 것. 페이지 경계의 신뢰도는 `metadata.pageMode` 를 따른다\n * (\"layout\" = 실제 페이지, \"section\" = 섹션 근사). `pages` 옵션으로 범위를\n * 줄이면 그 범위만 담긴다. 페이지 번호를 매기지 않는 포맷(DOCX 등)에서는 없고,\n * XLSX 는 `pageCount` 와 같은 의미로 시트 한 장이 한 쪽이다.\n *\n * 여러 페이지에 걸친 표는 시작 페이지 한 블록이라, 표가 이어지는 중간\n * 페이지의 `markdown` 은 빈 문자열일 수 있다 (현 IR 구조의 한계).\n */\n pages?: PageMarkdown[]\n /** 페이지별 텍스트 품질 신호 — PDF에서만 제공 */\n pageQuality?: PageQuality[]\n /** 문서 단위 품질 요약 — PDF에서만 제공 */\n qualitySummary?: DocumentQualitySummary\n}\n\n/** 페이지 한 장의 마크다운 (#68). ParseSuccess.pages 항목. */\nexport interface PageMarkdown {\n /** 원본 페이지 번호 (1-based) */\n pageNumber: number\n /** 그 페이지 블록만으로 만든 마크다운. 블록이 없으면 빈 문자열 */\n markdown: string\n}\n\n/** 페이지별 텍스트 품질 신호 (PDF 전용). 자세한 설명은 src/pdf/quality.ts */\nexport interface PageQuality {\n page: number\n textChars: number\n hangulRatio: number\n controlCharRatio: number\n replacementCharRatio: number\n puaRatio: number\n needsOcr: boolean\n ocrReason?: \"vector_text\" | \"low_text\" | \"high_pua\" | \"high_control\" | \"high_replacement\" | \"garbled_hangul\"\n}\n\n/** 문서 단위 품질 요약 (PDF 전용). */\nexport interface DocumentQualitySummary {\n totalPages: number\n totalTextChars: number\n avgHangulRatio: number\n avgControlCharRatio: number\n avgReplacementCharRatio: number\n avgPuaRatio: number\n lowTextPageCount: number\n highPuaPageCount: number\n needsOcr: boolean\n ocrCandidatePages: number[]\n}\n\n/** 추출된 이미지 — ParseSuccess.images에 포함 */\nexport interface ExtractedImage {\n /** 마크다운에서 참조되는 파일명 (예: image_001.png) */\n filename: string\n /** 이미지 바이너리 */\n data: Uint8Array\n /** MIME 타입 */\n mimeType: string\n /** 원본 컨테이너 내 항목명 (HWPX/DOCX ZIP 경로, HWP5 BinData 스토리지명 — PDF 등 합성 이미지는 없음, #70) */\n source?: string\n}\n\nexport interface ParseFailure extends ParseResultBase {\n success: false\n /** 오류 메시지 */\n error: string\n /** 구조화된 에러 코드 */\n code?: ErrorCode\n}\n\nexport type ParseResult = ParseSuccess | ParseFailure\n\n// ─── 문서 비교 (Diff) ───────────────────────────────\n\nexport type DiffChangeType = \"added\" | \"removed\" | \"modified\" | \"unchanged\"\n\nexport interface BlockDiff {\n type: DiffChangeType\n /** 원본 블록 (added이면 undefined) */\n before?: IRBlock\n /** 변경 후 블록 (removed이면 undefined) */\n after?: IRBlock\n /** modified 테이블의 셀 단위 diff */\n cellDiffs?: CellDiff[][]\n /** 유사도 (0-1) */\n similarity?: number\n}\n\nexport interface CellDiff {\n type: DiffChangeType\n before?: string\n after?: string\n}\n\nexport interface DiffResult {\n stats: { added: number; removed: number; modified: number; unchanged: number }\n diffs: BlockDiff[]\n}\n\n// ─── 라운드트립 패치 (v3.0) ─────────────────────────\n\n/** 패치 중 매핑 실패/미지원으로 건너뛴 항목 — silent 실패 금지 */\nexport interface PatchSkip {\n /** 건너뛴 사유 */\n reason: string\n /** 원본 쪽 내용 요약 (최대 80자) */\n before?: string\n /** 편집 쪽 내용 요약 (최대 80자) */\n after?: string\n /**\n * 부분 적용 표시 — 변경이 적용은 됐지만(applied 계상) 편집 원형 그대로는\n * 아님 (예: 셀 내 줄 추가를 마지막 문단에 병합). 완전 미적용 skip과 구분.\n */\n partial?: boolean\n}\n\n/** patchHwpx 옵션 */\nexport interface PatchOptions {\n /** 패치 후 재파싱 자동 검증 (기본 true) */\n verify?: boolean\n}\n\n/** patchHwpx / patchBlocks 결과 */\nexport interface PatchResult {\n success: boolean\n /** 패치된 HWPX (success=true) */\n data?: Uint8Array\n /** 적용된 변경 수 */\n applied: number\n /** 매핑 실패 항목 (이유 포함) */\n skipped: PatchSkip[]\n /**\n * 무손실 검증 (patchHwpx/patchHwp 전용): 패치본 재파싱 vs 편집 마크다운의\n * 잔차 diff — modified/added/removed가 0이어야 의도가 전부 반영된 것.\n * session.patchBlocks는 이 필드를 채우지 않는다 (changes 참조).\n */\n verification?: DiffResult\n /**\n * 변경 가시화 (session.patchBlocks 전용): 패치 전 → 후 문서 diff —\n * 적용된 편집 수만큼 modified가 나오는 것이 정상. verification과 의미가\n * 정반대이므로 혼용 금지.\n */\n changes?: DiffResult\n /** 실패 사유 (success=false) */\n error?: string\n}\n\n// ─── 양식 인식 ──────────────────────────────────────\n\nexport interface FormField {\n label: string\n value: string\n /** 0-based 소스 행 */\n row: number\n /** 0-based 소스 열 */\n col: number\n /** 매칭된 입력 키(정규화) — 모호 라벨 거부 가드(fillWithUniqueGuard)의 집계 기준 */\n key?: string\n /** 매칭 근거. `clickhere`(누름틀 name 정확 일치)는 서식 제작자가 선언한 계약이라\n * 여러 곳에 같은 이름이 있어도 \"모호\"가 아니다 — 모호 라벨 거부 집계에서 제외된다.\n * (머리말·본문에 같은 이름의 누름틀을 두는 서식이 실제로 있다) */\n source?: \"clickhere\"\n}\n\nexport interface FormResult {\n fields: FormField[]\n /** 양식 확신도 (0-1) */\n confidence: number\n}\n\n// ─── OCR 프로바이더 ─────────────────────────────────\n\n/** 사용자 제공 OCR 함수 — 페이지 이미지를 받아 텍스트 반환.\n * PDF 경로는 항상 \"image/png\", 이미지 직접 입력 경로는 원본 mime 그대로 전달. */\nexport type OcrProvider = (\n pageImage: Uint8Array,\n pageNumber: number,\n mimeType: \"image/png\" | \"image/jpeg\" | \"image/webp\"\n) => Promise<string>\n\n// ─── Watch 모드 ─────────────────────────────────────\n\nexport interface WatchOptions {\n dir: string\n outDir?: string\n webhook?: string\n format?: \"markdown\" | \"json\"\n pages?: string\n silent?: boolean\n}\n\n// ─── 헤딩 감지 공통 임계값 ──────────────────────────\n\n/** 폰트 크기 비율 → heading level (전 파서 공통) */\nexport const HEADING_RATIO_H1 = 1.5\nexport const HEADING_RATIO_H2 = 1.3\nexport const HEADING_RATIO_H3 = 1.15\n\n// ─── 내부 파서 반환 타입 ─────────────────────────────\n\n/** 내부 파서가 index.ts에 반환하는 공통 타입 (HWP5/HWPX/PDF/XLSX/DOCX) */\nexport interface InternalParseResult {\n markdown: string\n blocks: IRBlock[]\n metadata?: DocumentMetadata\n outline?: OutlineItem[]\n warnings?: ParseWarning[]\n images?: ExtractedImage[]\n /** PDF 전용: 이미지 기반 PDF 여부 */\n isImageBased?: boolean\n /** PDF 전용: 페이지별 품질 신호 */\n pageQuality?: PageQuality[]\n /** PDF 전용: 문서 단위 품질 요약 */\n qualitySummary?: DocumentQualitySummary\n /** PDF 전용: 페이지별 마크다운 — 문서 마크다운과 같은 PDF 마무리(1×1 표 펴기·cleanPdfText)를 쪽마다 적용한 것 */\n pages?: PageMarkdown[]\n}\n"]}
@@ -1,10 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- blocksToChunks
4
- } from "./chunk-PIMB2YNO.js";
5
- import "./chunk-TGD7UFHD.js";
6
- import "./chunk-RC4C7Q33.js";
7
- export {
8
- blocksToChunks
9
- };
10
- //# sourceMappingURL=chunks-JD25E5PM.js.map