kordoc 4.14.3 → 4.14.4

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 (136) hide show
  1. package/README.md +16 -4
  2. package/dist/{-7XBPXCXN.js → -Z4TJ3QGK.js} +11 -11
  3. package/dist/{chunk-EBZUZC6F.cjs → chunk-5LAZQCUJ.cjs} +4 -4
  4. package/dist/{chunk-EBZUZC6F.cjs.map → chunk-5LAZQCUJ.cjs.map} +1 -1
  5. package/dist/{chunk-NQCUAGLR.js → chunk-6BWU6SQX.js} +1 -1
  6. package/dist/{chunk-2LQGSDZ6.js.map → chunk-6BWU6SQX.js.map} +1 -1
  7. package/dist/{chunk-2LQGSDZ6.js → chunk-6CUIXJOC.js} +1 -1
  8. package/dist/{chunk-NQCUAGLR.js.map → chunk-6CUIXJOC.js.map} +1 -1
  9. package/dist/{chunk-D6KQKMU3.js → chunk-7SE4EZ45.js} +2 -2
  10. package/dist/{chunk-YGBZABI4.js → chunk-D4U2TXDC.js} +6 -6
  11. package/dist/chunk-DPWWXUOG.js +2222 -0
  12. package/dist/chunk-DPWWXUOG.js.map +1 -0
  13. package/dist/{chunk-FAZR34CA.js → chunk-GLSBEEZJ.js} +2 -2
  14. package/dist/{chunk-L5EH2NVO.js → chunk-GUJ4Q73I.js} +2 -2
  15. package/dist/{chunk-JCVIOLBF.js → chunk-HIJUKZHM.js} +3 -3
  16. package/dist/{chunk-IW2CB755.cjs → chunk-IMAAFSAX.cjs} +392 -42
  17. package/dist/chunk-IMAAFSAX.cjs.map +1 -0
  18. package/dist/{chunk-GTINVM5K.js → chunk-LCW7SEPG.js} +2 -2
  19. package/dist/{chunk-4R67INKT.js → chunk-LFSQAMSM.js} +389 -39
  20. package/dist/chunk-LFSQAMSM.js.map +1 -0
  21. package/dist/{chunk-VJHAUMCF.js → chunk-MG6DJEX4.js} +389 -39
  22. package/dist/chunk-MG6DJEX4.js.map +1 -0
  23. package/dist/{chunk-QGW7NMTX.js → chunk-NLU6LBLY.js} +2 -2
  24. package/dist/{chunk-5VVS2QIJ.cjs → chunk-QEJUY6SK.cjs} +3 -3
  25. package/dist/{chunk-5VVS2QIJ.cjs.map → chunk-QEJUY6SK.cjs.map} +1 -1
  26. package/dist/{chunk-VYOGXNQ3.js → chunk-QIF2VXBQ.js} +2 -2
  27. package/dist/{chunk-I4NNYWIR.js → chunk-TITDJWVZ.js} +2 -2
  28. package/dist/{chunk-NOAQRCV6.cjs → chunk-USHQCF5T.cjs} +676 -75
  29. package/dist/chunk-USHQCF5T.cjs.map +1 -0
  30. package/dist/{chunk-VNFJPTU4.js → chunk-V3DX4DUF.js} +2 -2
  31. package/dist/{chunk-WB5LW52Z.js → chunk-VDN7LMPL.js} +2 -2
  32. package/dist/{chunk-IHZEFW4L.js → chunk-VJIL6PIL.js} +2 -2
  33. package/dist/{chunk-HX4UUF7Y.cjs → chunk-VWUYVEFN.cjs} +1 -1
  34. package/dist/{chunk-HX4UUF7Y.cjs.map → chunk-VWUYVEFN.cjs.map} +1 -1
  35. package/dist/{chunk-EMGF6EEV.cjs → chunk-WJHAU73B.cjs} +2 -2
  36. package/dist/{chunk-EMGF6EEV.cjs.map → chunk-WJHAU73B.cjs.map} +1 -1
  37. package/dist/{chunk-O2PGQNYE.js → chunk-WJJGW5PL.js} +2 -2
  38. package/dist/{chunk-CVEERPMP.js → chunk-XVXHXQIO.js} +9 -9
  39. package/dist/{chunk-4RJ5D7MT.js → chunk-YAKC46HZ.js} +2 -2
  40. package/dist/{chunk-A275ZQLX.js → chunk-YAVHVFRY.js} +653 -52
  41. package/dist/chunk-YAVHVFRY.js.map +1 -0
  42. package/dist/{chunk-UVVEBI22.js → chunk-YT3GC62E.js} +653 -52
  43. package/dist/chunk-YT3GC62E.js.map +1 -0
  44. package/dist/chunks-CCMRL4MJ.js +10 -0
  45. package/dist/cli.js +28 -28
  46. package/dist/cli.js.map +1 -1
  47. package/dist/{formula-CPO2TZGB.cjs → formula-5LOVHAKE.cjs} +9 -9
  48. package/dist/{formula-CPO2TZGB.cjs.map → formula-5LOVHAKE.cjs.map} +1 -1
  49. package/dist/{formula-X44GV7TN.js → formula-7RANCW5L.js} +3 -3
  50. package/dist/{formula-NOIDZEBA.js → formula-7UYJOVTB.js} +4 -4
  51. package/dist/{image-ocr-72RAR4WR.js → image-ocr-I6JOCATS.js} +8 -8
  52. package/dist/{image-ocr-SUGT64ZM.cjs → image-ocr-NYBJEOH2.cjs} +13 -13
  53. package/dist/{image-ocr-SUGT64ZM.cjs.map → image-ocr-NYBJEOH2.cjs.map} +1 -1
  54. package/dist/{image-ocr-R6W6A75P.js → image-ocr-ZV6PDBUQ.js} +6 -6
  55. package/dist/index.cjs +2137 -537
  56. package/dist/index.cjs.map +1 -1
  57. package/dist/index.d.cts +4 -3
  58. package/dist/index.d.ts +4 -3
  59. package/dist/index.js +1851 -251
  60. package/dist/index.js.map +1 -1
  61. package/dist/mcp.js +24 -24
  62. package/dist/mcp.js.map +1 -1
  63. package/dist/{model-bundle-57CLEBCO.js → model-bundle-2C5IYDLE.js} +5 -5
  64. package/dist/{models-7ELIFTD7.js → models-SEQZHZFT.js} +5 -5
  65. package/dist/{parser-4NKAYWAX.js → parser-3FNXEP6T.js} +485 -47
  66. package/dist/parser-3FNXEP6T.js.map +1 -0
  67. package/dist/{parser-HWFQRSUQ.js → parser-FJBJXRJK.js} +485 -47
  68. package/dist/parser-FJBJXRJK.js.map +1 -0
  69. package/dist/{parser-RANGXGBD.cjs → parser-HVEKF73B.cjs} +535 -97
  70. package/dist/parser-HVEKF73B.cjs.map +1 -0
  71. package/dist/pdf-ocr-JRDBJ6TA.cjs +14 -0
  72. package/dist/{pdf-ocr-VHSFACH7.cjs.map → pdf-ocr-JRDBJ6TA.cjs.map} +1 -1
  73. package/dist/pdf-ocr-UUBWBEHL.js +16 -0
  74. package/dist/pdf-ocr-VJSF2SQC.js +13 -0
  75. package/dist/{profile-io-V52PL5ZO.js → profile-io-RUFZOZO6.js} +3 -3
  76. package/dist/{rasterize-ZIGLJDOG.js → rasterize-IOYXR5SI.js} +3 -3
  77. package/dist/{redact-TIQEQI3M.js → redact-NMIKVHMP.js} +2 -2
  78. package/dist/{redact-doc-KXZ3TZAG.js → redact-doc-QBHNJ2BU.js} +98 -30
  79. package/dist/redact-doc-QBHNJ2BU.js.map +1 -0
  80. package/dist/{render-U5PE6V2E.js → render-YZ2TEC3N.js} +7 -7
  81. package/dist/seal-ECV44TUH.js +10 -0
  82. package/dist/{setup-YRDM4ODP.js → setup-MPIEP6XT.js} +3 -3
  83. package/dist/visual-ICMOWUCR.js +19 -0
  84. package/dist/{watch-5IOLBATT.js → watch-RYO7MN5B.js} +12 -12
  85. package/package.json +2 -2
  86. package/dist/chunk-4R67INKT.js.map +0 -1
  87. package/dist/chunk-A275ZQLX.js.map +0 -1
  88. package/dist/chunk-IH56ZISC.js +0 -620
  89. package/dist/chunk-IH56ZISC.js.map +0 -1
  90. package/dist/chunk-IW2CB755.cjs.map +0 -1
  91. package/dist/chunk-NOAQRCV6.cjs.map +0 -1
  92. package/dist/chunk-UVVEBI22.js.map +0 -1
  93. package/dist/chunk-VJHAUMCF.js.map +0 -1
  94. package/dist/chunks-I7TEMJXH.js +0 -10
  95. package/dist/parser-4NKAYWAX.js.map +0 -1
  96. package/dist/parser-HWFQRSUQ.js.map +0 -1
  97. package/dist/parser-RANGXGBD.cjs.map +0 -1
  98. package/dist/pdf-ocr-BNC34MNU.js +0 -13
  99. package/dist/pdf-ocr-QX2GB33Q.js +0 -16
  100. package/dist/pdf-ocr-VHSFACH7.cjs +0 -14
  101. package/dist/redact-doc-KXZ3TZAG.js.map +0 -1
  102. package/dist/seal-DNXJBILR.js +0 -10
  103. package/dist/visual-5XWMA4EV.js +0 -19
  104. /package/dist/{-7XBPXCXN.js.map → -Z4TJ3QGK.js.map} +0 -0
  105. /package/dist/{chunk-D6KQKMU3.js.map → chunk-7SE4EZ45.js.map} +0 -0
  106. /package/dist/{chunk-YGBZABI4.js.map → chunk-D4U2TXDC.js.map} +0 -0
  107. /package/dist/{chunk-FAZR34CA.js.map → chunk-GLSBEEZJ.js.map} +0 -0
  108. /package/dist/{chunk-L5EH2NVO.js.map → chunk-GUJ4Q73I.js.map} +0 -0
  109. /package/dist/{chunk-JCVIOLBF.js.map → chunk-HIJUKZHM.js.map} +0 -0
  110. /package/dist/{chunk-GTINVM5K.js.map → chunk-LCW7SEPG.js.map} +0 -0
  111. /package/dist/{chunk-QGW7NMTX.js.map → chunk-NLU6LBLY.js.map} +0 -0
  112. /package/dist/{chunk-VYOGXNQ3.js.map → chunk-QIF2VXBQ.js.map} +0 -0
  113. /package/dist/{chunk-I4NNYWIR.js.map → chunk-TITDJWVZ.js.map} +0 -0
  114. /package/dist/{chunk-VNFJPTU4.js.map → chunk-V3DX4DUF.js.map} +0 -0
  115. /package/dist/{chunk-WB5LW52Z.js.map → chunk-VDN7LMPL.js.map} +0 -0
  116. /package/dist/{chunk-IHZEFW4L.js.map → chunk-VJIL6PIL.js.map} +0 -0
  117. /package/dist/{chunk-O2PGQNYE.js.map → chunk-WJJGW5PL.js.map} +0 -0
  118. /package/dist/{chunk-CVEERPMP.js.map → chunk-XVXHXQIO.js.map} +0 -0
  119. /package/dist/{chunk-4RJ5D7MT.js.map → chunk-YAKC46HZ.js.map} +0 -0
  120. /package/dist/{chunks-I7TEMJXH.js.map → chunks-CCMRL4MJ.js.map} +0 -0
  121. /package/dist/{formula-X44GV7TN.js.map → formula-7RANCW5L.js.map} +0 -0
  122. /package/dist/{formula-NOIDZEBA.js.map → formula-7UYJOVTB.js.map} +0 -0
  123. /package/dist/{image-ocr-72RAR4WR.js.map → image-ocr-I6JOCATS.js.map} +0 -0
  124. /package/dist/{image-ocr-R6W6A75P.js.map → image-ocr-ZV6PDBUQ.js.map} +0 -0
  125. /package/dist/{model-bundle-57CLEBCO.js.map → model-bundle-2C5IYDLE.js.map} +0 -0
  126. /package/dist/{models-7ELIFTD7.js.map → models-SEQZHZFT.js.map} +0 -0
  127. /package/dist/{pdf-ocr-BNC34MNU.js.map → pdf-ocr-UUBWBEHL.js.map} +0 -0
  128. /package/dist/{pdf-ocr-QX2GB33Q.js.map → pdf-ocr-VJSF2SQC.js.map} +0 -0
  129. /package/dist/{profile-io-V52PL5ZO.js.map → profile-io-RUFZOZO6.js.map} +0 -0
  130. /package/dist/{rasterize-ZIGLJDOG.js.map → rasterize-IOYXR5SI.js.map} +0 -0
  131. /package/dist/{redact-TIQEQI3M.js.map → redact-NMIKVHMP.js.map} +0 -0
  132. /package/dist/{render-U5PE6V2E.js.map → render-YZ2TEC3N.js.map} +0 -0
  133. /package/dist/{seal-DNXJBILR.js.map → seal-ECV44TUH.js.map} +0 -0
  134. /package/dist/{setup-YRDM4ODP.js.map → setup-MPIEP6XT.js.map} +0 -0
  135. /package/dist/{visual-5XWMA4EV.js.map → visual-ICMOWUCR.js.map} +0 -0
  136. /package/dist/{watch-5IOLBATT.js.map → watch-RYO7MN5B.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/redact-rules.ts","../src/redact.ts"],"sourcesContent":["/**\n * redact 룰 정의 — 룰별 정규식 변형·검증기(생년월일 세기·Luhn·사업자/법인 체크섬·전화 국번)·라벨 사전.\n * 엔진(정규화·겹침 처리·마스킹·마크다운 표 문맥)은 redact.ts. 룰별 근거·오탐 실측은 bench/redact-bench.mjs.\n */\n\nimport type { RedactRule } from \"./redact.js\"\n\n/**\n * 룰 우선순위 (앞이 높음). crn 을 rrn 앞에 — 법인 라벨이 붙은 6-7 번호는 법인번호로.\n * email 을 번호 룰들 앞에(로컬파트 숫자 오탐 방지), card·brn 을 phone·account 앞에,\n * driver 를 account 앞에(면허번호 12자리 4그룹이 계좌 패턴에 포섭됨).\n */\nexport const RULE_PRIORITY: readonly RedactRule[] = [\n \"crn\", \"rrn\", \"email\", \"card\", \"brn\", \"phone\", \"driver\", \"passport\", \"account\", \"ip\",\n]\n\n// ─── 검증기 ───────────────────────────────────────────\n\n/** Luhn 체크섬 (카드번호) */\nfunction luhnValid(digits: string): boolean {\n let sum = 0\n for (let i = 0; i < digits.length; i++) {\n let d = digits.charCodeAt(digits.length - 1 - i) - 48\n if (i % 2 === 1) {\n d *= 2\n if (d > 9) d -= 9\n }\n sum += d\n }\n return sum % 10 === 0\n}\n\nconst DAYS_IN_MONTH = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]\n\n/**\n * 주민·외국인등록번호 앞 6자리 생년월일 검증 — 성별 자리로 세기를 정한다\n * (1·2·5·6 → 1900년대, 3·4·7·8 → 2000년대). 2000년대생은 올해를 넘지 못한다.\n */\nfunction birthdateValid(front6: string, genderDigit: string): boolean {\n const yy = Number(front6.slice(0, 2))\n const mm = Number(front6.slice(2, 4))\n const dd = Number(front6.slice(4, 6))\n if (mm < 1 || mm > 12 || dd < 1) return false\n const g = Number(genderDigit)\n const year = (g === 3 || g === 4 || g === 7 || g === 8 ? 2000 : 1900) + yy\n if (year > new Date().getFullYear()) return false\n const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0)\n const max = mm === 2 ? (leap ? 29 : 28) : DAYS_IN_MONTH[mm - 1]\n return dd <= max\n}\n\n/** 사업자등록번호 체크섬 (국세청 공개 가중치 1,3,7,1,3,7,1,3,5) */\nfunction brnChecksumValid(d: string): boolean {\n const w = [1, 3, 7, 1, 3, 7, 1, 3, 5]\n let sum = 0\n for (let i = 0; i < 9; i++) sum += w[i] * (d.charCodeAt(i) - 48)\n sum += Math.floor(((d.charCodeAt(8) - 48) * 5) / 10)\n return (10 - (sum % 10)) % 10 === d.charCodeAt(9) - 48\n}\n\n/** 법인등록번호 체크섬 (가중치 1,2 반복, 모듈러 10) */\nfunction crnChecksumValid(d: string): boolean {\n let sum = 0\n for (let i = 0; i < 12; i++) sum += (i % 2 === 0 ? 1 : 2) * (d.charCodeAt(i) - 48)\n return (10 - (sum % 10)) % 10 === d.charCodeAt(12) - 48\n}\n\nconst digitsOf = (s: string): string => s.replace(/\\D/g, \"\")\n/** 같은 숫자 반복(0000…, 1111…) — 서식 예시·자리표시자 */\nconst allSameDigit = (d: string): boolean => d.length > 0 && /^(\\d)\\1*$/.test(d)\n\n// ─── 라벨 문맥 ────────────────────────────────────────\n\nexport type Ctx = \"rrn\" | \"crn\" | \"brn\" | \"account\" | \"card\" | \"phone\" | \"passport\" | \"driver\" | \"ip\" | \"other\"\n\n/**\n * 라벨 사전. 값 앞 창(현재 셀 + 왼쪽 셀, 태그 제거 후 40자, 앞선 값 뒤부터)에 있는 라벨 유형 **집합**이\n * 문맥이다. \"주민등록번호(법인등록번호)\"처럼 한 칸에 두 라벨이 붙은 서식은 둘 다 문맥이 된다 — 모호하면\n * 가리는 쪽(주민번호)으로 판정한다. \"other\" 는 PII 가 아닌 식별번호 라벨(출원번호·과제번호·ISBN·예산과목…).\n */\nconst LABELS: ReadonlyArray<readonly [Ctx, RegExp]> = [\n [\"crn\", /법\\s*인\\s*(?:등\\s*록\\s*)?번\\s*호/g],\n [\"rrn\", /주\\s*민\\s*(?:등\\s*록\\s*)?번\\s*호|주\\s*민\\s*등\\s*록|외\\s*국\\s*인\\s*(?:등\\s*록\\s*)?번\\s*호|거\\s*소\\s*신\\s*고\\s*번\\s*호|생\\s*년\\s*월\\s*일|resident\\s*registration|\\bRRN\\b/gi],\n [\"brn\", /사\\s*업\\s*자\\s*(?:등\\s*록\\s*)?번\\s*호|고\\s*유\\s*번\\s*호|business\\s*registration/gi],\n [\"account\", /계\\s*좌|예\\s*금\\s*주|입\\s*금|은\\s*행|뱅\\s*크|농\\s*협|신\\s*협|수\\s*협|우\\s*체\\s*국|금\\s*고|bank\\s*account|account\\s*(?:no|number)/gi],\n [\"card\", /카\\s*드|credit\\s*card|card\\s*(?:no|number)/gi],\n [\"phone\", /전\\s*화|연\\s*락\\s*처|휴\\s*대|핸\\s*드\\s*폰|팩\\s*스|☎|☏|콜\\s*센\\s*터|고\\s*객\\s*센\\s*터|대\\s*표\\s*번\\s*호|상\\s*담|문\\s*의|\\b(?:tel|fax|mobile|phone|h\\.?p|ARS)\\b/gi],\n [\"passport\", /여\\s*권|passport/gi],\n [\"driver\", /운\\s*전\\s*면\\s*허|면\\s*허\\s*번\\s*호|driver'?s?\\s*licen[cs]e/gi],\n [\"ip\", /\\bIP\\b|아\\s*이\\s*피/g],\n [\"other\", /출\\s*원\\s*번\\s*호|과\\s*제\\s*번\\s*호|사\\s*업\\s*번\\s*호|접\\s*수\\s*번\\s*호|민\\s*원\\s*번\\s*호|신\\s*청\\s*번\\s*호|문\\s*서\\s*번\\s*호|관\\s*리\\s*번\\s*호|주\\s*문\\s*번\\s*호|일\\s*련\\s*번\\s*호|발\\s*간\\s*등\\s*록|등\\s*록\\s*번\\s*호\\s*\\(\\s*기\\s*록|ISBN|ISSN|예\\s*산|과\\s*목|코\\s*드|모\\s*델|제\\s*품|부\\s*품|시\\s*리\\s*얼|serial|model|\\bS\\/N\\b|바\\s*코\\s*드|기\\s*록\\s*물|사\\s*건\\s*번\\s*호/gi],\n]\n\n/** 셀 경계 — GFM 파이프(이스케이프 제외)와 HTML 셀 닫는 태그 */\nconst CELL_SEP_RE = /(?<!\\\\)\\||<\\/t[dh]>/gi\n/** 앞선 값 — 창 안에서 이 뒤부터만 본다 (앞 값의 라벨이 뒤 값으로 번지지 않게) */\nconst PREV_VALUE_RE = /\\d[\\d\\-.\\s]{4,}\\d/g\n\n/** 텍스트 안 라벨 유형 집합 */\nexport function labelsIn(s: string): Set<Ctx> {\n const out = new Set<Ctx>()\n for (const [ctx, re] of LABELS) {\n re.lastIndex = 0\n if (re.test(s)) out.add(ctx)\n }\n return out\n}\n\n/** 매치 앞 문맥 라벨 집합 — 현재 셀 + 바로 왼쪽 셀, 태그 제거 후 40자, 앞선 값 뒤부터 */\nexport function labelsBefore(norm: string, start: number): Set<Ctx> {\n let win = norm.slice(Math.max(0, start - 120), start)\n const seps = [...win.matchAll(CELL_SEP_RE)]\n if (seps.length >= 2) {\n const s = seps[seps.length - 2]\n win = win.slice((s.index as number) + s[0].length)\n }\n win = win.replace(/<[^>]*>/g, \" \").slice(-40)\n const prev = [...win.matchAll(PREV_VALUE_RE)].pop()\n if (prev) win = win.slice((prev.index as number) + prev[0].length)\n return labelsIn(win)\n}\n\n/** 값 바로 뒤 비개인 식별번호 명사 — \"11-24-123456-01 (접수)\", \"S123A4567 부품\" */\nconst OTHER_AFTER_RE = /^\\s{0,2}[(\\[]?\\s{0,2}(?:접\\s*수|신\\s*청|주\\s*문|사\\s*건|과\\s*제|문\\s*서|관\\s*리|부\\s*품|제\\s*품|모\\s*델|코\\s*드|시\\s*리\\s*얼|serial|model)/i\n\n// ─── 룰 정의 ──────────────────────────────────────────\n\nexport interface Match {\n /** 정규화 텍스트 매치 (d 플래그 — 그룹 인덱스 포함) */\n m: RegExpMatchArray\n /** 라벨 문맥 집합 (인라인, 비었으면 표 머리글 — 지연 계산) */\n ctx: () => ReadonlySet<Ctx>\n}\n\nexport interface Variant {\n re: RegExp\n /** 이 유형 라벨이 문맥에 있을 때만 인정 (모호한 모양) */\n needs?: Ctx\n /** 추가 검증 — false면 스킵 */\n ok?: (x: Match) => boolean\n /** 마스킹할 이름 그룹 — 그 범위의 영숫자를 maskChar 로 (구분자·공백·기존 가림표는 유지) */\n mask: readonly string[]\n /** 그룹 안 글자를 구분자까지 전부 가린다 (이메일 로컬파트 — 이름 마디 길이도 숨김) */\n maskAll?: boolean\n}\n\nconst g = (x: Match, name: string): string => x.m.groups?.[name] ?? \"\"\nconst has = (x: Match, c: Ctx): boolean => x.ctx().has(c)\n/** 법인 라벨만 있고 주민 라벨은 없다 — 이때만 6-7 번호를 주민번호에서 뺀다 (모호하면 가린다) */\nconst crnOnly = (x: Match): boolean => has(x, \"crn\") && !has(x, \"rrn\")\n/** 매치 뒤 원문 (정규화 텍스트) */\nconst after = (x: Match, n = 16): string => {\n const end = (x.m.index as number) + x.m[0].length\n return x.m.input!.slice(end, end + n)\n}\nconst before = (x: Match, n = 2): string => {\n const start = x.m.index as number\n return x.m.input!.slice(Math.max(0, start - n), start)\n}\n/** 비개인 식별번호 라벨이 앞에 있거나(해당 PII 라벨 없이) 뒤에 \"(접수)\"류 명사가 붙은 값 */\nconst otherCtx = (x: Match, own: Ctx): boolean => (has(x, \"other\") && !has(x, own)) || OTHER_AFTER_RE.test(after(x))\n/**\n * 값 바로 앞이 일련번호 라벨(\"S/N 0507-9978-0522\", \"바코드: 1588-…\") — 전화 모양이어도 기기·상품 번호.\n * 전화는 \"예산\"·\"과목\" 같은 넓은 other 라벨로는 막지 않는다(예산담당관 044-… 실번호) — 바로 앞 좁은 라벨만\n */\nconst SERIAL_BEFORE_RE = /(?:\\bS\\/N|\\bserial(?:\\s*(?:no|number))?|시\\s*리\\s*얼(?:\\s*번\\s*호)?|일\\s*련\\s*번\\s*호|바\\s*코\\s*드)\\s*[:.]?\\s*$/i\nconst serialBefore = (x: Match): boolean => SERIAL_BEFORE_RE.test(before(x, 20)) && !has(x, \"phone\")\n\n// 공통 경계 — 숫자·영문·@·하이픈에 붙은 번호(더 긴 코드의 일부)는 제외\nconst PHONE_PREFIX = \"0(?:1[016789]|2|3[1-3]|4[1-4]|5[1-5]|6[1-4]|70|80|60|50[2-8])\"\nconst PHONE_AREA = \"0(?:1[016789]|2|3[1-3]|4[1-4]|5[1-5]|6[1-4]|70|80|50[2-8])\"\n/**\n * 전화번호 구분자 한 자리 — 하이픈(앞뒤 공백 0~2칸: \"02- 3668-1330\", \"031- 405 -8255\"), 공백 1~2칸, 온점.\n * 두 자리는 따로 고르되 온점은 온점끼리만(phoneSepsOk) — \"010.1234-5678\" 혼합은 미탐 계약 유지\n */\nconst PHONE_SEP = \"(?: {0,2}- {0,2}| {1,2}|\\\\.)\"\n/** 전화번호 끝 경계 — 영숫자·@·하이픈이 붙으면 더 긴 코드(예산과목 064-1100-1131-301-210) */\nconst PHONE_END = \"(?![\\\\w@-])\"\n\n/** 두 구분자 조합 — 둘 다 없거나 둘 다 있고, 온점은 온점끼리. 온점 구분이면 뒤 \".숫자\"(더 긴 점 코드) 거부 */\nfunction phoneSepsOk(x: Match, s1: string, s2: string): boolean {\n if ((s1 === \"\") !== (s2 === \"\")) return false\n if ((s1 === \".\") !== (s2 === \".\")) return false\n return s1 !== \".\" || !/^\\.\\d/.test(after(x, 2))\n}\n\n/**\n * 전화번호 숫자 검증 — 가입자 번호 전부 0(010-0000-0000 자리표시자) 제외. 구분자 없는 지역번호는\n * 국번이 0으로 시작하면 코드로 본다(0202030201 NCS 능력단위 류) — 구분자가 있으면 형식을 믿는다\n * (실결재 결문 팩스 02-02xx-xxxx 실측). 02-123-4567·010-1234-5678 같은 관용 예시는 실번호처럼 가린다.\n */\nfunction phoneDigitsOk(pre: string, mid: string, last: string, bare: boolean): boolean {\n if (/^0+$/.test(mid + last)) return false\n return !bare || !/^0(?:2|3[1-3]|4[1-4]|5[1-5]|6[1-4])$/.test(pre) || mid[0] !== \"0\"\n}\n\n/** 이메일 시작 경계 — 로컬파트 한가운데(gd.hong 의 hong)·이미 가린 로컬파트 꼬리(j*********5@…)에서는 시작하지\n * 않는다. 글머리·강조 가림표 뒤(*hong@…, ●hong@…)와 문장 부호 뒤(.hong@…)는 허용 */\nconst EMAIL_START = \"(?<![\\\\w%+])(?<!\\\\w[.\\\\-])(?<![A-Za-z0-9][*●○◯×#]+)\"\nconst EMAIL_DOMAIN = \"(?<domain>[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?(?:\\\\.[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?)*\\\\.(?<tld>[A-Za-z]{2,24}))(?![\\\\w-])\"\nconst FILE_EXT_TLD = /^(?:png|jpe?g|gif|bmp|svg|webp|tiff?|hwpx?|hwp|pdf|docx?|xlsx?|pptx?|txt|zip|csv|json|xml|html?|js|css|mp[34]|wav|avi|exe)$/i\n\nexport const RULES: Record<RedactRule, readonly Variant[]> = {\n // 주민·외국인등록번호 — 앞 6자리(생년월일) 유지, 뒤 7자리 마스크\n rrn: [\n { // 하이픈형 (앞뒤 공백 0~2칸, 유니코드 대시는 정규화로 흡수). 뒤에 \"-글자\"는 허용, \"-숫자\"는 더 긴 코드.\n // 법인 라벨만 있으면 제외\n re: /(?<![\\w-])(?<front>\\d{6})(?<sep> {0,2}- {0,2})(?<back>[1-8]\\d{6})(?![\\w])(?!-\\d)/dg,\n ok: (x) => birthdateValid(g(x, \"front\"), g(x, \"back\")[0]) && !crnOnly(x),\n mask: [\"back\"],\n },\n { // 뒷자리 일부만 가린 형태 (900101-1******, 900101-1694***) — 남은 숫자를 가린다\n re: /(?<![\\w-])(?<front>\\d{6})(?<sep> {0,2}- {0,2})(?<back>[1-8][0-9*●○◯×xX#]{6})(?![\\w*●○◯×#])(?!-\\d)/dg,\n ok: (x) => /[*●○◯×xX#]/.test(g(x, \"back\")) && birthdateValid(g(x, \"front\"), g(x, \"back\")[0]) && !crnOnly(x),\n mask: [\"back\"],\n },\n { // 구분자 없음·공백/온점/가운뎃점/빗금/물결 구분 — 주민/외국인등록번호(생년월일) 라벨이 있을 때만\n re: /(?<![\\w@.-])(?<front>\\d{6})(?<sep>[ .·/~]?)(?<back>[1-8]\\d{6})(?![\\w@-])/dg,\n needs: \"rrn\",\n ok: (x) => birthdateValid(g(x, \"front\"), g(x, \"back\")[0]),\n mask: [\"back\"],\n },\n ],\n // 법인등록번호 (opt-in) — 법인 라벨이 있거나, 뒷자리가 0으로 시작(주민번호 불가)하고 체크섬 유효\n crn: [\n {\n re: /(?<![\\w-])(?<front>\\d{6})(?<sep> {0,2}- {0,2})(?<back>\\d{7})(?![\\w-])/dg,\n ok: (x) => !allSameDigit(g(x, \"front\") + g(x, \"back\")) && (crnOnly(x)\n || (g(x, \"back\")[0] === \"0\" && crnChecksumValid(g(x, \"front\") + g(x, \"back\")))),\n mask: [\"back\"],\n },\n {\n re: /(?<![\\w@.-])(?<front>\\d{6})(?<back>\\d{7})(?![\\w@-])/dg,\n needs: \"crn\",\n ok: (x) => !allSameDigit(g(x, \"front\") + g(x, \"back\")) && !has(x, \"rrn\"),\n mask: [\"back\"],\n },\n ],\n // 이메일 — 로컬파트 첫 글자만 남기고 마스크, 도메인 유지. 파일명(image@2x.png)은 제외\n // 이미 가린 로컬파트(j*********5@…) 뒤꼬리에서 시작하지 않는다 — 가림표 바로 뒤 매치 금지\n email: [\n {\n re: new RegExp(`${EMAIL_START}(?<l1>[A-Za-z0-9])(?<lrest>[A-Za-z0-9._%+-]+)@${EMAIL_DOMAIN}`, \"dg\"),\n ok: (x) => !FILE_EXT_TLD.test(g(x, \"tld\")),\n mask: [\"lrest\"],\n maskAll: true,\n },\n { // 한 글자 로컬파트 — 첫 글자를 남기면 가린 게 없으므로 전부 가린다\n re: new RegExp(`${EMAIL_START}(?<l1>[A-Za-z0-9])@${EMAIL_DOMAIN}`, \"dg\"),\n ok: (x) => !FILE_EXT_TLD.test(g(x, \"tld\")),\n mask: [\"l1\"],\n },\n ],\n // 카드번호 — 가운데 마스크 + Luhn. 구분자 없는 15~16자리는 카드 라벨이 있을 때만\n card: [\n { // 4-4-4-4 (같은 구분자 강제)\n re: /(?<![\\w-])(?<g1>\\d{4})(?<sep>[- ])(?<g2>\\d{4})\\k<sep>(?<g3>\\d{4})\\k<sep>(?<g4>\\d{4})(?![\\w-])(?![- ]\\d)/dg,\n ok: (x) => { const d = digitsOf(x.m[0]); return luhnValid(d) && !allSameDigit(d) },\n mask: [\"g2\", \"g3\"],\n },\n { // AMEX 4-6-5\n re: /(?<![\\w-])(?<g1>3[47]\\d{2})(?<sep>[- ])(?<g2>\\d{6})\\k<sep>(?<g3a>\\d)(?<g3b>\\d{4})(?![\\w-])(?![- ]\\d)/dg,\n ok: (x) => luhnValid(digitsOf(x.m[0])),\n mask: [\"g2\", \"g3a\"],\n },\n { // 온점 구분 4.4.4.4 — 카드 라벨 필요 (버전·좌표 모양과 겹침)\n re: /(?<![\\w.-])(?<g1>\\d{4})\\.(?<g2>\\d{4})\\.(?<g3>\\d{4})\\.(?<g4>\\d{4})(?![\\w-])(?!\\.\\d)/dg,\n needs: \"card\",\n ok: (x) => { const d = digitsOf(x.m[0]); return luhnValid(d) && !allSameDigit(d) },\n mask: [\"g2\", \"g3\"],\n },\n { // 무구분 15~16자리 — 카드 라벨 필요\n re: /(?<![\\w@.-])(?<g1>\\d{4})(?<mid>\\d{7,8})(?<g4>\\d{4})(?![\\w@-])/dg,\n needs: \"card\",\n ok: (x) => { const d = digitsOf(x.m[0]); return luhnValid(d) && !allSameDigit(d) },\n mask: [\"mid\"],\n },\n ],\n // 사업자등록번호 3-2-5 — 체크섬 유효 또는 사업자 라벨. 뒤 5자리(일련·검증번호) 마스크\n brn: [\n {\n re: /(?<![\\w-])(?<a>\\d{3})-(?<b>\\d{2})-(?<c>\\d{5})(?![\\w-])/dg,\n ok: (x) => {\n const d = digitsOf(x.m[0])\n if (allSameDigit(d)) return false\n return has(x, \"brn\") || (brnChecksumValid(d) && !has(x, \"account\") && !otherCtx(x, \"brn\"))\n },\n mask: [\"c\"],\n },\n {\n re: /(?<![\\w@.-])(?<a>\\d{3})(?<b>\\d{2})(?<c>\\d{5})(?![\\w@-])/dg,\n needs: \"brn\",\n ok: (x) => !allSameDigit(digitsOf(x.m[0])),\n mask: [\"c\"],\n },\n ],\n // 전화번호 — 가운데 자리만 마스크 (대표번호는 뒤 4자리).\n // 뒤에 \"-\"가 붙으면 더 긴 코드(예산과목 064-1100-1131-301-210)의 일부라 제외.\n phone: [\n { // 국내 일반: 휴대폰·지역·070/080/060·050x 안심번호. 구분자 -·공백·온점(PHONE_SEP) 또는 무구분\n re: new RegExp(`(?<![\\\\w@+-])(?<pre>${PHONE_PREFIX})(?<s1>${PHONE_SEP}?)(?<mid>\\\\d{3,4})(?<s2>${PHONE_SEP}?)(?<last>\\\\d{4})${PHONE_END}`, \"dg\"),\n ok: (x) => {\n const pre = g(x, \"pre\")\n const bare = g(x, \"s1\") === \"\"\n if (!phoneSepsOk(x, g(x, \"s1\"), g(x, \"s2\"))) return false\n if (!phoneDigitsOk(pre, g(x, \"mid\"), g(x, \"last\"), bare)) return false\n if (serialBefore(x)) return false\n if (!bare) return true // 구분자 있는 번호는 쉼표·온점 목록 안에서도 번호 (\"02-…,010-…\")\n // 무구분 — 소수점 뒤(3.0212345678)는 아님, 휴대폰이 아닌 번호는 쉼표 숫자 목록 안의 코드(…,0507502002,…) 제외\n if (/\\d\\.$/.test(before(x))) return false\n if (pre.startsWith(\"01\")) return true // 휴대폰 무구분 목록 \"01023456789,01098765432\"\n return !/\\d,$/.test(before(x)) && !/^,\\d/.test(after(x, 2))\n },\n mask: [\"mid\"],\n },\n { // 괄호 지역번호: (02) 123-4567, (042)481-6300\n re: new RegExp(`(?<![\\\\w@])\\\\((?<pre>${PHONE_AREA})\\\\) ?(?<mid>\\\\d{3,4})(?<s2>${PHONE_SEP}?)(?<last>\\\\d{4})${PHONE_END}`, \"dg\"),\n ok: (x) => phoneDigitsOk(g(x, \"pre\"), g(x, \"mid\"), g(x, \"last\"), false) && (g(x, \"s2\") !== \".\" || !/^\\.\\d/.test(after(x, 2))),\n mask: [\"mid\"],\n },\n { // 지역번호 뒤 닫는 괄호: 02)450-1234\n re: new RegExp(`(?<![\\\\w@(+-])(?<!\\\\d\\\\.)(?<pre>${PHONE_AREA})\\\\) ?(?<mid>\\\\d{3,4})(?<s2>${PHONE_SEP})(?<last>\\\\d{4})${PHONE_END}`, \"dg\"),\n ok: (x) => phoneDigitsOk(g(x, \"pre\"), g(x, \"mid\"), g(x, \"last\"), false) && (g(x, \"s2\") !== \".\" || !/^\\.\\d/.test(after(x, 2))),\n mask: [\"mid\"],\n },\n { // 국제 표기: +82 10-1234-5678, +82-2-123-4567, +82 (0)10 1234 5678, +821012345678\n re: new RegExp(`(?<![\\\\w@+])(?<cc>\\\\+82)(?<s0>[-. ]?)(?:\\\\(0\\\\) ?)?(?<pre>1[016789]|2|3[1-3]|4[1-4]|5[1-5]|6[1-4]|70|80)(?<s1>${PHONE_SEP}?)(?<mid>\\\\d{3,4})(?<s2>${PHONE_SEP}?)(?<last>\\\\d{4})${PHONE_END}`, \"dg\"),\n ok: (x) => phoneSepsOk(x, g(x, \"s1\"), g(x, \"s2\")) && phoneDigitsOk(\"0\" + g(x, \"pre\"), g(x, \"mid\"), g(x, \"last\"), g(x, \"s1\") === \"\"),\n mask: [\"mid\"],\n },\n { // 대표번호 15xx·16xx·18xx — 구분자 필수. 연도 범위 모양(1592-1598)·온점/공백 구분은 전화 라벨이 있을 때만.\n // 뒤 4자리 0000 은 자리표시자\n re: new RegExp(`(?<![\\\\w@+-])(?<!\\\\d\\\\.)(?<pre>1(?:5[2-9]\\\\d|6\\\\d\\\\d|8\\\\d\\\\d))(?<sep> {0,2}- {0,2}| {1,2}|\\\\.)(?<last>\\\\d{4})${PHONE_END}`, \"dg\"),\n ok: (x) => {\n if (g(x, \"last\") === \"0000\" || serialBefore(x)) return false\n if (g(x, \"sep\") === \".\" && /^\\.\\d/.test(after(x, 2))) return false\n const a = Number(g(x, \"pre\"))\n const b = Number(g(x, \"last\"))\n // 뒤 그룹이 앞 그룹 이상이고 올해 이하면 연도 범위(재위·생몰·전쟁·시대) — 폭과 무관\n const yearRange = b >= a && b <= new Date().getFullYear()\n return (g(x, \"sep\").trim() === \"-\" && !yearRange) || has(x, \"phone\")\n },\n mask: [\"last\"],\n },\n ],\n // 운전면허번호 — 지역코드(11~28)-연도-일련6-검증2 / 구형 지역명 + 2-6-2. 뒤 8자리 마스크\n driver: [\n { // 신형 — 무문맥이지만 비개인 식별번호 문맥(\"(접수)\" 등)이면 아님\n re: /(?<![\\w-])(?<r>1[1-9]|2[0-8])-(?<y>\\d{2})-(?<s>\\d{6})-(?<c>\\d{2})(?![\\w-])/dg,\n ok: (x) => has(x, \"driver\") || !otherCtx(x, \"driver\"),\n mask: [\"s\", \"c\"],\n },\n {\n re: /(?<rn>서울|부산|경기|강원|충북|충남|전북|전남|경북|경남|제주|대구|인천|광주|대전|울산) ?(?<y>\\d{2})-(?<s>\\d{6})-(?<c>\\d{2})(?![\\w-])/dg,\n mask: [\"s\", \"c\"],\n },\n {\n re: /(?<![\\w@.-])(?<r>1[1-9]|2[0-8])(?<y>\\d{2})(?<s>\\d{6})(?<c>\\d{2})(?![\\w@-])/dg,\n needs: \"driver\",\n mask: [\"s\", \"c\"],\n },\n ],\n // 여권번호 — 신형 복수여권(M123A4567)은 무문맥, 그 밖의 종류 글자(S·R·O·D·G)와 구형(M12345678)은 여권 라벨\n // 필요 (\"S123A4567 부품\" 같은 제품 코드). 첫 글자만 남김\n passport: [\n {\n re: /(?<![A-Za-z0-9])(?<l>[MSRODG])(?<num>\\d{3}[A-Z]\\d{4})(?![A-Za-z0-9])/dg,\n ok: (x) => has(x, \"passport\") || (g(x, \"l\") === \"M\" && !otherCtx(x, \"passport\")),\n mask: [\"num\"],\n },\n {\n re: /(?<![A-Za-z0-9])(?<l>[MSRODG])(?<num>\\d{8})(?![A-Za-z0-9])/dg,\n needs: \"passport\",\n mask: [\"num\"],\n },\n { // 소문자로 친 여권번호(m123a4567) — 여권 라벨이 있을 때만\n re: /(?<![A-Za-z0-9])(?<l>[msrodg])(?<num>\\d{3}[a-z]\\d{4}|\\d{8})(?![A-Za-z0-9])/dg,\n needs: \"passport\",\n mask: [\"num\"],\n },\n ],\n // 계좌번호 — 하이픈 3~4그룹, 총 11~16자리 (10자리는 계좌 라벨 필요). 마지막 그룹 빼고 마스크.\n // 무구분 10~14자리는 계좌·은행 라벨이 있을 때만 (뒤 4자리 유지)\n account: [\n {\n // 마지막 그룹 1자리 허용 — 새마을금고 9002-1234-5678-9, 3-2-6-1·3-3-6-1 형식\n re: /(?<![\\w-])(?<!\\d\\.)(?<head>\\d{2,6}(?:-\\d{2,6}){1,2})-(?<last>\\d{1,8})(?![\\w-])(?!\\.\\d)/dg,\n ok: (x) => {\n const s = x.m[0]\n const d = digitsOf(s)\n const acct = has(x, \"account\")\n if (allSameDigit(d) || (!acct && otherCtx(x, \"account\"))) return false\n // 쉼표 숫자 목록 안(1,234-…)은 계좌 라벨이 있을 때만 (\"계좌 110-…,110-…\" 나열)\n if (!acct && (/\\d,$/.test(before(x)) || /^,\\d/.test(after(x, 2)))) return false\n if (/^\\d+(?:-0+)+$/.test(s)) return false // 010-0000-0000·000-00-00000 류 자리표시자\n if (d.length < 10 || d.length > 16 || (d.length === 10 && !acct)) return false\n if (/^\\s*원/.test(after(x, 3))) return false // 금액 표기 뒤 \"원\"\n if (acct) return true\n if (/^(?:10|20|30|40)-(?:19|20)\\d\\d-\\d{6,7}$/.test(s)) return false // 특허·상표·디자인 출원번호\n // 연도로 시작하는 날짜 부호 일련번호(2024-01-15-1234, 2024-0115-123456, 2023-10-123456)\n if (/^(?:19|20)\\d\\d-(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\\d|3[01])?-/.test(s)) return false\n if (s.split(\"-\").filter((p) => /^(?:19|20)\\d\\d$/.test(p)).length >= 2) return false // 연도 나열 2019-2020-2021\n if (/^\\d{3}-\\d{2}-\\d{5}$/.test(s) && brnChecksumValid(d)) return false // 사업자번호 몫\n return true\n },\n mask: [\"head\"],\n },\n {\n re: /(?<![\\w@.-])(?<!\\d,)(?<head>\\d{6,10})(?<last>\\d{4})(?![\\w@-])(?!,\\d)/dg,\n needs: \"account\",\n ok: (x) => !allSameDigit(x.m[0]),\n mask: [\"head\"],\n },\n ],\n // IPv4 (opt-in) — 옥텟 0~255, 첫 옥텟 10 이상(1.2.3.4 절 번호·버전 제외) 또는 IP 라벨. 뒤 두 옥텟 마스크\n ip: [\n {\n re: /(?<![\\w.])(?<a>\\d{1,3})\\.(?<b>\\d{1,3})\\.(?<c>\\d{1,3})\\.(?<d>\\d{1,3})(?![\\w])(?!\\.\\d)/dg,\n ok: (x) => {\n const oct = [g(x, \"a\"), g(x, \"b\"), g(x, \"c\"), g(x, \"d\")]\n if (oct.some((o) => Number(o) > 255 || (o.length > 1 && o[0] === \"0\"))) return false\n return Number(oct[0]) >= 10 || has(x, \"ip\")\n },\n mask: [\"c\", \"d\"],\n },\n ],\n}\n","/**\n * 한국 공문서 PII(개인정보) 탐지·마스킹 순수 로직.\n *\n * 텍스트 in → 마스킹된 텍스트 + 히트 리포트 out. 문서 파일 단위 마스킹(머리말·각주·미리보기·메타데이터\n * 포함)은 redact-doc.ts 가 이 엔진을 불러 쓴다.\n *\n * 원칙:\n * - 서식 보존 마스킹 — 자릿수·구분자를 유지해 마스킹 전후 길이(UTF-16 단위)가 동일\n * - 히트 리포트에 원본 PII를 절대 담지 않는다 (`masked` 필드만 존재)\n * - 탐지는 같은 길이로 정규화한 그림자 문자열에서 한다 — 전각 숫자·영문·@, 유니코드 대시(‐‑–—−-·─·ㅡ),\n * NBSP·전각 공백을 ASCII 로 1:1 치환하므로 오프셋이 그대로이고, 마스킹은 원문 글자에 적용한다\n * - 번호 모양만으로 모호한 형태(구분자 없는 13자리·10~14자리, 여권 구형 등)는 바로 앞 라벨\n * (\"주민등록번호\", \"계좌\", \"여권\" …) 이나 표 열 머리글이 해당 유형일 때만 잡는다. 가장 가까운\n * 라벨이 이긴다 — \"법인등록번호\" 옆 6-7 번호는 주민번호로 보지 않는다\n * - 체크섬: 카드 Luhn, 사업자·법인등록번호 가중합. 주민·외국인등록번호는 2020-10 이후 뒷자리가\n * 임의 번호라 체크섬으로 거르지 않고 생년월일(세기 포함)·성별 자리만 검증한다\n * - 룰 우선순위 겹침 처리 — 우선순위순으로 매치를 수집하고, 이미 점유된 구간과 겹치는 하위 룰\n * 매치는 스킵 (RULE_PRIORITY 참조)\n * - 정규식은 모듈 로드 시 1회 컴파일 (matchAll은 내부 클론이라 lastIndex 안전)\n *\n * 룰별 근거·오탐 실측은 bench/redact-bench.mjs (합성 정답 셋 + 실코퍼스) 참조.\n */\n\nimport { RULES, RULE_PRIORITY, labelsBefore, labelsIn, type Ctx, type Match, type Variant } from \"./redact-rules.js\"\n\nexport type RedactRule =\n | \"rrn\" | \"phone\" | \"email\" | \"card\" | \"account\" | \"passport\" | \"driver\" | \"brn\" | \"crn\" | \"ip\"\n\nexport interface RedactHit {\n rule: RedactRule\n /** 마스킹 후 문자열 — 원본 PII는 리포트에 담지 않는다 */\n masked: string\n /** 원문 내 시작 오프셋 (UTF-16 단위) */\n index: number\n /** 매치 길이 (서식 보존이라 마스킹 전후 동일) */\n length: number\n}\n\nexport interface RedactTextResult {\n text: string\n hits: RedactHit[]\n}\n\nexport interface RedactOptions {\n /** 적용할 룰 (기본: DEFAULT_REDACT_RULES — crn·ip는 기본 OFF) */\n rules?: readonly RedactRule[]\n /** 마스크 문자 — 1글자(UTF-16 1단위). 영숫자·공백·제어문자·마크다운/XML 특수문자(|\\<>&\"')는 금지. 기본 \"●\" */\n maskChar?: string\n}\n\n/**\n * 기본 적용 룰. 실코퍼스 2,303문서 오탐 실측 근거(bench/redact-bench.mjs --corpus):\n * - brn(사업자등록번호)은 종전 account 가 잡던 것을 체크섬으로 분리 — 기본 마스킹 범위 그대로\n * - passport 는 신형(M123A4567)만 무문맥, 구형(M12345678)은 \"여권\" 라벨이 있을 때만 → 기본 ON\n * - driver 는 지역코드(11~28)·지역명 검증으로 좁혔고 종전에도 account 로 가려지던 모양 → 기본 ON\n * - crn(법인등록번호)은 개인정보가 아니고, ip 는 \"1.2.3.4\" 절 번호·버전과 겹쳐 opt-in\n */\nexport const DEFAULT_REDACT_RULES: readonly RedactRule[] = [\n \"rrn\", \"phone\", \"email\", \"card\", \"account\", \"brn\", \"passport\", \"driver\",\n]\n\n/** 엔진이 아는 전체 룰 (CLI/MCP 입력 검증·벤치용) */\nexport const ALL_REDACT_RULES: readonly RedactRule[] = [...DEFAULT_REDACT_RULES, \"crn\", \"ip\"]\n\n// ─── 정규화 (같은 길이) ───────────────────────────────\n\n// 대시 모양 — 유니코드 하이픈·대시류 + 한글 문서에서 대시로 쓰는 괘선(─ ━)·한글 모음 ㅡ·장음 ー\nconst DASHES = new Set([0x2010, 0x2011, 0x2012, 0x2013, 0x2014, 0x2015, 0x2212, 0xfe58, 0xfe63, 0xff0d, 0x2500, 0x2501, 0x3161, 0x30fc])\nconst SPACES = new Set([0x00a0, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, 0x2007, 0x2008, 0x2009, 0x200a, 0x202f, 0x3000])\n\n/** 탐지용 그림자 문자열 — 모든 치환이 UTF-16 1단위 → 1단위라 오프셋이 원문과 같다 */\nexport function normalizeForDetect(text: string): string {\n let out = \"\"\n let changed = false\n for (let i = 0; i < text.length; i++) {\n const c = text.charCodeAt(i)\n let r = c\n if (c >= 0xff01 && c <= 0xff5e) r = c - 0xfee0 // 전각 ASCII(!~~) → 반각\n if (DASHES.has(c)) r = 0x2d\n else if (SPACES.has(c)) r = 0x20\n if (r !== c) changed = true\n out += r === c ? text[i] : String.fromCharCode(r)\n }\n return changed ? out : text\n}\n\n/**\n * 매치 원문에 이름 그룹 범위를 maskChar 로 치환한 문자열 (길이 동일). 영숫자 판정은 정규화 텍스트로\n * 해서 전각 숫자도 가린다. maskAll 이면 구분자까지 전부.\n */\nfunction maskMatch(orig: string, x: Match, v: Variant, maskChar: string): string {\n const base = x.m.index as number\n const norm = x.m[0]\n const chars = orig.split(\"\")\n const idx = x.m.indices?.groups\n for (const name of v.mask) {\n const r = idx?.[name]\n if (!r) continue\n for (let i = r[0] - base; i < r[1] - base; i++) {\n if (v.maskAll || /[0-9A-Za-z]/.test(norm[i])) chars[i] = maskChar\n }\n }\n return chars.join(\"\")\n}\n\n/**\n * 마스크 문자 검증 — 1글자(UTF-16 1단위). 금지: 영숫자(전각 포함 — 정규화하면 숫자라 재탐지됨), 공백·제어·\n * 서식·결합 문자·비문자(U+FFFE/FFFF 등 \\\\p{Z}\\\\p{C}\\\\p{M}), 표·태그를 깨는 |\\\\<>&\"', 번호·주소 구분자\n * ._-@+:/,; (가린 결과가 다시 번호·이메일 모양이 되어 재검사 잔존으로 잡힌다)\n */\nfunction assertMaskChar(maskChar: string): void {\n const n = normalizeForDetect(maskChar)\n if (\n maskChar.length !== 1 || /[0-9A-Za-z|\\\\<>&\"'._\\-@+:/,;]/.test(n)\n || /[\\p{Z}\\p{C}\\p{M}]/u.test(maskChar) || /[\\uFDD0-\\uFDEF\\uFFFE\\uFFFF]/.test(maskChar)\n ) {\n throw new Error(`maskChar는 영숫자·공백·제어/결합 문자·구분 기호(|\\\\<>&\"'._-@+:/,;)가 아닌 보이는 1글자여야 함: ${JSON.stringify(maskChar)}`)\n }\n}\n\n/** 나열 구분자 — 앞 값과 이 값 사이가 이것뿐이면 같은 라벨 아래 목록이다 (\"계좌 110-…, 110-…\") */\nconst LIST_SEP_RE = /^\\s{0,2}(?:[,;/·、]|및|또는)?\\s{0,2}$/\n\n/**\n * 탐지 코어 — headerAt 은 오프셋 → 그 위치 표 열 머리글 텍스트 (redactMarkdown 이 표에서 제공).\n * 인라인 라벨이 없으면 (1) 바로 앞에 나열 구분자로 이어진 값이 있을 때 그 값의 라벨과 룰을 잇고,\n * (2) 그래도 없으면 머리글 라벨 집합을 쓴다.\n */\nfunction detect(\n text: string, rules: readonly RedactRule[], maskChar: string, headerAt?: (index: number) => string | undefined,\n): RedactHit[] {\n const norm = normalizeForDetect(text)\n const hits: RedactHit[] = []\n const occupied: Array<{ start: number; end: number; ctx: () => ReadonlySet<Ctx>; rule: RedactRule }> = []\n for (const rule of RULE_PRIORITY) {\n if (!rules.includes(rule)) continue\n for (const v of RULES[rule]) {\n for (const m of norm.matchAll(v.re)) {\n const start = m.index as number\n const end = start + m[0].length\n if (occupied.some((o) => start < o.end && end > o.start)) continue\n let cached: ReadonlySet<Ctx> | undefined\n const ctx = (): ReadonlySet<Ctx> => {\n if (!cached) {\n let c: ReadonlySet<Ctx> = labelsBefore(norm, start)\n if (c.size === 0) {\n const prev = occupied.find((o) => o.end < start && start - o.end <= 6 && LIST_SEP_RE.test(norm.slice(o.end, start)))\n // 앞 값의 라벨 + 앞 값이 무엇이었는지 자체가 문맥 (주민번호 다음 무구분 13자리도 주민번호)\n if (prev) c = prev.rule === \"email\" ? prev.ctx() : new Set<Ctx>([...prev.ctx(), prev.rule])\n }\n if (c.size === 0 && headerAt) {\n const h = headerAt(start)\n if (h) c = labelsIn(normalizeForDetect(h))\n }\n cached = c\n }\n return cached\n }\n const x: Match = { m, ctx }\n if (v.needs && !ctx().has(v.needs)) continue\n if (v.ok && !v.ok(x)) continue\n occupied.push({ start, end, ctx, rule })\n hits.push({ rule, masked: maskMatch(text.slice(start, end), x, v, maskChar), index: start, length: end - start })\n }\n }\n }\n return hits.sort((a, b) => a.index - b.index)\n}\n\nfunction applyHits(text: string, hits: readonly RedactHit[]): string {\n let out = \"\"\n let cursor = 0\n for (const h of hits) {\n out += text.slice(cursor, h.index) + h.masked\n cursor = h.index + h.length\n }\n return out + text.slice(cursor)\n}\n\n/**\n * 텍스트에서 PII를 탐지해 서식 보존 마스킹.\n *\n * @param text - 대상 텍스트 (마크다운 포함)\n * @param options - 룰 선택·마스크 문자 (기본: DEFAULT_REDACT_RULES, \"●\")\n * @returns 마스킹된 텍스트 + 히트 리포트 (index 오름차순, 원본 PII 미포함)\n */\nexport function redactText(text: string, options?: RedactOptions): RedactTextResult {\n const maskChar = options?.maskChar ?? \"●\"\n assertMaskChar(maskChar)\n const rules = options?.rules ?? DEFAULT_REDACT_RULES\n if (text === \"\" || rules.length === 0) return { text, hits: [] }\n const hits = detect(text, rules, maskChar)\n return { text: applyHits(text, hits), hits }\n}\n\n// ─── 마크다운 표 머리글 문맥 ──────────────────────────\n\n/** GFM 행의 셀 경계(이스케이프 안 된 파이프) 위치 */\nfunction pipePositions(line: string): number[] {\n const out: number[] = []\n for (let i = 0; i < line.length; i++) {\n if (line[i] === \"|\" && line[i - 1] !== \"\\\\\") out.push(i)\n }\n return out\n}\n\nconst isGfmRow = (l: string): boolean => /^\\s*\\|.*\\|\\s*$/.test(l)\nconst isGfmSep = (l: string): boolean => /^\\s*\\|(?:\\s*:?-{3,}:?\\s*\\|)+\\s*$/.test(l)\n\ninterface HtmlCell { start: number; end: number; col: number; span: number; text: string }\n\n/** HTML 표 행 한 줄의 셀 목록 (colspan 반영 열 번호) */\nfunction htmlCells(line: string): HtmlCell[] {\n const out: HtmlCell[] = []\n let col = 0\n for (const m of line.matchAll(/<t([dh])\\b([^>]*)>([\\s\\S]*?)<\\/t\\1>/gi)) {\n const span = Math.max(1, Number(m[2].match(/colspan=\"?(\\d+)/i)?.[1] ?? 1))\n out.push({ start: m.index as number, end: (m.index as number) + m[0].length, col, span, text: m[3].replace(/<[^>]*>/g, \" \") })\n col += span\n }\n return out\n}\n\n/**\n * 줄 번호 → (줄 내 오프셋 → 열 머리글 텍스트). GFM 표는 첫 행, HTML 표는 첫 <tr> 을 머리글로 본다\n * (병합·중첩 표에서는 근사 — 인라인 라벨이 없을 때만 쓰이는 보조 문맥).\n */\nfunction tableHeaderContext(lines: string[]): Map<number, (offset: number) => string | undefined> {\n const out = new Map<number, (offset: number) => string | undefined>()\n for (let i = 0; i < lines.length; i++) {\n // GFM\n if (isGfmRow(lines[i]) && i + 1 < lines.length && isGfmSep(lines[i + 1])) {\n const head = lines[i]\n const hp = pipePositions(head)\n const headers = hp.slice(0, -1).map((p, k) => head.slice(p + 1, hp[k + 1]))\n let j = i + 2\n for (; j < lines.length && isGfmRow(lines[j]); j++) {\n const rp = pipePositions(lines[j])\n out.set(j, (off) => {\n let k = -1\n for (const p of rp) { if (p < off) k++; else break }\n return k >= 0 ? headers[k] : undefined\n })\n }\n i = j - 1\n continue\n }\n // HTML — 표 시작 줄부터 </table> 까지, 첫 <tr> 이 머리글\n if (/<table\\b/i.test(lines[i])) {\n let headers: HtmlCell[] | null = null\n let depth = 0\n for (let j = i; j < lines.length; j++) {\n depth += (lines[j].match(/<table\\b/gi) ?? []).length\n if (/<tr\\b/i.test(lines[j]) && depth === 1) {\n const cells = htmlCells(lines[j])\n if (!headers) headers = cells\n else if (cells.length > 0) {\n const hdr = headers\n out.set(j, (off) => {\n const c = cells.find((x) => off >= x.start && off < x.end)\n if (!c) return undefined\n const h = hdr.find((x) => c.col >= x.col && c.col < x.col + x.span)\n return h?.text\n })\n }\n }\n depth -= (lines[j].match(/<\\/table>/gi) ?? []).length\n if (depth <= 0) { i = j; break }\n }\n }\n }\n return out\n}\n\n/**\n * 인라인 서식 표지 — 굵게·취소선·밑줄/HTML 강조 태그·백슬래시 이스케이프. 번호 한가운데 끼어도\n * (run 경계에서 굵게가 바뀐 \"010-98**76-5431**\", 이스케이프된 가림표 \"900101-1\\\\*\\\\*…\") 탐지되게\n * 지운 사본에서 찾고, 마스킹은 원문 글자 자리에만 입힌다 (표지는 그대로).\n * 항상 표지: ** __ ~~ (3개 이상 연속은 가림표일 수 있어 제외), 속성 없는 인라인 태그(href 속 값은 탐지\n * 대상으로 남긴다), 백슬래시 이스케이프.\n */\nconst MD_ALWAYS_MARKER_RE = new RegExp([\n \"(?<!\\\\*)\\\\*\\\\*(?!\\\\*)\", \"(?<!_)__(?!_)\", \"(?<!~)~~(?!~)\",\n \"<\\\\/?(?:u|b|i|s|em|strong|sup|sub|del|span|mark|small|big|ins|strike|tt|code|kbd|samp|var|q|cite|abbr|dfn)>\",\n \"\\\\\\\\(?=[\\\\\\\\`*_{}[\\\\]()#+\\\\-.!|~>])\",\n].join(\"|\"), \"g\")\n/**\n * 짝지어야 표지 — 홑 * · 홑 _ · *** 기울임은 단어 경계의 여는 표지와 닫는 표지가 한 줄 안에서 짝을 이룰 때만.\n * 짝 없는 것은 가림표(900101-1694***, 010-****-5678)일 수 있어 둔다.\n */\nconst MD_OPEN_RE = /(?<![\\w*\\\\])(\\*\\*\\*|\\*|_)(?=[0-9A-Za-z0-9])/g\nconst MD_CLOSE_RE = /(?<=[0-9A-Za-z0-9])(\\*\\*\\*|\\*|_)(?![\\w*])/g\n\n/** 마크다운 한 줄의 인라인 서식 표지 범위 [시작, 끝) — 오름차순 */\nexport function mdMarkerRanges(line: string): Array<[number, number]> {\n const out: Array<[number, number]> = []\n for (const m of line.matchAll(MD_ALWAYS_MARKER_RE)) out.push([m.index as number, (m.index as number) + m[0].length])\n const taken = (a: number, b: number): boolean => out.some(([s, e]) => a < e && b > s)\n const closes = [...line.matchAll(MD_CLOSE_RE)].filter((m) => !taken(m.index as number, (m.index as number) + m[0].length))\n let from = 0\n for (const o of line.matchAll(MD_OPEN_RE)) {\n const os = o.index as number\n const oe = os + o[0].length\n if (os < from || taken(os, oe)) continue\n const c = closes.find((m) => (m.index as number) >= oe && m[0] === o[0])\n if (!c) continue\n out.push([os, oe], [c.index as number, (c.index as number) + c[0].length])\n from = (c.index as number) + c[0].length\n }\n return out.sort((a, b) => a[0] - b[0])\n}\n\n/** 마크다운 한 줄 탐지 — 인라인 표지를 지운 사본에서 찾아 원문 오프셋·마스킹으로 되돌린다 */\nfunction detectMarkdownLine(\n line: string, rules: readonly RedactRule[], maskChar: string, headerAt?: (index: number) => string | undefined,\n): RedactHit[] {\n const markers = mdMarkerRanges(line)\n if (markers.length === 0) return detect(line, rules, maskChar, headerAt)\n let clean = \"\"\n const map: number[] = []\n let cursor = 0\n const keep = (to: number): void => {\n for (let i = cursor; i < to; i++) { clean += line[i]; map.push(i) }\n }\n for (const [ms, me] of markers) {\n keep(ms)\n cursor = me\n }\n keep(line.length)\n const hits = detect(clean, rules, maskChar, headerAt ? (i) => headerAt(map[i] ?? i) : undefined)\n return hits.map((h) => {\n const start = map[h.index]\n const end = map[h.index + h.length - 1] + 1\n const chars = line.slice(start, end).split(\"\")\n for (let k = 0; k < h.length; k++) {\n if (h.masked[k] !== clean[h.index + k]) chars[map[h.index + k] - start] = maskChar\n }\n return { rule: h.rule, masked: chars.join(\"\"), index: start, length: end - start }\n })\n}\n\n/**\n * 마크다운 문서 전용 래퍼 — base64 이미지(data URI) 라인은 마스킹에서 제외한다.\n * base64 페이로드의 숫자열이 phone 등에 오탐되면 이미지가 깨지기 때문.\n * 표 셀은 열 머리글(\"주민등록번호\"·\"계좌번호\" …)을 라벨 문맥으로 쓰고, 인라인 서식 표지(**·\\\\ 이스케이프 등)가\n * 번호를 갈라도 잡는다. hits의 index는 문서 전체 기준 절대 오프셋으로 환산된다 (masked 에는 표지가 그대로).\n */\nexport function redactMarkdown(markdown: string, options?: RedactOptions): RedactTextResult {\n const maskChar = options?.maskChar ?? \"●\"\n assertMaskChar(maskChar)\n const rules = options?.rules ?? DEFAULT_REDACT_RULES\n const lines = markdown.split(\"\\n\")\n const headers = tableHeaderContext(lines)\n const hits: RedactHit[] = []\n let offset = 0\n const outLines = lines.map((line, i) => {\n if (line.includes(\"data:image/\") || line === \"\" || rules.length === 0) {\n offset += line.length + 1\n return line\n }\n const lineHits = detectMarkdownLine(line, rules, maskChar, headers.get(i))\n for (const h of lineHits) hits.push({ ...h, index: h.index + offset })\n offset += line.length + 1\n return applyHits(line, lineHits)\n })\n return { text: outLines.join(\"\\n\"), hits }\n}\n"],"mappings":";;;AAYO,IAAM,gBAAuC;AAAA,EAClD;AAAA,EAAO;AAAA,EAAO;AAAA,EAAS;AAAA,EAAQ;AAAA,EAAO;AAAA,EAAS;AAAA,EAAU;AAAA,EAAY;AAAA,EAAW;AAClF;AAKA,SAAS,UAAU,QAAyB;AAC1C,MAAI,MAAM;AACV,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,QAAI,IAAI,OAAO,WAAW,OAAO,SAAS,IAAI,CAAC,IAAI;AACnD,QAAI,IAAI,MAAM,GAAG;AACf,WAAK;AACL,UAAI,IAAI,EAAG,MAAK;AAAA,IAClB;AACA,WAAO;AAAA,EACT;AACA,SAAO,MAAM,OAAO;AACtB;AAEA,IAAM,gBAAgB,CAAC,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,EAAE;AAMrE,SAAS,eAAe,QAAgB,aAA8B;AACpE,QAAM,KAAK,OAAO,OAAO,MAAM,GAAG,CAAC,CAAC;AACpC,QAAM,KAAK,OAAO,OAAO,MAAM,GAAG,CAAC,CAAC;AACpC,QAAM,KAAK,OAAO,OAAO,MAAM,GAAG,CAAC,CAAC;AACpC,MAAI,KAAK,KAAK,KAAK,MAAM,KAAK,EAAG,QAAO;AACxC,QAAMA,KAAI,OAAO,WAAW;AAC5B,QAAM,QAAQA,OAAM,KAAKA,OAAM,KAAKA,OAAM,KAAKA,OAAM,IAAI,MAAO,QAAQ;AACxE,MAAI,QAAO,oBAAI,KAAK,GAAE,YAAY,EAAG,QAAO;AAC5C,QAAM,OAAO,OAAO,MAAM,MAAM,OAAO,QAAQ,KAAK,OAAO,QAAQ;AACnE,QAAM,MAAM,OAAO,IAAK,OAAO,KAAK,KAAM,cAAc,KAAK,CAAC;AAC9D,SAAO,MAAM;AACf;AAGA,SAAS,iBAAiB,GAAoB;AAC5C,QAAM,IAAI,CAAC,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AACpC,MAAI,MAAM;AACV,WAAS,IAAI,GAAG,IAAI,GAAG,IAAK,QAAO,EAAE,CAAC,KAAK,EAAE,WAAW,CAAC,IAAI;AAC7D,SAAO,KAAK,OAAQ,EAAE,WAAW,CAAC,IAAI,MAAM,IAAK,EAAE;AACnD,UAAQ,KAAM,MAAM,MAAO,OAAO,EAAE,WAAW,CAAC,IAAI;AACtD;AAGA,SAAS,iBAAiB,GAAoB;AAC5C,MAAI,MAAM;AACV,WAAS,IAAI,GAAG,IAAI,IAAI,IAAK,SAAQ,IAAI,MAAM,IAAI,IAAI,MAAM,EAAE,WAAW,CAAC,IAAI;AAC/E,UAAQ,KAAM,MAAM,MAAO,OAAO,EAAE,WAAW,EAAE,IAAI;AACvD;AAEA,IAAM,WAAW,CAAC,MAAsB,EAAE,QAAQ,OAAO,EAAE;AAE3D,IAAM,eAAe,CAAC,MAAuB,EAAE,SAAS,KAAK,YAAY,KAAK,CAAC;AAW/E,IAAM,SAAgD;AAAA,EACpD,CAAC,OAAO,6BAA6B;AAAA,EACrC,CAAC,OAAO,+IAA+I;AAAA,EACvJ,CAAC,OAAO,wEAAwE;AAAA,EAChF,CAAC,WAAW,8GAA8G;AAAA,EAC1H,CAAC,QAAQ,4CAA4C;AAAA,EACrD,CAAC,SAAS,sIAAsI;AAAA,EAChJ,CAAC,YAAY,kBAAkB;AAAA,EAC/B,CAAC,UAAU,uDAAuD;AAAA,EAClE,CAAC,MAAM,mBAAmB;AAAA,EAC1B,CAAC,SAAS,wSAAwS;AACpT;AAGA,IAAM,cAAc;AAEpB,IAAM,gBAAgB;AAGf,SAAS,SAAS,GAAqB;AAC5C,QAAM,MAAM,oBAAI,IAAS;AACzB,aAAW,CAAC,KAAK,EAAE,KAAK,QAAQ;AAC9B,OAAG,YAAY;AACf,QAAI,GAAG,KAAK,CAAC,EAAG,KAAI,IAAI,GAAG;AAAA,EAC7B;AACA,SAAO;AACT;AAGO,SAAS,aAAa,MAAc,OAAyB;AAClE,MAAI,MAAM,KAAK,MAAM,KAAK,IAAI,GAAG,QAAQ,GAAG,GAAG,KAAK;AACpD,QAAM,OAAO,CAAC,GAAG,IAAI,SAAS,WAAW,CAAC;AAC1C,MAAI,KAAK,UAAU,GAAG;AACpB,UAAM,IAAI,KAAK,KAAK,SAAS,CAAC;AAC9B,UAAM,IAAI,MAAO,EAAE,QAAmB,EAAE,CAAC,EAAE,MAAM;AAAA,EACnD;AACA,QAAM,IAAI,QAAQ,YAAY,GAAG,EAAE,MAAM,GAAG;AAC5C,QAAM,OAAO,CAAC,GAAG,IAAI,SAAS,aAAa,CAAC,EAAE,IAAI;AAClD,MAAI,KAAM,OAAM,IAAI,MAAO,KAAK,QAAmB,KAAK,CAAC,EAAE,MAAM;AACjE,SAAO,SAAS,GAAG;AACrB;AAGA,IAAM,iBAAiB;AAuBvB,IAAM,IAAI,CAAC,GAAU,SAAyB,EAAE,EAAE,SAAS,IAAI,KAAK;AACpE,IAAM,MAAM,CAAC,GAAU,MAAoB,EAAE,IAAI,EAAE,IAAI,CAAC;AAExD,IAAM,UAAU,CAAC,MAAsB,IAAI,GAAG,KAAK,KAAK,CAAC,IAAI,GAAG,KAAK;AAErE,IAAM,QAAQ,CAAC,GAAU,IAAI,OAAe;AAC1C,QAAM,MAAO,EAAE,EAAE,QAAmB,EAAE,EAAE,CAAC,EAAE;AAC3C,SAAO,EAAE,EAAE,MAAO,MAAM,KAAK,MAAM,CAAC;AACtC;AACA,IAAM,SAAS,CAAC,GAAU,IAAI,MAAc;AAC1C,QAAM,QAAQ,EAAE,EAAE;AAClB,SAAO,EAAE,EAAE,MAAO,MAAM,KAAK,IAAI,GAAG,QAAQ,CAAC,GAAG,KAAK;AACvD;AAEA,IAAM,WAAW,CAAC,GAAU,QAAuB,IAAI,GAAG,OAAO,KAAK,CAAC,IAAI,GAAG,GAAG,KAAM,eAAe,KAAK,MAAM,CAAC,CAAC;AAKnH,IAAM,mBAAmB;AACzB,IAAM,eAAe,CAAC,MAAsB,iBAAiB,KAAK,OAAO,GAAG,EAAE,CAAC,KAAK,CAAC,IAAI,GAAG,OAAO;AAGnG,IAAM,eAAe;AACrB,IAAM,aAAa;AAKnB,IAAM,YAAY;AAElB,IAAM,YAAY;AAGlB,SAAS,YAAY,GAAU,IAAY,IAAqB;AAC9D,MAAK,OAAO,QAAS,OAAO,IAAK,QAAO;AACxC,MAAK,OAAO,SAAU,OAAO,KAAM,QAAO;AAC1C,SAAO,OAAO,OAAO,CAAC,QAAQ,KAAK,MAAM,GAAG,CAAC,CAAC;AAChD;AAOA,SAAS,cAAc,KAAa,KAAa,MAAc,MAAwB;AACrF,MAAI,OAAO,KAAK,MAAM,IAAI,EAAG,QAAO;AACpC,SAAO,CAAC,QAAQ,CAAC,uCAAuC,KAAK,GAAG,KAAK,IAAI,CAAC,MAAM;AAClF;AAIA,IAAM,cAAc;AACpB,IAAM,eAAe;AACrB,IAAM,eAAe;AAEd,IAAM,QAAgD;AAAA;AAAA,EAE3D,KAAK;AAAA,IACH;AAAA;AAAA;AAAA,MAEE,IAAI;AAAA,MACJ,IAAI,CAAC,MAAM,eAAe,EAAE,GAAG,OAAO,GAAG,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC;AAAA,MACvE,MAAM,CAAC,MAAM;AAAA,IACf;AAAA,IACA;AAAA;AAAA,MACE,IAAI;AAAA,MACJ,IAAI,CAAC,MAAM,aAAa,KAAK,EAAE,GAAG,MAAM,CAAC,KAAK,eAAe,EAAE,GAAG,OAAO,GAAG,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC;AAAA,MAC1G,MAAM,CAAC,MAAM;AAAA,IACf;AAAA,IACA;AAAA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,IAAI,CAAC,MAAM,eAAe,EAAE,GAAG,OAAO,GAAG,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;AAAA,MACxD,MAAM,CAAC,MAAM;AAAA,IACf;AAAA,EACF;AAAA;AAAA,EAEA,KAAK;AAAA,IACH;AAAA,MACE,IAAI;AAAA,MACJ,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,GAAG,OAAO,IAAI,EAAE,GAAG,MAAM,CAAC,MAAM,QAAQ,CAAC,KAC9D,EAAE,GAAG,MAAM,EAAE,CAAC,MAAM,OAAO,iBAAiB,EAAE,GAAG,OAAO,IAAI,EAAE,GAAG,MAAM,CAAC;AAAA,MAC9E,MAAM,CAAC,MAAM;AAAA,IACf;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,GAAG,OAAO,IAAI,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,KAAK;AAAA,MACvE,MAAM,CAAC,MAAM;AAAA,IACf;AAAA,EACF;AAAA;AAAA;AAAA,EAGA,OAAO;AAAA,IACL;AAAA,MACE,IAAI,IAAI,OAAO,GAAG,WAAW,iDAAiD,YAAY,IAAI,IAAI;AAAA,MAClG,IAAI,CAAC,MAAM,CAAC,aAAa,KAAK,EAAE,GAAG,KAAK,CAAC;AAAA,MACzC,MAAM,CAAC,OAAO;AAAA,MACd,SAAS;AAAA,IACX;AAAA,IACA;AAAA;AAAA,MACE,IAAI,IAAI,OAAO,GAAG,WAAW,sBAAsB,YAAY,IAAI,IAAI;AAAA,MACvE,IAAI,CAAC,MAAM,CAAC,aAAa,KAAK,EAAE,GAAG,KAAK,CAAC;AAAA,MACzC,MAAM,CAAC,IAAI;AAAA,IACb;AAAA,EACF;AAAA;AAAA,EAEA,MAAM;AAAA,IACJ;AAAA;AAAA,MACE,IAAI;AAAA,MACJ,IAAI,CAAC,MAAM;AAAE,cAAM,IAAI,SAAS,EAAE,EAAE,CAAC,CAAC;AAAG,eAAO,UAAU,CAAC,KAAK,CAAC,aAAa,CAAC;AAAA,MAAE;AAAA,MACjF,MAAM,CAAC,MAAM,IAAI;AAAA,IACnB;AAAA,IACA;AAAA;AAAA,MACE,IAAI;AAAA,MACJ,IAAI,CAAC,MAAM,UAAU,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC;AAAA,MACrC,MAAM,CAAC,MAAM,KAAK;AAAA,IACpB;AAAA,IACA;AAAA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,IAAI,CAAC,MAAM;AAAE,cAAM,IAAI,SAAS,EAAE,EAAE,CAAC,CAAC;AAAG,eAAO,UAAU,CAAC,KAAK,CAAC,aAAa,CAAC;AAAA,MAAE;AAAA,MACjF,MAAM,CAAC,MAAM,IAAI;AAAA,IACnB;AAAA,IACA;AAAA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,IAAI,CAAC,MAAM;AAAE,cAAM,IAAI,SAAS,EAAE,EAAE,CAAC,CAAC;AAAG,eAAO,UAAU,CAAC,KAAK,CAAC,aAAa,CAAC;AAAA,MAAE;AAAA,MACjF,MAAM,CAAC,KAAK;AAAA,IACd;AAAA,EACF;AAAA;AAAA,EAEA,KAAK;AAAA,IACH;AAAA,MACE,IAAI;AAAA,MACJ,IAAI,CAAC,MAAM;AACT,cAAM,IAAI,SAAS,EAAE,EAAE,CAAC,CAAC;AACzB,YAAI,aAAa,CAAC,EAAG,QAAO;AAC5B,eAAO,IAAI,GAAG,KAAK,KAAM,iBAAiB,CAAC,KAAK,CAAC,IAAI,GAAG,SAAS,KAAK,CAAC,SAAS,GAAG,KAAK;AAAA,MAC1F;AAAA,MACA,MAAM,CAAC,GAAG;AAAA,IACZ;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,IAAI,CAAC,MAAM,CAAC,aAAa,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC;AAAA,MACzC,MAAM,CAAC,GAAG;AAAA,IACZ;AAAA,EACF;AAAA;AAAA;AAAA,EAGA,OAAO;AAAA,IACL;AAAA;AAAA,MACE,IAAI,IAAI,OAAO,uBAAuB,YAAY,UAAU,SAAS,2BAA2B,SAAS,oBAAoB,SAAS,IAAI,IAAI;AAAA,MAC9I,IAAI,CAAC,MAAM;AACT,cAAM,MAAM,EAAE,GAAG,KAAK;AACtB,cAAM,OAAO,EAAE,GAAG,IAAI,MAAM;AAC5B,YAAI,CAAC,YAAY,GAAG,EAAE,GAAG,IAAI,GAAG,EAAE,GAAG,IAAI,CAAC,EAAG,QAAO;AACpD,YAAI,CAAC,cAAc,KAAK,EAAE,GAAG,KAAK,GAAG,EAAE,GAAG,MAAM,GAAG,IAAI,EAAG,QAAO;AACjE,YAAI,aAAa,CAAC,EAAG,QAAO;AAC5B,YAAI,CAAC,KAAM,QAAO;AAElB,YAAI,QAAQ,KAAK,OAAO,CAAC,CAAC,EAAG,QAAO;AACpC,YAAI,IAAI,WAAW,IAAI,EAAG,QAAO;AACjC,eAAO,CAAC,OAAO,KAAK,OAAO,CAAC,CAAC,KAAK,CAAC,OAAO,KAAK,MAAM,GAAG,CAAC,CAAC;AAAA,MAC5D;AAAA,MACA,MAAM,CAAC,KAAK;AAAA,IACd;AAAA,IACA;AAAA;AAAA,MACE,IAAI,IAAI,OAAO,wBAAwB,UAAU,+BAA+B,SAAS,oBAAoB,SAAS,IAAI,IAAI;AAAA,MAC9H,IAAI,CAAC,MAAM,cAAc,EAAE,GAAG,KAAK,GAAG,EAAE,GAAG,KAAK,GAAG,EAAE,GAAG,MAAM,GAAG,KAAK,MAAM,EAAE,GAAG,IAAI,MAAM,OAAO,CAAC,QAAQ,KAAK,MAAM,GAAG,CAAC,CAAC;AAAA,MAC3H,MAAM,CAAC,KAAK;AAAA,IACd;AAAA,IACA;AAAA;AAAA,MACE,IAAI,IAAI,OAAO,mCAAmC,UAAU,+BAA+B,SAAS,mBAAmB,SAAS,IAAI,IAAI;AAAA,MACxI,IAAI,CAAC,MAAM,cAAc,EAAE,GAAG,KAAK,GAAG,EAAE,GAAG,KAAK,GAAG,EAAE,GAAG,MAAM,GAAG,KAAK,MAAM,EAAE,GAAG,IAAI,MAAM,OAAO,CAAC,QAAQ,KAAK,MAAM,GAAG,CAAC,CAAC;AAAA,MAC3H,MAAM,CAAC,KAAK;AAAA,IACd;AAAA,IACA;AAAA;AAAA,MACE,IAAI,IAAI,OAAO,iHAAiH,SAAS,2BAA2B,SAAS,oBAAoB,SAAS,IAAI,IAAI;AAAA,MAClN,IAAI,CAAC,MAAM,YAAY,GAAG,EAAE,GAAG,IAAI,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,cAAc,MAAM,EAAE,GAAG,KAAK,GAAG,EAAE,GAAG,KAAK,GAAG,EAAE,GAAG,MAAM,GAAG,EAAE,GAAG,IAAI,MAAM,EAAE;AAAA,MAClI,MAAM,CAAC,KAAK;AAAA,IACd;AAAA,IACA;AAAA;AAAA;AAAA,MAEE,IAAI,IAAI,OAAO,gHAAgH,SAAS,IAAI,IAAI;AAAA,MAChJ,IAAI,CAAC,MAAM;AACT,YAAI,EAAE,GAAG,MAAM,MAAM,UAAU,aAAa,CAAC,EAAG,QAAO;AACvD,YAAI,EAAE,GAAG,KAAK,MAAM,OAAO,QAAQ,KAAK,MAAM,GAAG,CAAC,CAAC,EAAG,QAAO;AAC7D,cAAM,IAAI,OAAO,EAAE,GAAG,KAAK,CAAC;AAC5B,cAAM,IAAI,OAAO,EAAE,GAAG,MAAM,CAAC;AAE7B,cAAM,YAAY,KAAK,KAAK,MAAK,oBAAI,KAAK,GAAE,YAAY;AACxD,eAAQ,EAAE,GAAG,KAAK,EAAE,KAAK,MAAM,OAAO,CAAC,aAAc,IAAI,GAAG,OAAO;AAAA,MACrE;AAAA,MACA,MAAM,CAAC,MAAM;AAAA,IACf;AAAA,EACF;AAAA;AAAA,EAEA,QAAQ;AAAA,IACN;AAAA;AAAA,MACE,IAAI;AAAA,MACJ,IAAI,CAAC,MAAM,IAAI,GAAG,QAAQ,KAAK,CAAC,SAAS,GAAG,QAAQ;AAAA,MACpD,MAAM,CAAC,KAAK,GAAG;AAAA,IACjB;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,MAAM,CAAC,KAAK,GAAG;AAAA,IACjB;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,MAAM,CAAC,KAAK,GAAG;AAAA,IACjB;AAAA,EACF;AAAA;AAAA;AAAA,EAGA,UAAU;AAAA,IACR;AAAA,MACE,IAAI;AAAA,MACJ,IAAI,CAAC,MAAM,IAAI,GAAG,UAAU,KAAM,EAAE,GAAG,GAAG,MAAM,OAAO,CAAC,SAAS,GAAG,UAAU;AAAA,MAC9E,MAAM,CAAC,KAAK;AAAA,IACd;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,MAAM,CAAC,KAAK;AAAA,IACd;AAAA,IACA;AAAA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,MAAM,CAAC,KAAK;AAAA,IACd;AAAA,EACF;AAAA;AAAA;AAAA,EAGA,SAAS;AAAA,IACP;AAAA;AAAA,MAEE,IAAI;AAAA,MACJ,IAAI,CAAC,MAAM;AACT,cAAM,IAAI,EAAE,EAAE,CAAC;AACf,cAAM,IAAI,SAAS,CAAC;AACpB,cAAM,OAAO,IAAI,GAAG,SAAS;AAC7B,YAAI,aAAa,CAAC,KAAM,CAAC,QAAQ,SAAS,GAAG,SAAS,EAAI,QAAO;AAEjE,YAAI,CAAC,SAAS,OAAO,KAAK,OAAO,CAAC,CAAC,KAAK,OAAO,KAAK,MAAM,GAAG,CAAC,CAAC,GAAI,QAAO;AAC1E,YAAI,gBAAgB,KAAK,CAAC,EAAG,QAAO;AACpC,YAAI,EAAE,SAAS,MAAM,EAAE,SAAS,MAAO,EAAE,WAAW,MAAM,CAAC,KAAO,QAAO;AACzE,YAAI,QAAQ,KAAK,MAAM,GAAG,CAAC,CAAC,EAAG,QAAO;AACtC,YAAI,KAAM,QAAO;AACjB,YAAI,0CAA0C,KAAK,CAAC,EAAG,QAAO;AAE9D,YAAI,4DAA4D,KAAK,CAAC,EAAG,QAAO;AAChF,YAAI,EAAE,MAAM,GAAG,EAAE,OAAO,CAAC,MAAM,kBAAkB,KAAK,CAAC,CAAC,EAAE,UAAU,EAAG,QAAO;AAC9E,YAAI,sBAAsB,KAAK,CAAC,KAAK,iBAAiB,CAAC,EAAG,QAAO;AACjE,eAAO;AAAA,MACT;AAAA,MACA,MAAM,CAAC,MAAM;AAAA,IACf;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;AAAA,MAC/B,MAAM,CAAC,MAAM;AAAA,IACf;AAAA,EACF;AAAA;AAAA,EAEA,IAAI;AAAA,IACF;AAAA,MACE,IAAI;AAAA,MACJ,IAAI,CAAC,MAAM;AACT,cAAM,MAAM,CAAC,EAAE,GAAG,GAAG,GAAG,EAAE,GAAG,GAAG,GAAG,EAAE,GAAG,GAAG,GAAG,EAAE,GAAG,GAAG,CAAC;AACvD,YAAI,IAAI,KAAK,CAAC,MAAM,OAAO,CAAC,IAAI,OAAQ,EAAE,SAAS,KAAK,EAAE,CAAC,MAAM,GAAI,EAAG,QAAO;AAC/E,eAAO,OAAO,IAAI,CAAC,CAAC,KAAK,MAAM,IAAI,GAAG,IAAI;AAAA,MAC5C;AAAA,MACA,MAAM,CAAC,KAAK,GAAG;AAAA,IACjB;AAAA,EACF;AACF;;;ACjXO,IAAM,uBAA8C;AAAA,EACzD;AAAA,EAAO;AAAA,EAAS;AAAA,EAAS;AAAA,EAAQ;AAAA,EAAW;AAAA,EAAO;AAAA,EAAY;AACjE;AAGO,IAAM,mBAA0C,CAAC,GAAG,sBAAsB,OAAO,IAAI;AAK5F,IAAM,SAAS,oBAAI,IAAI,CAAC,MAAQ,MAAQ,MAAQ,MAAQ,MAAQ,MAAQ,MAAQ,OAAQ,OAAQ,OAAQ,MAAQ,MAAQ,OAAQ,KAAM,CAAC;AACvI,IAAM,SAAS,oBAAI,IAAI,CAAC,KAAQ,MAAQ,MAAQ,MAAQ,MAAQ,MAAQ,MAAQ,MAAQ,MAAQ,MAAQ,MAAQ,KAAM,CAAC;AAGhH,SAAS,mBAAmB,MAAsB;AACvD,MAAI,MAAM;AACV,MAAI,UAAU;AACd,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,IAAI,KAAK,WAAW,CAAC;AAC3B,QAAI,IAAI;AACR,QAAI,KAAK,SAAU,KAAK,MAAQ,KAAI,IAAI;AACxC,QAAI,OAAO,IAAI,CAAC,EAAG,KAAI;AAAA,aACd,OAAO,IAAI,CAAC,EAAG,KAAI;AAC5B,QAAI,MAAM,EAAG,WAAU;AACvB,WAAO,MAAM,IAAI,KAAK,CAAC,IAAI,OAAO,aAAa,CAAC;AAAA,EAClD;AACA,SAAO,UAAU,MAAM;AACzB;AAMA,SAAS,UAAU,MAAc,GAAU,GAAY,UAA0B;AAC/E,QAAM,OAAO,EAAE,EAAE;AACjB,QAAM,OAAO,EAAE,EAAE,CAAC;AAClB,QAAM,QAAQ,KAAK,MAAM,EAAE;AAC3B,QAAM,MAAM,EAAE,EAAE,SAAS;AACzB,aAAW,QAAQ,EAAE,MAAM;AACzB,UAAM,IAAI,MAAM,IAAI;AACpB,QAAI,CAAC,EAAG;AACR,aAAS,IAAI,EAAE,CAAC,IAAI,MAAM,IAAI,EAAE,CAAC,IAAI,MAAM,KAAK;AAC9C,UAAI,EAAE,WAAW,cAAc,KAAK,KAAK,CAAC,CAAC,EAAG,OAAM,CAAC,IAAI;AAAA,IAC3D;AAAA,EACF;AACA,SAAO,MAAM,KAAK,EAAE;AACtB;AAOA,SAAS,eAAe,UAAwB;AAC9C,QAAM,IAAI,mBAAmB,QAAQ;AACrC,MACE,SAAS,WAAW,KAAK,gCAAgC,KAAK,CAAC,KAC5D,qBAAqB,KAAK,QAAQ,KAAK,8BAA8B,KAAK,QAAQ,GACrF;AACA,UAAM,IAAI,MAAM,uNAAuE,KAAK,UAAU,QAAQ,CAAC,EAAE;AAAA,EACnH;AACF;AAGA,IAAM,cAAc;AAOpB,SAAS,OACP,MAAc,OAA8B,UAAkB,UACjD;AACb,QAAM,OAAO,mBAAmB,IAAI;AACpC,QAAM,OAAoB,CAAC;AAC3B,QAAM,WAAiG,CAAC;AACxG,aAAW,QAAQ,eAAe;AAChC,QAAI,CAAC,MAAM,SAAS,IAAI,EAAG;AAC3B,eAAW,KAAK,MAAM,IAAI,GAAG;AAC3B,iBAAW,KAAK,KAAK,SAAS,EAAE,EAAE,GAAG;AACnC,cAAM,QAAQ,EAAE;AAChB,cAAM,MAAM,QAAQ,EAAE,CAAC,EAAE;AACzB,YAAI,SAAS,KAAK,CAAC,MAAM,QAAQ,EAAE,OAAO,MAAM,EAAE,KAAK,EAAG;AAC1D,YAAI;AACJ,cAAM,MAAM,MAAwB;AAClC,cAAI,CAAC,QAAQ;AACX,gBAAI,IAAsB,aAAa,MAAM,KAAK;AAClD,gBAAI,EAAE,SAAS,GAAG;AAChB,oBAAM,OAAO,SAAS,KAAK,CAAC,MAAM,EAAE,MAAM,SAAS,QAAQ,EAAE,OAAO,KAAK,YAAY,KAAK,KAAK,MAAM,EAAE,KAAK,KAAK,CAAC,CAAC;AAEnH,kBAAI,KAAM,KAAI,KAAK,SAAS,UAAU,KAAK,IAAI,IAAI,oBAAI,IAAS,CAAC,GAAG,KAAK,IAAI,GAAG,KAAK,IAAI,CAAC;AAAA,YAC5F;AACA,gBAAI,EAAE,SAAS,KAAK,UAAU;AAC5B,oBAAM,IAAI,SAAS,KAAK;AACxB,kBAAI,EAAG,KAAI,SAAS,mBAAmB,CAAC,CAAC;AAAA,YAC3C;AACA,qBAAS;AAAA,UACX;AACA,iBAAO;AAAA,QACT;AACA,cAAM,IAAW,EAAE,GAAG,IAAI;AAC1B,YAAI,EAAE,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAG;AACpC,YAAI,EAAE,MAAM,CAAC,EAAE,GAAG,CAAC,EAAG;AACtB,iBAAS,KAAK,EAAE,OAAO,KAAK,KAAK,KAAK,CAAC;AACvC,aAAK,KAAK,EAAE,MAAM,QAAQ,UAAU,KAAK,MAAM,OAAO,GAAG,GAAG,GAAG,GAAG,QAAQ,GAAG,OAAO,OAAO,QAAQ,MAAM,MAAM,CAAC;AAAA,MAClH;AAAA,IACF;AAAA,EACF;AACA,SAAO,KAAK,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AAC9C;AAEA,SAAS,UAAU,MAAc,MAAoC;AACnE,MAAI,MAAM;AACV,MAAI,SAAS;AACb,aAAW,KAAK,MAAM;AACpB,WAAO,KAAK,MAAM,QAAQ,EAAE,KAAK,IAAI,EAAE;AACvC,aAAS,EAAE,QAAQ,EAAE;AAAA,EACvB;AACA,SAAO,MAAM,KAAK,MAAM,MAAM;AAChC;AASO,SAAS,WAAW,MAAc,SAA2C;AAClF,QAAM,WAAW,SAAS,YAAY;AACtC,iBAAe,QAAQ;AACvB,QAAM,QAAQ,SAAS,SAAS;AAChC,MAAI,SAAS,MAAM,MAAM,WAAW,EAAG,QAAO,EAAE,MAAM,MAAM,CAAC,EAAE;AAC/D,QAAM,OAAO,OAAO,MAAM,OAAO,QAAQ;AACzC,SAAO,EAAE,MAAM,UAAU,MAAM,IAAI,GAAG,KAAK;AAC7C;AAKA,SAAS,cAAc,MAAwB;AAC7C,QAAM,MAAgB,CAAC;AACvB,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,QAAI,KAAK,CAAC,MAAM,OAAO,KAAK,IAAI,CAAC,MAAM,KAAM,KAAI,KAAK,CAAC;AAAA,EACzD;AACA,SAAO;AACT;AAEA,IAAM,WAAW,CAAC,MAAuB,iBAAiB,KAAK,CAAC;AAChE,IAAM,WAAW,CAAC,MAAuB,mCAAmC,KAAK,CAAC;AAKlF,SAAS,UAAU,MAA0B;AAC3C,QAAM,MAAkB,CAAC;AACzB,MAAI,MAAM;AACV,aAAW,KAAK,KAAK,SAAS,uCAAuC,GAAG;AACtE,UAAM,OAAO,KAAK,IAAI,GAAG,OAAO,EAAE,CAAC,EAAE,MAAM,kBAAkB,IAAI,CAAC,KAAK,CAAC,CAAC;AACzE,QAAI,KAAK,EAAE,OAAO,EAAE,OAAiB,KAAM,EAAE,QAAmB,EAAE,CAAC,EAAE,QAAQ,KAAK,MAAM,MAAM,EAAE,CAAC,EAAE,QAAQ,YAAY,GAAG,EAAE,CAAC;AAC7H,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAMA,SAAS,mBAAmB,OAAsE;AAChG,QAAM,MAAM,oBAAI,IAAoD;AACpE,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AAErC,QAAI,SAAS,MAAM,CAAC,CAAC,KAAK,IAAI,IAAI,MAAM,UAAU,SAAS,MAAM,IAAI,CAAC,CAAC,GAAG;AACxE,YAAM,OAAO,MAAM,CAAC;AACpB,YAAM,KAAK,cAAc,IAAI;AAC7B,YAAM,UAAU,GAAG,MAAM,GAAG,EAAE,EAAE,IAAI,CAAC,GAAG,MAAM,KAAK,MAAM,IAAI,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC;AAC1E,UAAI,IAAI,IAAI;AACZ,aAAO,IAAI,MAAM,UAAU,SAAS,MAAM,CAAC,CAAC,GAAG,KAAK;AAClD,cAAM,KAAK,cAAc,MAAM,CAAC,CAAC;AACjC,YAAI,IAAI,GAAG,CAAC,QAAQ;AAClB,cAAI,IAAI;AACR,qBAAW,KAAK,IAAI;AAAE,gBAAI,IAAI,IAAK;AAAA,gBAAU;AAAA,UAAM;AACnD,iBAAO,KAAK,IAAI,QAAQ,CAAC,IAAI;AAAA,QAC/B,CAAC;AAAA,MACH;AACA,UAAI,IAAI;AACR;AAAA,IACF;AAEA,QAAI,YAAY,KAAK,MAAM,CAAC,CAAC,GAAG;AAC9B,UAAI,UAA6B;AACjC,UAAI,QAAQ;AACZ,eAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,kBAAU,MAAM,CAAC,EAAE,MAAM,YAAY,KAAK,CAAC,GAAG;AAC9C,YAAI,SAAS,KAAK,MAAM,CAAC,CAAC,KAAK,UAAU,GAAG;AAC1C,gBAAM,QAAQ,UAAU,MAAM,CAAC,CAAC;AAChC,cAAI,CAAC,QAAS,WAAU;AAAA,mBACf,MAAM,SAAS,GAAG;AACzB,kBAAM,MAAM;AACZ,gBAAI,IAAI,GAAG,CAAC,QAAQ;AAClB,oBAAM,IAAI,MAAM,KAAK,CAAC,MAAM,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG;AACzD,kBAAI,CAAC,EAAG,QAAO;AACf,oBAAM,IAAI,IAAI,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI;AAClE,qBAAO,GAAG;AAAA,YACZ,CAAC;AAAA,UACH;AAAA,QACF;AACA,kBAAU,MAAM,CAAC,EAAE,MAAM,aAAa,KAAK,CAAC,GAAG;AAC/C,YAAI,SAAS,GAAG;AAAE,cAAI;AAAG;AAAA,QAAM;AAAA,MACjC;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AASA,IAAM,sBAAsB,IAAI,OAAO;AAAA,EACrC;AAAA,EAAyB;AAAA,EAAiB;AAAA,EAC1C;AAAA,EACA;AACF,EAAE,KAAK,GAAG,GAAG,GAAG;AAKhB,IAAM,aAAa;AACnB,IAAM,cAAc;AAGb,SAAS,eAAe,MAAuC;AACpE,QAAM,MAA+B,CAAC;AACtC,aAAW,KAAK,KAAK,SAAS,mBAAmB,EAAG,KAAI,KAAK,CAAC,EAAE,OAAkB,EAAE,QAAmB,EAAE,CAAC,EAAE,MAAM,CAAC;AACnH,QAAM,QAAQ,CAAC,GAAW,MAAuB,IAAI,KAAK,CAAC,CAAC,GAAG,CAAC,MAAM,IAAI,KAAK,IAAI,CAAC;AACpF,QAAM,SAAS,CAAC,GAAG,KAAK,SAAS,WAAW,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAkB,EAAE,QAAmB,EAAE,CAAC,EAAE,MAAM,CAAC;AACzH,MAAI,OAAO;AACX,aAAW,KAAK,KAAK,SAAS,UAAU,GAAG;AACzC,UAAM,KAAK,EAAE;AACb,UAAM,KAAK,KAAK,EAAE,CAAC,EAAE;AACrB,QAAI,KAAK,QAAQ,MAAM,IAAI,EAAE,EAAG;AAChC,UAAM,IAAI,OAAO,KAAK,CAAC,MAAO,EAAE,SAAoB,MAAM,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC;AACvE,QAAI,CAAC,EAAG;AACR,QAAI,KAAK,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,OAAkB,EAAE,QAAmB,EAAE,CAAC,EAAE,MAAM,CAAC;AACzE,WAAQ,EAAE,QAAmB,EAAE,CAAC,EAAE;AAAA,EACpC;AACA,SAAO,IAAI,KAAK,CAAC,GAAG,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;AACvC;AAGA,SAAS,mBACP,MAAc,OAA8B,UAAkB,UACjD;AACb,QAAM,UAAU,eAAe,IAAI;AACnC,MAAI,QAAQ,WAAW,EAAG,QAAO,OAAO,MAAM,OAAO,UAAU,QAAQ;AACvE,MAAI,QAAQ;AACZ,QAAM,MAAgB,CAAC;AACvB,MAAI,SAAS;AACb,QAAM,OAAO,CAAC,OAAqB;AACjC,aAAS,IAAI,QAAQ,IAAI,IAAI,KAAK;AAAE,eAAS,KAAK,CAAC;AAAG,UAAI,KAAK,CAAC;AAAA,IAAE;AAAA,EACpE;AACA,aAAW,CAAC,IAAI,EAAE,KAAK,SAAS;AAC9B,SAAK,EAAE;AACP,aAAS;AAAA,EACX;AACA,OAAK,KAAK,MAAM;AAChB,QAAM,OAAO,OAAO,OAAO,OAAO,UAAU,WAAW,CAAC,MAAM,SAAS,IAAI,CAAC,KAAK,CAAC,IAAI,MAAS;AAC/F,SAAO,KAAK,IAAI,CAAC,MAAM;AACrB,UAAM,QAAQ,IAAI,EAAE,KAAK;AACzB,UAAM,MAAM,IAAI,EAAE,QAAQ,EAAE,SAAS,CAAC,IAAI;AAC1C,UAAM,QAAQ,KAAK,MAAM,OAAO,GAAG,EAAE,MAAM,EAAE;AAC7C,aAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK;AACjC,UAAI,EAAE,OAAO,CAAC,MAAM,MAAM,EAAE,QAAQ,CAAC,EAAG,OAAM,IAAI,EAAE,QAAQ,CAAC,IAAI,KAAK,IAAI;AAAA,IAC5E;AACA,WAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,MAAM,KAAK,EAAE,GAAG,OAAO,OAAO,QAAQ,MAAM,MAAM;AAAA,EACnF,CAAC;AACH;AAQO,SAAS,eAAe,UAAkB,SAA2C;AAC1F,QAAM,WAAW,SAAS,YAAY;AACtC,iBAAe,QAAQ;AACvB,QAAM,QAAQ,SAAS,SAAS;AAChC,QAAM,QAAQ,SAAS,MAAM,IAAI;AACjC,QAAM,UAAU,mBAAmB,KAAK;AACxC,QAAM,OAAoB,CAAC;AAC3B,MAAI,SAAS;AACb,QAAM,WAAW,MAAM,IAAI,CAAC,MAAM,MAAM;AACtC,QAAI,KAAK,SAAS,aAAa,KAAK,SAAS,MAAM,MAAM,WAAW,GAAG;AACrE,gBAAU,KAAK,SAAS;AACxB,aAAO;AAAA,IACT;AACA,UAAM,WAAW,mBAAmB,MAAM,OAAO,UAAU,QAAQ,IAAI,CAAC,CAAC;AACzE,eAAW,KAAK,SAAU,MAAK,KAAK,EAAE,GAAG,GAAG,OAAO,EAAE,QAAQ,OAAO,CAAC;AACrE,cAAU,KAAK,SAAS;AACxB,WAAO,UAAU,MAAM,QAAQ;AAAA,EACjC,CAAC;AACD,SAAO,EAAE,MAAM,SAAS,KAAK,IAAI,GAAG,KAAK;AAC3C;","names":["g"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/Users/chris_gomdori/workspace/kordoc/dist/chunk-IW2CB755.cjs","../src/ocr/ruling-lines.ts","../src/ocr/engine.ts","../src/ocr/models.ts","../src/ocr/line-split.ts","../src/ocr/postprocess.ts","../src/ocr/crop.ts","../src/ocr/deskew.ts","../src/ocr/pdf-ocr.ts"],"names":["m","prev"],"mappings":"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","file":"/Users/chris_gomdori/workspace/kordoc/dist/chunk-IW2CB755.cjs","sourcesContent":[null,"/**\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\nimport type { LineSegment } from \"../pdf/line-types.js\"\n\n/** 잉크 판정 휘도 상한 — 셀 배경 음영(회색 ~220)은 통과, 실선(진회~검정·남색)만 잉크 */\nconst INK_LUMA_MAX = 190\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/** 픽셀 공간(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 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 for (let p = 0, i = 0; p < ink.length; p++, i += 4) {\n // ITU-R BT.601 휘도 근사 (정수 시프트)\n const luma = (rgba[i] * 77 + rgba[i + 1] * 150 + rgba[i + 2] * 29) >> 8\n if (luma <= INK_LUMA_MAX && rgba[i + 3] >= 128) ink[p] = 1\n }\n\n const horizontals = detectBands(ink, width, height, minLenPx, maxThickPx, false)\n const verticals = detectBands(ink, width, height, minLenPx, maxThickPx, true)\n return { horizontals, verticals }\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 minLenPx: number,\n maxThickPx: number,\n transpose: boolean,\n): PxSegment[] {\n const lines = transpose ? width : height // 스캔라인 개수 (직교축)\n const span = transpose ? height : width // 런 진행축 길이\n const at = transpose\n ? (line: number, pos: number) => ink[pos * width + line]\n : (line: number, pos: number) => ink[line * width + pos]\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 for (let pos = b.lo; pos <= b.hi; pos += 3) {\n total++\n if (at(side, pos) === 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) 이 스캔라인의 잉크 런 수집 (GAP_TOL_PX 이하 끊김 허용)\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 && at(line, pos) === 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\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 {\n OCR_DET_MODEL,\n OCR_REC_MODEL,\n OCR_REC_DICT,\n getOcrModelsDir,\n parseCharacterDict,\n} from \"./models.js\"\nimport { grayCrop, inkBounds, inkStats, splitRowBands } from \"./line-split.js\"\nimport { isDotFragment, restoreBulletItems, restoreSymbols } from \"./postprocess.js\"\nimport { bandBoxes, 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\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}\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})\n\nconst DET_MIN_SIZE = 3\nconst DET_MAX_BOXES = 1000\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\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=시계) */\ninterface LineJob { box: Box; rot: 0 | 90 | 270; group: number }\n\nexport class OcrEngine {\n private det: InferenceSession\n private rec: InferenceSession\n private dict: string[]\n private ort: typeof import(\"onnxruntime-node\")\n private sharp: SharpFactory\n\n private constructor(parts: {\n det: InferenceSession\n rec: InferenceSession\n dict: string[]\n ort: typeof import(\"onnxruntime-node\")\n sharp: SharpFactory\n }) {\n this.det = parts.det\n this.rec = parts.rec\n this.dict = parts.dict\n this.ort = parts.ort\n this.sharp = parts.sharp\n }\n\n static async create(): 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?: { droppedLowConf: number },\n tuning: Readonly<OcrTuning> = DEFAULT_OCR_TUNING,\n ): Promise<OcrItem[]> {\n if (width < DET_MIN_SIZE || height < DET_MIN_SIZE) return []\n const boxes = await this.detect(rgba, width, height, tuning)\n\n // 박스 픽셀 분석 → 인식 작업(라인) 목록. group = 한 결과로 합칠 후보 묶음(회전 후보)\n const jobs: LineJob[] = []\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 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 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 const items: OcrItem[] = []\n for (const { job, text: raw, confidence } of best.values()) {\n const text = tuning.postprocess ? restoreSymbols(raw.trim()) : raw\n if (!text.trim()) continue\n if (tuning.postprocess && isDotFragment(text)) continue\n if (confidence < tuning.textScore) { if (stats) stats.droppedLowConf++; continue }\n let b = job.box\n if (tuning.tightBoxes) {\n const gray = grayCrop(rgba, width, b)\n const t = inkBounds(gray, b.w, b.h, inkStats(gray))\n b = { x: b.x + t.x0, y: b.y + t.y0, w: t.x1 - t.x0, h: t.y1 - t.y0 }\n }\n items.push({ text, x: b.x, y: b.y, w: b.w, h: b.h, confidence })\n }\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 ─────────────────────────────────────────────\n\n private async detect(\n rgba: Uint8Array,\n width: number,\n height: number,\n tuning: Readonly<OcrTuning>,\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 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}\\` 후 다시 실행하세요. 원인: ${(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 * 내장 텍스트 OCR 모델 (PP-OCRv5 korean) 스펙 + 다운로드/검증.\n *\n * 캐시 위치: `~/.cache/kordoc/models/ppocr/` — 총 ~18MB (det 4.6 + rec 12.8 + dict 0.1)\n * 다운로드 인프라는 수식 OCR 과 공용 (formula/models.ts ensureModelsIn).\n *\n * 모델 출처: PaddlePaddle 공식 HuggingFace org 의 ONNX 변환본 (Apache-2.0).\n * SHA-256 은 실제 다운로드 + HF API lfs.oid 대조로 검증됨 — 변경 금지.\n * 한국어 사전은 rec 리포의 inference.yml `PostProcess.character_dict` 에\n * 내장 (11,945자 — 완성형 한글 11,172 음절 전량 + 자모/라틴/기호).\n */\n\nimport { join } from \"path\"\nimport {\n type ModelSpec,\n type ModelStatus,\n type ProgressHandler,\n ensureModelsIn,\n getModelStatusIn,\n getModelsDir,\n} from \"../pdf/formula/models.js\"\n\nexport const OCR_DET_MODEL: ModelSpec = {\n name: \"PP-OCRv5 mobile det\",\n filename: \"det.onnx\",\n url: \"https://huggingface.co/PaddlePaddle/PP-OCRv5_mobile_det_onnx/resolve/main/inference.onnx\",\n sha256: \"a431985659dc921974177a95adcfbb90fd9e51989a5e04d70d0b75f597b6e61d\",\n sizeMb: 5,\n}\n\nexport const OCR_REC_MODEL: ModelSpec = {\n name: \"PP-OCRv5 korean rec\",\n filename: \"rec_korean.onnx\",\n url: \"https://huggingface.co/PaddlePaddle/korean_PP-OCRv5_mobile_rec_onnx/resolve/main/inference.onnx\",\n sha256: \"92f0b7785e64fc9090106a241cf4c1eb97472824558272751b88a2a4476d3a08\",\n sizeMb: 13,\n}\n\nexport const OCR_REC_DICT: ModelSpec = {\n name: \"PP-OCRv5 korean dict\",\n filename: \"rec_korean.yml\",\n url: \"https://huggingface.co/PaddlePaddle/korean_PP-OCRv5_mobile_rec_onnx/resolve/main/inference.yml\",\n sha256: \"f757fa1c40e99edcf27e9cce879b93eb2a51fa46f5ef39095689b8c37dd75998\",\n sizeMb: 1,\n}\n\nexport const ALL_OCR_MODELS: ReadonlyArray<ModelSpec> = [OCR_DET_MODEL, OCR_REC_MODEL, OCR_REC_DICT]\n\nexport function getOcrModelsDir(): string {\n return getModelsDir(\"ppocr\")\n}\n\n/** 모든 텍스트 OCR 모델 다운로드/검증 (있으면 skip) */\nexport async function ensureOcrModels(onProgress?: ProgressHandler): Promise<void> {\n return ensureModelsIn(getOcrModelsDir(), ALL_OCR_MODELS, onProgress)\n}\n\n/** 텍스트 OCR 모델 상태 (다운로드 없이 확인만) */\nexport async function getOcrModelStatus(): Promise<ModelStatus[]> {\n return getModelStatusIn(getOcrModelsDir(), ALL_OCR_MODELS)\n}\n\n/**\n * inference.yml 에서 `PostProcess.character_dict` 리스트를 추출.\n * 전체 YAML 파서 없이 해당 블록의 `- <char>` 라인만 순서대로 읽는다\n * (공식 yml 구조 고정 — 들여쓰기 2칸, 한 줄 한 글자).\n *\n * CTC 클래스 배열: index 0 = blank, 1..N = 사전 순서, N+1(마지막) = space.\n */\nexport function parseCharacterDict(yml: string): string[] {\n const lines = yml.split(\"\\n\")\n const chars: string[] = []\n let inDict = false\n let dictIndent = -1\n for (const line of lines) {\n if (!inDict) {\n const m = /^(\\s*)character_dict:\\s*$/.exec(line)\n if (m) {\n inDict = true\n dictIndent = m[1].length\n }\n continue\n }\n // 리스트 항목 — YAML 은 키와 같은 들여쓰기의 \"- x\" 도 허용 (공식 yml 실측: 둘 다 2칸)\n const m = /^(\\s*)- (.*)$/.exec(line)\n if (m && m[1].length >= dictIndent) {\n let v = m[2]\n // YAML 인용 문자 처리 ('#' 같은 예약 문자는 인용됨)\n if (v.length >= 2 && ((v.startsWith(\"'\") && v.endsWith(\"'\")) || (v.startsWith('\"') && v.endsWith('\"')))) {\n v = v.slice(1, -1).replace(/''/g, \"'\")\n }\n chars.push(v)\n continue\n }\n // 들여쓰기가 얕아지면 블록 종료 (빈 줄은 통과)\n if (line.trim() !== \"\") break\n }\n return chars\n}\n\nexport function ocrModelPath(spec: ModelSpec): string {\n return join(getOcrModelsDir(), spec.filename)\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\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 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\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 * 스캔 기울기 보정 — 투영 프로파일 방식.\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 } from \"./engine.js\"\nimport { deskewPage } from \"./deskew.js\"\nimport { ensureOcrModels } from \"./models.js\"\n\n/** OCR 렌더 스케일 (72dpi × 3 = 216dpi) — 10pt 본문이 rec 입력 높이(48px)에 근접 */\nconst OCR_RENDER_SCALE = 3\n/** 페이지 하나당 OCR 타임아웃 — det+rec 수십 라인 기준 넉넉히 */\nconst PAGE_TIMEOUT_MS = 120_000\n\nexport type OcrMode = \"builtin\" | OcrProvider\n\n/**\n * 대상 페이지들을 OCR 해 페이지별 블록 맵 반환.\n * @param buffer 원본 PDF (pdfjs 가 detach 하기 전에 clone 해 둔 것)\n * @param targets 1-based 페이지 번호 집합\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): 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 for (const page of doc.pages()) {\n // pdfium page.number 는 0-based pageIndex — 대외 계약(1-based)으로 환산\n const pageNo = page.number + 1\n if (!targets.has(pageNo)) continue\n onProgress?.(++done, targets.size)\n try {\n const blocks = await withTimeout(\n ocrOnePage(page, pageNo, mode, engine, warnings, detectTables),\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): Promise<IRBlock[]> {\n const { originalWidth: pdfW, originalHeight: pdfH } = page.getOriginalSize()\n const rendered = await page.render({\n scale: OCR_RENDER_SCALE,\n render: async ({ data }) => data,\n })\n const { data: bgra, width: rw, height: rh } = rendered\n const rgba = bgraToRgba(bgra)\n\n if (mode === \"builtin\") {\n // 스캔 기울기 보정 — 인식과 괘선 감지가 같은(바로 선) 래스터를 본다. 클린 렌더는 무보정\n const { rgba: upright } = deskewPage(rgba, rw, rh)\n const stats = { droppedLowConf: 0 }\n const items = await engine!.recognizePage(upright, rw, rh, stats)\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 // 래스터에서 표 괘선 감지 — 스캔본 병합셀 서식도 선 기반 표 파이프라인을 탄다\n const scale = rh / pdfH\n const ruling = detectRulingLines(upright, rw, rh, scale)\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/**\n * OcrItem(렌더 픽셀, top-left origin) → NormItem(PDF pt, bottom-up baseline)\n * 으로 환산해 기존 블록 파이프라인에 태운다. 괘선은 그래픽 ops 대신\n * 래스터 감지 결과(extraLines)로 공급 — 없으면 클러스터 감지기 몫이다.\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): 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, { fnArray: [], argsArray: [] }, 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}\\` 후 다시 실행하세요. 원인: ${(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"]}