kordoc 2.9.0 → 3.0.0

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 (40) hide show
  1. package/README.md +66 -8
  2. package/dist/-K5SLEFZD.js +71 -0
  3. package/dist/-K5SLEFZD.js.map +1 -0
  4. package/dist/{chunk-M24KMDAR.js → chunk-326STEDU.js} +6684 -4061
  5. package/dist/chunk-326STEDU.js.map +1 -0
  6. package/dist/{chunk-QB7CS534.cjs → chunk-3WRJQQIO.cjs} +185 -16
  7. package/dist/chunk-3WRJQQIO.cjs.map +1 -0
  8. package/dist/chunk-MUOQXDZ4.cjs.map +1 -1
  9. package/dist/{chunk-RXZLTACX.js → chunk-NHXKJWR7.js} +182 -13
  10. package/dist/chunk-NHXKJWR7.js.map +1 -0
  11. package/dist/{chunk-SJ5TPMBT.js → chunk-SA2PERJ5.js} +182 -13
  12. package/dist/chunk-SA2PERJ5.js.map +1 -0
  13. package/dist/cli.js +42 -3
  14. package/dist/cli.js.map +1 -1
  15. package/dist/formula-XGG6ZP42.cjs.map +1 -1
  16. package/dist/index.cjs +3247 -822
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +61 -2
  19. package/dist/index.d.ts +61 -2
  20. package/dist/index.js +3025 -600
  21. package/dist/index.js.map +1 -1
  22. package/dist/mcp.js +3 -3
  23. package/dist/page-range-3C7UGGEK.cjs.map +1 -1
  24. package/dist/{parser-OMPBVEFU.js → parser-4IVYHKSL.js} +677 -85
  25. package/dist/parser-4IVYHKSL.js.map +1 -0
  26. package/dist/{parser-EL5YETUA.cjs → parser-5KHU732L.cjs} +689 -97
  27. package/dist/parser-5KHU732L.cjs.map +1 -0
  28. package/dist/{parser-XBYGROQB.js → parser-AU2NLC44.js} +677 -85
  29. package/dist/parser-AU2NLC44.js.map +1 -0
  30. package/dist/provider-SNONEZNW.cjs.map +1 -1
  31. package/dist/{watch-ULLLK7ID.js → watch-5DDN4BUI.js} +3 -3
  32. package/package.json +1 -1
  33. package/dist/chunk-M24KMDAR.js.map +0 -1
  34. package/dist/chunk-QB7CS534.cjs.map +0 -1
  35. package/dist/chunk-RXZLTACX.js.map +0 -1
  36. package/dist/chunk-SJ5TPMBT.js.map +0 -1
  37. package/dist/parser-EL5YETUA.cjs.map +0 -1
  38. package/dist/parser-OMPBVEFU.js.map +0 -1
  39. package/dist/parser-XBYGROQB.js.map +0 -1
  40. /package/dist/{watch-ULLLK7ID.js.map → watch-5DDN4BUI.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/pdf/line-detector.ts","../src/pdf/cluster-detector.ts","../src/pdf/quality.ts","../src/pdf/polyfill.ts","../src/pdf/parser.ts"],"sourcesContent":["/**\n * PDF 그래픽 명령에서 수평/수직 선을 추출하고,\n * 선 교차점(Vertex) 기반으로 테이블 그리드를 구성하는 모듈.\n *\n * 이 파일의 테이블 감지 알고리즘은 OpenDataLoader PDF의\n * TableBorderBuilder / LinesPreprocessingConsumer를 참고하여\n * TypeScript로 clean-room 재구현한 것입니다.\n *\n * v2: Vertex 기반 동적 tolerance, 선 전처리 파이프라인,\n * 정밀 병합 셀 감지 (ODL 알고리즘 충실 포팅)\n *\n * Original algorithm: Copyright 2025-2026 Hancom, Inc. (Apache 2.0)\n * https://github.com/opendataloader-project/opendataloader-pdf\n * Core algorithm concepts from veraPDF-wcag-algs (GPLv3+/MPLv2+)\n * This is an independent clean-room reimplementation in TypeScript.\n */\n\nimport { OPS } from \"pdfjs-dist/legacy/build/pdf.mjs\"\n\n// ─── pdfjs-dist v5 DrawOPS ──\nconst enum DrawOPS {\n moveTo = 0,\n lineTo = 1,\n curveTo = 2,\n quadraticCurveTo = 3,\n closePath = 4,\n}\n\n// ─── 타입 ─────────────────────────────────────────────\n\nexport interface LineSegment {\n x1: number; y1: number\n x2: number; y2: number\n lineWidth: number\n}\n\n/** 선 교차점 (Vertex) — ODL의 핵심 개념 */\ninterface Vertex {\n x: number\n y: number\n /** 교차하는 선들의 최대 lineWidth → tolerance 계산에 사용 */\n radius: number\n}\n\nexport interface TableGrid {\n /** 행 Y 좌표 경계 (위→아래 내림차순) */\n rowYs: number[]\n /** 열 X 좌표 경계 (좌→우 오름차순) */\n colXs: number[]\n /** 테이블 바운딩 박스 */\n bbox: { x1: number; y1: number; x2: number; y2: number }\n /** 그리드 내 교차점 반경 (동적 tolerance용) */\n vertexRadius: number\n}\n\nexport interface ExtractedCell {\n row: number; col: number\n rowSpan: number; colSpan: number\n /** 셀 바운딩 박스 */\n bbox: { x1: number; y1: number; x2: number; y2: number }\n}\n\n// ─── 상수 ─────────────────────────────────────────────\n\n/** 수평/수직 판별 허용 오차 (pt) */\nconst ORIENTATION_TOL = 2\n/** 최소 선 길이 — 짧은 장식선(체크박스 테두리 등) 무시 */\nconst MIN_LINE_LENGTH = 15\n/** 굵은 선 필터 — ODL: MAX_LINE_WIDTH = 5.0 (배경 채움/장식 사각형 제외) */\nconst MAX_LINE_WIDTH = 5.0\n/** 두 선이 같은 테이블에 속하는지 판별하는 거리 */\nconst CONNECT_TOL = 5\n/** 셀 경계 내부 판별 여유 (텍스트 매핑용) */\nconst CELL_PADDING = 2\n/** 최소 열 폭 (pt) — 이보다 좁은 열은 인접 열과 병합 */\nconst MIN_COL_WIDTH = 15\n/** 최소 행 높이 (pt) */\nconst MIN_ROW_HEIGHT = 6\n/** Vertex 기반 좌표 병합 시 radius 배수 — ODL: VERTEX_TABLE_FACTOR */\nconst VERTEX_MERGE_FACTOR = 4\n/** 좌표 병합 최소 tolerance (pt) — vertexRadius가 작아도 이 값 이하로 내려가지 않음 */\nconst MIN_COORD_MERGE_TOL = 8\n\n// ─── 선 추출 ──────────────────────────────────────────\n\n/**\n * pdfjs operatorList에서 수평/수직 선을 추출.\n * constructPath(91) 내의 moveTo→lineTo, rectangle 패턴을 인식.\n */\nexport function extractLines(\n fnArray: Uint32Array | number[],\n argsArray: unknown[][],\n): { horizontals: LineSegment[]; verticals: LineSegment[] } {\n const horizontals: LineSegment[] = []\n const verticals: LineSegment[] = []\n let lineWidth = 1\n\n let currentPath: Array<{ x1: number; y1: number; x2: number; y2: number }> = []\n let pathStartX = 0, pathStartY = 0\n let curX = 0, curY = 0\n\n function pushRectangle(\n path: Array<{ x1: number; y1: number; x2: number; y2: number }>,\n rx: number, ry: number, rw: number, rh: number,\n ) {\n if (Math.abs(rh) < ORIENTATION_TOL * 2) {\n path.push({ x1: rx, y1: ry + rh / 2, x2: rx + rw, y2: ry + rh / 2 })\n } else if (Math.abs(rw) < ORIENTATION_TOL * 2) {\n path.push({ x1: rx + rw / 2, y1: ry, x2: rx + rw / 2, y2: ry + rh })\n } else {\n path.push(\n { x1: rx, y1: ry, x2: rx + rw, y2: ry },\n { x1: rx + rw, y1: ry, x2: rx + rw, y2: ry + rh },\n { x1: rx + rw, y1: ry + rh, x2: rx, y2: ry + rh },\n { x1: rx, y1: ry + rh, x2: rx, y2: ry },\n )\n }\n }\n\n function flushPath(isStroke: boolean) {\n if (!isStroke) { currentPath = []; return }\n for (const seg of currentPath) {\n classifyAndAdd(seg, lineWidth, horizontals, verticals)\n }\n currentPath = []\n }\n\n for (let i = 0; i < fnArray.length; i++) {\n const op = fnArray[i]\n const args = argsArray[i]\n\n switch (op) {\n case OPS.setLineWidth:\n lineWidth = (args as number[])[0] || 1\n break\n\n case OPS.constructPath: {\n const arg0 = args[0]\n\n if (Array.isArray(arg0)) {\n // ── pdfjs-dist v4 형식 ──\n const subOps = arg0 as number[]\n const coords = (args as [number[], number[]])[1]\n let ci = 0\n\n for (const subOp of subOps) {\n if (subOp === OPS.moveTo) {\n curX = coords[ci++]; curY = coords[ci++]\n pathStartX = curX; pathStartY = curY\n } else if (subOp === OPS.lineTo) {\n const x2 = coords[ci++], y2 = coords[ci++]\n currentPath.push({ x1: curX, y1: curY, x2, y2 })\n curX = x2; curY = y2\n } else if (subOp === OPS.rectangle) {\n const rx = coords[ci++], ry = coords[ci++]\n const rw = coords[ci++], rh = coords[ci++]\n pushRectangle(currentPath, rx, ry, rw, rh)\n } else if (subOp === OPS.closePath) {\n if (curX !== pathStartX || curY !== pathStartY) {\n currentPath.push({ x1: curX, y1: curY, x2: pathStartX, y2: pathStartY })\n }\n curX = pathStartX; curY = pathStartY\n } else if (subOp === OPS.curveTo) {\n ci += 6\n } else if (subOp === OPS.curveTo2 || subOp === OPS.curveTo3) {\n ci += 4\n }\n }\n } else {\n // ── pdfjs-dist v5 형식 ──\n const afterOp = arg0 as number\n const dataArr = args[1] as unknown[]\n const pathData = dataArr?.[0] as Record<number, number> | undefined\n if (pathData && typeof pathData === \"object\") {\n const len = Object.keys(pathData).length\n let di = 0\n while (di < len) {\n const drawOp = pathData[di++]\n if (drawOp === DrawOPS.moveTo) {\n curX = pathData[di++]; curY = pathData[di++]\n pathStartX = curX; pathStartY = curY\n } else if (drawOp === DrawOPS.lineTo) {\n const x2 = pathData[di++], y2 = pathData[di++]\n currentPath.push({ x1: curX, y1: curY, x2, y2 })\n curX = x2; curY = y2\n } else if (drawOp === DrawOPS.curveTo) {\n di += 6\n } else if (drawOp === DrawOPS.quadraticCurveTo) {\n di += 4\n } else if (drawOp === DrawOPS.closePath) {\n if (curX !== pathStartX || curY !== pathStartY) {\n currentPath.push({ x1: curX, y1: curY, x2: pathStartX, y2: pathStartY })\n }\n curX = pathStartX; curY = pathStartY\n } else {\n break\n }\n }\n }\n\n if (afterOp === OPS.stroke || afterOp === OPS.closeStroke) {\n flushPath(true)\n } else if (afterOp === OPS.fill || afterOp === OPS.eoFill ||\n afterOp === OPS.fillStroke || afterOp === OPS.eoFillStroke ||\n afterOp === OPS.closeFillStroke || afterOp === OPS.closeEOFillStroke) {\n flushPath(true)\n } else if (afterOp === OPS.endPath) {\n flushPath(false)\n }\n }\n break\n }\n\n case OPS.stroke:\n case OPS.closeStroke:\n flushPath(true)\n break\n\n case OPS.fill:\n case OPS.eoFill:\n case OPS.fillStroke:\n case OPS.eoFillStroke:\n case OPS.closeFillStroke:\n case OPS.closeEOFillStroke:\n flushPath(true)\n break\n\n case OPS.endPath:\n flushPath(false)\n break\n }\n }\n\n return { horizontals, verticals }\n}\n\n// ─── 이미지 영역 추출 (정보손실 가시화용) ─────────────\n\n/** 페이지 내 이미지 XObject가 그려진 영역 (PDF 사용자 공간 좌표) */\nexport interface ImageRegion {\n x1: number; y1: number; x2: number; y2: number\n}\n\n/** 2D 어파인 행렬 곱 — t 적용 후 m 적용 (pdfjs Util.transform과 동일 순서) */\nfunction multiplyTransform(m: number[], t: number[]): number[] {\n return [\n m[0] * t[0] + m[2] * t[1],\n m[1] * t[0] + m[3] * t[1],\n m[0] * t[2] + m[2] * t[3],\n m[1] * t[2] + m[3] * t[3],\n m[0] * t[4] + m[2] * t[5] + m[4],\n m[1] * t[4] + m[3] * t[5] + m[5],\n ]\n}\n\n/**\n * pdfjs operatorList에서 이미지 paint 영역을 추출.\n * save/restore/transform으로 CTM을 추적하고, 이미지는 단위 정사각형(0,0)-(1,1)에\n * CTM을 적용한 bbox로 계산한다 (PDF 이미지 렌더링 규약).\n */\nexport function extractImageRegions(\n fnArray: Uint32Array | number[],\n argsArray: unknown[][],\n): ImageRegion[] {\n const regions: ImageRegion[] = []\n let ctm = [1, 0, 0, 1, 0, 0]\n const stack: number[][] = []\n\n for (let i = 0; i < fnArray.length; i++) {\n const op = fnArray[i]\n switch (op) {\n case OPS.save:\n stack.push(ctm)\n break\n case OPS.restore:\n ctm = stack.pop() || [1, 0, 0, 1, 0, 0]\n break\n case OPS.transform: {\n const t = argsArray[i] as number[]\n if (Array.isArray(t) && t.length >= 6) ctm = multiplyTransform(ctm, t)\n break\n }\n case OPS.paintImageXObject:\n case OPS.paintInlineImageXObject:\n case OPS.paintImageMaskXObject:\n case OPS.paintImageXObjectRepeat: {\n // 단위 정사각형 4꼭짓점에 CTM 적용\n const corners = [[0, 0], [1, 0], [0, 1], [1, 1]]\n let x1 = Infinity, y1 = Infinity, x2 = -Infinity, y2 = -Infinity\n for (const [u, v] of corners) {\n const x = ctm[0] * u + ctm[2] * v + ctm[4]\n const y = ctm[1] * u + ctm[3] * v + ctm[5]\n if (x < x1) x1 = x\n if (x > x2) x2 = x\n if (y < y1) y1 = y\n if (y > y2) y2 = y\n }\n if (x2 - x1 > 0 && y2 - y1 > 0) regions.push({ x1, y1, x2, y2 })\n break\n }\n }\n }\n return regions\n}\n\nfunction classifyAndAdd(\n seg: { x1: number; y1: number; x2: number; y2: number },\n lineWidth: number,\n horizontals: LineSegment[],\n verticals: LineSegment[],\n) {\n const dx = Math.abs(seg.x2 - seg.x1)\n const dy = Math.abs(seg.y2 - seg.y1)\n const length = Math.sqrt(dx * dx + dy * dy)\n\n if (length < MIN_LINE_LENGTH) return\n\n if (dy <= ORIENTATION_TOL) {\n const y = (seg.y1 + seg.y2) / 2\n const x1 = Math.min(seg.x1, seg.x2)\n const x2 = Math.max(seg.x1, seg.x2)\n horizontals.push({ x1, y1: y, x2, y2: y, lineWidth })\n } else if (dx <= ORIENTATION_TOL) {\n const x = (seg.x1 + seg.x2) / 2\n const y1 = Math.min(seg.y1, seg.y2)\n const y2 = Math.max(seg.y1, seg.y2)\n verticals.push({ x1: x, y1, x2: x, y2, lineWidth })\n }\n}\n\n// ─── 선 전처리 파이프라인 (ODL LinesPreprocessingConsumer 포팅) ──\n\n/**\n * 선 전처리: 굵은 선 필터 → 근접 선 병합 → 장식선 필터링\n * ODL의 LinesPreprocessingConsumer가 하는 핵심 로직.\n */\nexport function preprocessLines(\n horizontals: LineSegment[],\n verticals: LineSegment[],\n): { horizontals: LineSegment[]; verticals: LineSegment[] } {\n // 1. 굵은 선 필터링 (배경 채움 사각형, 장식 테두리 등)\n let h = horizontals.filter(l => l.lineWidth <= MAX_LINE_WIDTH)\n let v = verticals.filter(l => l.lineWidth <= MAX_LINE_WIDTH)\n\n // 2. 근접 평행 선 병합 (인쇄 잔상, 이중선)\n h = mergeParallelLines(h, \"h\")\n v = mergeParallelLines(v, \"v\")\n\n return { horizontals: h, verticals: v }\n}\n\n/**\n * 근접 평행 선 병합 — 같은 방향의 가까운 선을 하나로 합침.\n * 이중선, 인쇄 잔상, PDF 렌더링 미세 차이로 인한 중복 선 제거.\n */\nfunction mergeParallelLines(lines: LineSegment[], dir: \"h\" | \"v\"): LineSegment[] {\n if (lines.length <= 1) return lines\n\n // 수평선: y로 정렬, 수직선: x로 정렬\n const sorted = [...lines].sort((a, b) => {\n const posA = dir === \"h\" ? a.y1 : a.x1\n const posB = dir === \"h\" ? b.y1 : b.x1\n if (Math.abs(posA - posB) > 0.1) return posA - posB\n // 같은 위치면 시작 좌표로\n return dir === \"h\" ? (a.x1 - b.x1) : (a.y1 - b.y1)\n })\n\n const MERGE_TOL = 3 // 3pt 이내 평행 선 병합\n\n const result: LineSegment[] = [sorted[0]]\n for (let i = 1; i < sorted.length; i++) {\n const prev = result[result.length - 1]\n const curr = sorted[i]\n\n const prevPos = dir === \"h\" ? prev.y1 : prev.x1\n const currPos = dir === \"h\" ? curr.y1 : curr.x1\n\n if (Math.abs(prevPos - currPos) <= MERGE_TOL) {\n // 범위가 겹치는지 확인\n const prevStart = dir === \"h\" ? prev.x1 : prev.y1\n const prevEnd = dir === \"h\" ? prev.x2 : prev.y2\n const currStart = dir === \"h\" ? curr.x1 : curr.y1\n const currEnd = dir === \"h\" ? curr.x2 : curr.y2\n\n const overlap = Math.min(prevEnd, currEnd) - Math.max(prevStart, currStart)\n const minLen = Math.min(prevEnd - prevStart, currEnd - currStart)\n\n if (overlap > minLen * 0.3) {\n // 병합: 범위 확장, lineWidth는 최대값 유지\n if (dir === \"h\") {\n prev.x1 = Math.min(prev.x1, curr.x1)\n prev.x2 = Math.max(prev.x2, curr.x2)\n prev.y1 = (prev.y1 + curr.y1) / 2\n prev.y2 = prev.y1\n } else {\n prev.y1 = Math.min(prev.y1, curr.y1)\n prev.y2 = Math.max(prev.y2, curr.y2)\n prev.x1 = (prev.x1 + curr.x1) / 2\n prev.x2 = prev.x1\n }\n prev.lineWidth = Math.max(prev.lineWidth, curr.lineWidth)\n continue\n }\n }\n result.push(curr)\n }\n return result\n}\n\n// ─── 페이지 경계(클립) 선 필터링 ──────────────────────\n\nexport function filterPageBorderLines(\n horizontals: LineSegment[],\n verticals: LineSegment[],\n pageWidth: number,\n pageHeight: number,\n): { horizontals: LineSegment[]; verticals: LineSegment[] } {\n const margin = 5\n return {\n horizontals: horizontals.filter(l =>\n !(Math.abs(l.y1) < margin || Math.abs(l.y1 - pageHeight) < margin) ||\n (l.x2 - l.x1) < pageWidth * 0.9\n ),\n verticals: verticals.filter(l =>\n !(Math.abs(l.x1) < margin || Math.abs(l.x1 - pageWidth) < margin) ||\n (l.y2 - l.y1) < pageHeight * 0.9\n ),\n }\n}\n\n// ─── Vertex(교차점) 생성 ─────────────────────────────\n\n/**\n * 수평선과 수직선의 교차점(Vertex)을 생성.\n * ODL의 TableBorderBuilder.addLine()이 교차점을 자동 생성하는 것과 동일.\n * 각 Vertex는 교차하는 선들의 lineWidth로 radius를 계산 → 동적 tolerance.\n */\nfunction buildVertices(horizontals: LineSegment[], verticals: LineSegment[]): Vertex[] {\n const vertices: Vertex[] = []\n const tol = CONNECT_TOL\n\n for (const h of horizontals) {\n for (const v of verticals) {\n // 수평선의 X범위에 수직선의 X가 포함되고\n // 수직선의 Y범위에 수평선의 Y가 포함되면 → 교차\n if (v.x1 >= h.x1 - tol && v.x1 <= h.x2 + tol &&\n h.y1 >= v.y1 - tol && h.y1 <= v.y2 + tol) {\n const radius = Math.max(h.lineWidth, v.lineWidth, 1)\n vertices.push({ x: v.x1, y: h.y1, radius })\n }\n }\n }\n\n return vertices\n}\n\n/**\n * 근접 Vertex 병합 — 같은 교차점의 미세 위치 차이를 하나로 합침.\n */\nfunction mergeVertices(vertices: Vertex[]): Vertex[] {\n if (vertices.length <= 1) return vertices\n\n const merged: Vertex[] = []\n const used = new Array(vertices.length).fill(false)\n\n for (let i = 0; i < vertices.length; i++) {\n if (used[i]) continue\n let sumX = vertices[i].x, sumY = vertices[i].y\n let maxRadius = vertices[i].radius\n let count = 1\n\n for (let j = i + 1; j < vertices.length; j++) {\n if (used[j]) continue\n const mergeTol = VERTEX_MERGE_FACTOR * Math.max(maxRadius, vertices[j].radius)\n if (Math.abs(vertices[i].x - vertices[j].x) <= mergeTol &&\n Math.abs(vertices[i].y - vertices[j].y) <= mergeTol) {\n sumX += vertices[j].x\n sumY += vertices[j].y\n maxRadius = Math.max(maxRadius, vertices[j].radius)\n count++\n used[j] = true\n }\n }\n\n merged.push({ x: sumX / count, y: sumY / count, radius: maxRadius })\n }\n\n return merged\n}\n\n// ─── 테이블 그리드 구성 (Vertex 기반) ─────────────────\n\n/**\n * 수평/수직 선에서 테이블 그리드를 추출.\n * ODL과 동일한 흐름:\n * 1. 선 전처리 (preprocessLines — 호출측에서 수행)\n * 2. 교차점(Vertex) 생성 + 병합\n * 3. 교차하는 선들을 그룹화 (연결 컴포넌트)\n * 4. 각 그룹에서 Vertex의 X/Y 좌표를 동적 tolerance로 클러스터링\n * 5. 그리드 검증 (최소 열 폭, 최소 행 높이)\n */\nexport function buildTableGrids(\n horizontals: LineSegment[],\n verticals: LineSegment[],\n): TableGrid[] {\n if (horizontals.length < 2 || verticals.length < 2) return []\n\n // 1. 교차점 생성\n const allVertices = buildVertices(horizontals, verticals)\n const vertices = mergeVertices(allVertices)\n\n if (vertices.length < 4) return [] // 최소 4꼭짓점 필요 (사각형)\n\n // 전체 vertex의 대표 radius (동적 tolerance)\n const globalRadius = vertices.reduce((max, v) => Math.max(max, v.radius), 1)\n\n // 2. 선들을 교차 관계로 그룹화\n const allLines = [\n ...horizontals.map((l, i) => ({ ...l, type: \"h\" as const, id: i })),\n ...verticals.map((l, i) => ({ ...l, type: \"v\" as const, id: i + horizontals.length })),\n ]\n\n const groups = groupConnectedLines(allLines)\n const grids: TableGrid[] = []\n\n for (const group of groups) {\n const hLines = group.filter(l => l.type === \"h\")\n const vLines = group.filter(l => l.type === \"v\")\n\n if (hLines.length < 2 || vLines.length < 2) continue\n\n // 3. 이 그룹의 Vertex만 수집\n let gx1 = Infinity, gy1 = Infinity, gx2 = -Infinity, gy2 = -Infinity\n for (const l of vLines) { if (l.x1 < gx1) gx1 = l.x1; if (l.x1 > gx2) gx2 = l.x1 }\n for (const l of hLines) { if (l.y1 < gy1) gy1 = l.y1; if (l.y1 > gy2) gy2 = l.y1 }\n const groupBbox = {\n x1: gx1 - CONNECT_TOL,\n y1: gy1 - CONNECT_TOL,\n x2: gx2 + CONNECT_TOL,\n y2: gy2 + CONNECT_TOL,\n }\n\n const groupVertices = vertices.filter(v =>\n v.x >= groupBbox.x1 && v.x <= groupBbox.x2 &&\n v.y >= groupBbox.y1 && v.y <= groupBbox.y2\n )\n\n // 그룹 vertex의 대표 radius\n const groupRadius = groupVertices.length > 0\n ? groupVertices.reduce((max, v) => Math.max(max, v.radius), 1)\n : globalRadius\n\n // 4. Vertex 기반 좌표 클러스터링 (동적 tolerance)\n const coordMergeTol = Math.max(VERTEX_MERGE_FACTOR * groupRadius, MIN_COORD_MERGE_TOL)\n\n // Y좌표: 수평선 y + Vertex y\n const rawYs = [\n ...hLines.map(l => l.y1),\n ...groupVertices.map(v => v.y),\n ]\n const rowYs = clusterCoordinates(rawYs, coordMergeTol).sort((a, b) => b - a)\n\n // X좌표: 수직선 x + Vertex x\n const rawXs = [\n ...vLines.map(l => l.x1),\n ...groupVertices.map(v => v.x),\n ]\n const colXs = clusterCoordinates(rawXs, coordMergeTol).sort((a, b) => a - b)\n\n if (rowYs.length < 2 || colXs.length < 2) continue\n\n // 5. 그리드 검증: 최소 열 폭, 최소 행 높이\n const validColXs = enforceMinWidth(colXs, MIN_COL_WIDTH)\n const validRowYs = enforceMinHeight(rowYs, MIN_ROW_HEIGHT)\n\n if (validRowYs.length < 2 || validColXs.length < 2) continue\n\n const bbox = {\n x1: validColXs[0], y1: validRowYs[validRowYs.length - 1],\n x2: validColXs[validColXs.length - 1], y2: validRowYs[0],\n }\n\n grids.push({ rowYs: validRowYs, colXs: validColXs, bbox, vertexRadius: groupRadius })\n }\n\n return mergeAdjacentGrids(grids)\n}\n\n/** 최소 열 폭 보장 — 너무 좁은 열은 인접 열과 병합 */\nfunction enforceMinWidth(colXs: number[], minWidth: number): number[] {\n if (colXs.length <= 2) return colXs\n const result: number[] = [colXs[0]]\n for (let i = 1; i < colXs.length; i++) {\n const prevX = result[result.length - 1]\n if (colXs[i] - prevX < minWidth && i < colXs.length - 1) {\n // 너무 좁으면 스킵 (다음 열과 병합)\n continue\n }\n result.push(colXs[i])\n }\n return result\n}\n\n/** 최소 행 높이 보장 — 너무 낮은 행은 인접 행과 병합 */\nfunction enforceMinHeight(rowYs: number[], minHeight: number): number[] {\n if (rowYs.length <= 2) return rowYs\n // rowYs는 내림차순 (위→아래)\n const result: number[] = [rowYs[0]]\n for (let i = 1; i < rowYs.length; i++) {\n const prevY = result[result.length - 1]\n if (prevY - rowYs[i] < minHeight && i < rowYs.length - 1) {\n continue\n }\n result.push(rowYs[i])\n }\n return result\n}\n\n/** 같은 열 구조를 가진 인접 그리드를 병합 */\nfunction mergeAdjacentGrids(grids: TableGrid[]): TableGrid[] {\n if (grids.length <= 1) return grids\n const sorted = [...grids].sort((a, b) => b.bbox.y2 - a.bbox.y2)\n const merged: TableGrid[] = [sorted[0]]\n\n for (let i = 1; i < sorted.length; i++) {\n const prev = merged[merged.length - 1]\n const curr = sorted[i]\n\n if (prev.colXs.length === curr.colXs.length) {\n const mergeTol = Math.max(VERTEX_MERGE_FACTOR * Math.max(prev.vertexRadius, curr.vertexRadius), 6) * 3\n const colMatch = prev.colXs.every((x, ci) => Math.abs(x - curr.colXs[ci]) <= mergeTol)\n const verticalGap = prev.bbox.y1 - curr.bbox.y2\n if (colMatch && verticalGap >= -CONNECT_TOL && verticalGap <= 20) {\n const allRowYs = [...new Set([...prev.rowYs, ...curr.rowYs])].sort((a, b) => b - a)\n merged[merged.length - 1] = {\n rowYs: allRowYs,\n colXs: prev.colXs,\n bbox: {\n x1: Math.min(prev.bbox.x1, curr.bbox.x1),\n y1: Math.min(prev.bbox.y1, curr.bbox.y1),\n x2: Math.max(prev.bbox.x2, curr.bbox.x2),\n y2: Math.max(prev.bbox.y2, curr.bbox.y2),\n },\n vertexRadius: Math.max(prev.vertexRadius, curr.vertexRadius),\n }\n continue\n }\n }\n merged.push(curr)\n }\n return merged\n}\n\n/** 좌표값 클러스터링 — 동적 tolerance 기반 (ODL의 vertex radius 반영) */\nfunction clusterCoordinates(values: number[], tolerance: number): number[] {\n if (values.length === 0) return []\n const sorted = [...values].sort((a, b) => a - b)\n const clusters: { sum: number; count: number }[] = [{ sum: sorted[0], count: 1 }]\n\n for (let i = 1; i < sorted.length; i++) {\n const last = clusters[clusters.length - 1]\n const avg = last.sum / last.count\n if (Math.abs(sorted[i] - avg) <= tolerance) {\n last.sum += sorted[i]\n last.count++\n } else {\n clusters.push({ sum: sorted[i], count: 1 })\n }\n }\n\n return clusters.map(c => c.sum / c.count)\n}\n\ntype TypedLine = LineSegment & { type: \"h\" | \"v\"; id: number }\n\n/** 교차하는 선들을 Union-Find로 그룹화 */\nfunction groupConnectedLines(lines: TypedLine[]): TypedLine[][] {\n const parent = lines.map((_, i) => i)\n\n function find(x: number): number {\n while (parent[x] !== x) { parent[x] = parent[parent[x]]; x = parent[x] }\n return x\n }\n function union(a: number, b: number) {\n const ra = find(a), rb = find(b)\n if (ra !== rb) parent[ra] = rb\n }\n\n for (let i = 0; i < lines.length; i++) {\n for (let j = i + 1; j < lines.length; j++) {\n if (linesIntersect(lines[i], lines[j])) {\n union(i, j)\n }\n }\n }\n\n const groups = new Map<number, TypedLine[]>()\n for (let i = 0; i < lines.length; i++) {\n const root = find(i)\n if (!groups.has(root)) groups.set(root, [])\n groups.get(root)!.push(lines[i])\n }\n\n return [...groups.values()]\n}\n\n/** 수평선과 수직선의 교차 판정 (tolerance 포함) */\nfunction linesIntersect(a: TypedLine, b: TypedLine): boolean {\n if (a.type === b.type) {\n if (a.type === \"h\") {\n if (Math.abs(a.y1 - b.y1) > CONNECT_TOL) return false\n return Math.min(a.x2, b.x2) >= Math.max(a.x1, b.x1) - CONNECT_TOL\n } else {\n if (Math.abs(a.x1 - b.x1) > CONNECT_TOL) return false\n return Math.min(a.y2, b.y2) >= Math.max(a.y1, b.y1) - CONNECT_TOL\n }\n }\n\n const h = a.type === \"h\" ? a : b\n const v = a.type === \"h\" ? b : a\n const tol = CONNECT_TOL\n\n return (\n v.x1 >= h.x1 - tol && v.x1 <= h.x2 + tol &&\n h.y1 >= v.y1 - tol && h.y1 <= v.y2 + tol\n )\n}\n\n// ─── 셀 구조 추출 (Vertex 기반 정밀 병합 셀 감지) ─────\n\n/**\n * 테이블 그리드에서 셀 목록을 추출.\n * ODL의 createMatrix() 알고리즘:\n * - 수직선 존재 여부로 colSpan 감지 (75% 커버 기준)\n * - 수평선 존재 여부로 rowSpan 감지 (75% 커버 기준)\n * - 우하단→좌상단 propagation으로 병합 셀 정리\n * - 중복 행/열 제거\n */\nexport function extractCells(\n grid: TableGrid,\n horizontals: LineSegment[],\n verticals: LineSegment[],\n): ExtractedCell[] {\n const { rowYs, colXs } = grid\n const numRows = rowYs.length - 1\n const numCols = colXs.length - 1\n if (numRows <= 0 || numCols <= 0) return []\n\n // 경계선 존재 여부를 행렬로 사전 계산\n // vBorders[r][c] = colXs[c]에 row r 구간의 수직선이 있는지\n const vBorders: boolean[][] = Array.from({ length: numRows },\n (_, r) => Array.from({ length: numCols + 1 },\n (_, c) => hasVerticalLine(verticals, colXs[c], rowYs[r], rowYs[r + 1], grid.vertexRadius)))\n\n // hBorders[r][c] = rowYs[r]에 col c 구간의 수평선이 있는지\n const hBorders: boolean[][] = Array.from({ length: numRows + 1 },\n (_, r) => Array.from({ length: numCols },\n (_, c) => hasHorizontalLine(horizontals, rowYs[r], colXs[c], colXs[c + 1], grid.vertexRadius)))\n\n // 셀이 이미 병합된 셀에 포함되는지 추적\n const occupied = Array.from({ length: numRows }, () => Array(numCols).fill(false))\n const cells: ExtractedCell[] = []\n\n for (let r = 0; r < numRows; r++) {\n for (let c = 0; c < numCols; c++) {\n if (occupied[r][c]) continue\n\n let colSpan = 1\n let rowSpan = 1\n\n // colSpan: 오른쪽 내부 경계에 수직선이 없으면 병합\n while (c + colSpan < numCols && !vBorders[r][c + colSpan]) {\n // 추가 검증: 확장하려는 영역의 모든 행에서 수직선이 없어야 함\n let canExpand = true\n for (let dr = 0; dr < rowSpan; dr++) {\n if (vBorders[r + dr][c + colSpan]) { canExpand = false; break }\n }\n if (!canExpand) break\n colSpan++\n }\n\n // rowSpan: 아래쪽 내부 경계에 수평선이 없으면 병합\n while (r + rowSpan < numRows) {\n let hasLine = false\n for (let dc = 0; dc < colSpan; dc++) {\n if (hBorders[r + rowSpan][c + dc]) { hasLine = true; break }\n }\n if (hasLine) break\n rowSpan++\n }\n\n // 병합 영역 마킹\n for (let dr = 0; dr < rowSpan; dr++) {\n for (let dc = 0; dc < colSpan; dc++) {\n occupied[r + dr][c + dc] = true\n }\n }\n\n cells.push({\n row: r, col: c, rowSpan, colSpan,\n bbox: {\n x1: colXs[c], y1: rowYs[r + rowSpan],\n x2: colXs[c + colSpan], y2: rowYs[r],\n },\n })\n }\n }\n\n return cells\n}\n\n/**\n * 특정 X 위치에 수직선이 Y 범위를 커버하는지 확인.\n * v2: 75% 커버 기준 + 동적 tolerance (vertex radius 기반)\n */\nfunction hasVerticalLine(\n verticals: LineSegment[], x: number, topY: number, botY: number, vertexRadius: number,\n): boolean {\n const tol = Math.max(VERTEX_MERGE_FACTOR * vertexRadius, 4)\n for (const v of verticals) {\n if (Math.abs(v.x1 - x) <= tol) {\n const cellH = Math.abs(topY - botY)\n if (cellH < 0.1) continue\n const overlapTop = Math.min(v.y2, topY)\n const overlapBot = Math.max(v.y1, botY)\n const overlap = overlapTop - overlapBot\n // 75% 커버 기준 (기존 50% → 병합 셀 내부 단선 오탐 방지)\n if (overlap >= cellH * 0.75) return true\n }\n }\n return false\n}\n\n/**\n * 특정 Y 위치에 수평선이 X 범위를 커버하는지 확인.\n * v2: 75% 커버 기준 + 동적 tolerance\n */\nfunction hasHorizontalLine(\n horizontals: LineSegment[], y: number, leftX: number, rightX: number, vertexRadius: number,\n): boolean {\n const tol = Math.max(VERTEX_MERGE_FACTOR * vertexRadius, 4)\n for (const h of horizontals) {\n if (Math.abs(h.y1 - y) <= tol) {\n const cellW = Math.abs(rightX - leftX)\n if (cellW < 0.1) continue\n const overlapLeft = Math.max(h.x1, leftX)\n const overlapRight = Math.min(h.x2, rightX)\n const overlap = overlapRight - overlapLeft\n if (overlap >= cellW * 0.75) return true\n }\n }\n return false\n}\n\n// ─── 텍스트→셀 매핑 ──────────────────────────────────\n\nexport interface TextItem {\n text: string\n x: number; y: number; w: number; h: number\n fontSize: number; fontName: string\n /** pdfjs 공백 아이템이 이 아이템 직전에 있었음 — 단어 경계 힌트 (parser.ts NormItem에서 전파) */\n hasSpaceBefore?: boolean\n}\n\n/**\n * 공백 삽입 갭 임계값 — 폰트 크기 비례.\n * 절대 px(3px) 기준은 Type3 폰트(예: fontSize 10.5에서 단어 갭 2.7px)에서 공백이\n * 소실되고, 작은 폰트에서는 과다 삽입됨. fontSize×0.17 비례 기준으로 교체\n * (veraPDF wcag-algs TEXT_LINE_SPACE_RATIO 아이디어의 클린룸 재구현 — 코드 비복사).\n */\nexport const SPACE_GAP_RATIO = 0.17\n\nexport function spaceGapThreshold(fontSize: number): number {\n return Math.max(fontSize * SPACE_GAP_RATIO, 1)\n}\n\n/**\n * 텍스트 아이템을 셀에 매핑.\n * v2: ODL의 getIntersectionPercent 방식 — 텍스트 bbox와 셀 bbox의 교차 비율로 판별.\n * 중심점만 보는 기존 방식보다 정확 (긴 텍스트가 셀 경계를 걸치는 경우 처리).\n */\nexport function mapTextToCells(\n items: TextItem[],\n cells: ExtractedCell[],\n): Map<ExtractedCell, TextItem[]> {\n const result = new Map<ExtractedCell, TextItem[]>()\n for (const cell of cells) {\n result.set(cell, [])\n }\n\n for (const item of items) {\n const pad = CELL_PADDING\n\n let bestCell: ExtractedCell | null = null\n let bestScore = 0\n\n for (const cell of cells) {\n // 텍스트 bbox와 셀 bbox의 교차 영역 계산\n const ix1 = Math.max(item.x, cell.bbox.x1 - pad)\n const ix2 = Math.min(item.x + item.w, cell.bbox.x2 + pad)\n const iy1 = Math.max(item.y, cell.bbox.y1 - pad)\n const iy2 = Math.min(item.y + (item.h || item.fontSize), cell.bbox.y2 + pad)\n\n if (ix1 >= ix2 || iy1 >= iy2) continue\n\n const intersectArea = (ix2 - ix1) * (iy2 - iy1)\n const itemArea = Math.max(item.w, 1) * Math.max(item.h || item.fontSize, 1)\n const score = intersectArea / itemArea // ODL의 MIN_CELL_CONTENT_INTERSECTION_PERCENT\n\n if (score > bestScore) {\n bestScore = score\n bestCell = cell\n }\n }\n\n // 교차 비율 > 0.3이면 셀에 할당 (ODL은 0.6이지만 PDF 텍스트 좌표 오차 고려)\n if (bestCell && bestScore > 0.3) {\n result.get(bestCell)!.push(item)\n }\n }\n\n return result\n}\n\n/**\n * 셀 내 텍스트 아이템을 읽기 순서로 정렬 후 합치기.\n * Y 내림차순 (위→아래) → X 오름차순 (좌→우)\n */\nexport function cellTextToString(items: TextItem[]): string {\n if (items.length === 0) return \"\"\n if (items.length === 1) return items[0].text\n\n // Y좌표로 행 그룹핑 (tolerance: max(3, fontSize*0.6))\n const sorted = [...items].sort((a, b) => b.y - a.y || a.x - b.x)\n const lines: TextItem[][] = []\n let curLine: TextItem[] = [sorted[0]]\n let curY = sorted[0].y\n\n for (let i = 1; i < sorted.length; i++) {\n const tol = Math.max(3, Math.min(sorted[i].fontSize, curLine[0].fontSize) * 0.6)\n if (Math.abs(sorted[i].y - curY) <= tol) {\n curLine.push(sorted[i])\n } else {\n lines.push(curLine)\n curLine = [sorted[i]]\n curY = sorted[i].y\n }\n }\n lines.push(curLine)\n\n // 각 행을 텍스트로 변환 — 좌표 기반 균등배분 감지 포함\n const textLines = lines.map(line => {\n const s = line.sort((a, b) => a.x - b.x)\n if (s.length === 1) return s[0].text\n\n // 균등배분 구간 감지 (좌표 기반)\n const evenSpaced = detectEvenSpacedItems(s)\n\n let result = s[0].text\n for (let j = 1; j < s.length; j++) {\n // 균등배분 구간이면 무조건 공백 없이 합침\n if (evenSpaced[j]) {\n result += s[j].text\n continue\n }\n\n const gap = s[j].x - (s[j - 1].x + s[j - 1].w)\n const avgFs = (s[j].fontSize + s[j - 1].fontSize) / 2\n // pdfjs 공백 아이템 힌트 — 단어 경계 확정 (Type3 폰트 글자 분리 셀 텍스트 복원)\n if (s[j].hasSpaceBefore && gap >= avgFs * 0.05) {\n result += \" \" + s[j].text\n } else if (gap > spaceGapThreshold(avgFs)) {\n result += \" \" + s[j].text\n } else {\n result += s[j].text\n }\n }\n return result\n })\n\n return mergeCellTextLines(textLines)\n}\n\n/**\n * 좌표 기반 균등배분 감지 — TextItem 배열에서 한글 1~2자 아이템이\n * 일정 간격으로 3개+ 연속되면 균등배분으로 판단.\n * ODL TextLineProcessor의 핵심 로직을 좌표 기반으로 구현.\n */\nfunction detectEvenSpacedItems(items: TextItem[]): boolean[] {\n const result = new Array(items.length).fill(false)\n if (items.length < 3) return result\n\n let runStart = -1\n for (let i = 0; i < items.length; i++) {\n // 균등배분 = 한글 1자 개별 배치. 2자 단어는 균등배분이 아니라 실제 단어.\n const isShortKorean = /^[가-힣]{1}$/.test(items[i].text) || /^[\\d]{1}$/.test(items[i].text)\n\n // 명시적 공백 글리프가 직전에 있으면 단어 경계 — 균등배분 run 분리.\n // (Type3 폰트가 글자를 1자씩 배치하면서 공백 글리프를 따로 두는 경우,\n // 진짜 단어 경계를 균등배분으로 오판해 문장 전체가 붙는 것을 방지)\n if (isShortKorean && runStart >= 0 && items[i].hasSpaceBefore) {\n if (i - runStart >= 3) markEvenRun(items, result, runStart, i)\n runStart = i\n continue\n }\n\n // 이전 아이템과의 갭이 fontSize*3+ 이면 run 끊기 (다른 영역)\n if (isShortKorean && runStart >= 0 && i > 0) {\n const gap = items[i].x - (items[i - 1].x + items[i - 1].w)\n const maxRunGap = Math.max(items[i].fontSize * 3, 30)\n if (gap > maxRunGap) {\n if (i - runStart >= 3) markEvenRun(items, result, runStart, i)\n runStart = i\n continue\n }\n }\n\n if (isShortKorean) {\n if (runStart < 0) runStart = i\n } else {\n if (runStart >= 0 && i - runStart >= 3) {\n markEvenRun(items, result, runStart, i)\n }\n runStart = -1\n }\n }\n if (runStart >= 0 && items.length - runStart >= 3) {\n markEvenRun(items, result, runStart, items.length)\n }\n\n return result\n}\n\nfunction markEvenRun(items: TextItem[], result: boolean[], start: number, end: number): void {\n const gaps: number[] = []\n for (let i = start + 1; i < end; i++) {\n gaps.push(items[i].x - (items[i - 1].x + items[i - 1].w))\n }\n const posGaps = gaps.filter(g => g > 0)\n if (posGaps.length < 2) return\n\n let minGap = Infinity, maxGap = -Infinity\n for (const g of posGaps) { if (g < minGap) minGap = g; if (g > maxGap) maxGap = g }\n const avgFs = items[start].fontSize\n\n // 간격이 fontSize의 0.1~3배 사이이고, 최대/최소 비율 3배 이내\n if (minGap >= avgFs * 0.1 && maxGap <= avgFs * 3 && maxGap / Math.max(minGap, 0.1) <= 3) {\n for (let i = start + 1; i < end; i++) {\n result[i] = true\n }\n }\n}\n\nexport { detectEvenSpacedItems }\n\n// ─── 과소분할 표 재구성 (ODL TableStructureNormalizer 포팅) ──\n//\n// 행 구분선이 생략된 표(헤더 아래만 선이 있는 한국 공문서 표)는 본문 전체가\n// 1~2행으로 합쳐진다. 셀 안에 텍스트 줄이 8개+ 뭉친 경우 줄의 centerY로\n// row band를 재유도해 행을 복원한다. 품질이 개선될 때만 교체.\n//\n// Original work: Copyright 2025-2026 Hancom Inc. (Apache-2.0)\n// https://github.com/opendataloader-project/opendataloader-pdf\n\nconst MAX_UNDERSEGMENTED_ROWS = 2\nconst MIN_UNDERSEGMENTED_COLUMNS = 3\nconst MIN_UNDERSEGMENTED_TEXT_LINES = 8\nconst MIN_ROW_BAND_MISMATCH = 2\nconst MIN_ROW_BAND_EPSILON = 3.0\nconst ROW_BAND_EPSILON_RATIO = 0.6\n\ninterface RowBand {\n centerY: number\n avgHeight: number\n topY: number\n bottomY: number\n lineCount: number\n /** 컬럼별 아이템 */\n itemsByCol: TextItem[][]\n}\n\n/** 아이템 중심 Y (h가 0이면 fontSize 대용) */\nfunction itemCenterY(item: TextItem): number {\n return item.y + (item.h > 0 ? item.h : item.fontSize) / 2\n}\n\nfunction itemHeight(item: TextItem): number {\n return item.h > 0 ? item.h : item.fontSize\n}\n\n/** 아이템을 colXs 경계 기준 컬럼에 배정 (중심 X 기준, 범위 밖이면 최근접) */\nfunction findColumnIndex(item: TextItem, colXs: number[]): number {\n const cx = item.x + item.w / 2\n for (let c = 0; c < colXs.length - 1; c++) {\n if (cx >= colXs[c] && cx <= colXs[c + 1]) return c\n }\n let best = 0\n let bestDist = Infinity\n for (let c = 0; c < colXs.length - 1; c++) {\n const center = (colXs[c] + colXs[c + 1]) / 2\n const d = Math.abs(cx - center)\n if (d < bestDist) { bestDist = d; best = c }\n }\n return best\n}\n\n/** 아이템들을 Y 기준 시각적 줄로 그룹핑 */\nfunction groupItemsToVisualLines(items: TextItem[]): TextItem[][] {\n if (items.length === 0) return []\n const sorted = [...items].sort((a, b) => b.y - a.y || a.x - b.x)\n const lines: TextItem[][] = []\n let cur: TextItem[] = [sorted[0]]\n let curY = sorted[0].y\n for (let i = 1; i < sorted.length; i++) {\n const tol = Math.max(3, Math.min(sorted[i].fontSize, cur[0].fontSize) * 0.6)\n if (Math.abs(sorted[i].y - curY) <= tol) {\n cur.push(sorted[i])\n } else {\n lines.push(cur)\n cur = [sorted[i]]\n curY = sorted[i].y\n }\n }\n lines.push(cur)\n return lines\n}\n\n/**\n * 과소분할 표 재구성. 조건(행≤2 + 열≥3 + dense 컬럼 2개+)을 만족하고\n * row band 재유도가 품질을 개선할 때만 새 셀 행렬을 반환, 아니면 null.\n *\n * @param originalCells 기존 셀 행렬 (품질 비교용)\n * @param colXs 그리드 열 경계\n * @param items 표 영역 내 텍스트 아이템\n */\nexport function normalizeUndersegmentedTable(\n originalCells: { text: string }[][],\n colXs: number[],\n items: TextItem[],\n): string[][] | null {\n const numRows = originalCells.length\n const numCols = colXs.length - 1\n if (numRows > MAX_UNDERSEGMENTED_ROWS || numCols < MIN_UNDERSEGMENTED_COLUMNS) return null\n if (items.length === 0) return null\n\n // 1) 컬럼별 의미있는 줄 수 — dense 컬럼(8줄+) 2개 이상이어야 과소분할로 판정\n const itemsByCol: TextItem[][] = Array.from({ length: numCols }, () => [])\n for (const item of items) {\n if (!item.text.trim()) continue\n itemsByCol[findColumnIndex(item, colXs)].push(item)\n }\n let denseColumns = 0\n for (const colItems of itemsByCol) {\n if (groupItemsToVisualLines(colItems).length >= MIN_UNDERSEGMENTED_TEXT_LINES) denseColumns++\n }\n if (denseColumns < 2) return null\n\n // 2) 전체 줄에서 row band 유도 — centerY 근접(epsilon) 또는 수직 겹침이면 같은 band\n const allLines = groupItemsToVisualLines(items.filter(i => i.text.trim()))\n const bands: RowBand[] = []\n for (const line of allLines) {\n let cy = 0, h = 0\n for (const it of line) { cy += itemCenterY(it); h += itemHeight(it) }\n cy /= line.length\n h /= line.length\n const top = cy + h / 2\n const bottom = cy - h / 2\n\n let matched: RowBand | null = null\n for (const band of bands) {\n const epsilon = Math.max(MIN_ROW_BAND_EPSILON, Math.min(band.avgHeight, h) * ROW_BAND_EPSILON_RATIO)\n if (Math.abs(band.centerY - cy) <= epsilon ||\n (bottom <= band.topY && top >= band.bottomY)) {\n matched = band\n break\n }\n }\n if (!matched) {\n matched = { centerY: 0, avgHeight: 0, topY: -Infinity, bottomY: Infinity, lineCount: 0, itemsByCol: Array.from({ length: numCols }, () => []) }\n bands.push(matched)\n }\n matched.centerY = (matched.centerY * matched.lineCount + cy) / (matched.lineCount + 1)\n matched.avgHeight = (matched.avgHeight * matched.lineCount + h) / (matched.lineCount + 1)\n matched.topY = Math.max(matched.topY, top)\n matched.bottomY = Math.min(matched.bottomY, bottom)\n matched.lineCount++\n for (const it of line) {\n matched.itemsByCol[findColumnIndex(it, colXs)].push(it)\n }\n }\n\n // 3) band 수가 기존 행 수 + 2 이상이어야 재구축 의미 있음\n if (bands.length < numRows + MIN_ROW_BAND_MISMATCH) return null\n\n bands.sort((a, b) => b.centerY - a.centerY)\n\n // 4) 셀 행렬 재구축\n const rebuilt: string[][] = bands.map(band =>\n band.itemsByCol.map(colItems => colItems.length > 0 ? cellTextToString(colItems) : \"\"),\n )\n\n // 5) 품질 검증: 비어있지 않은 행 수가 늘고, 비어있지 않은 열 수가 줄지 않아야 교체\n const countNonEmptyRows = (cells: { text: string }[][] | string[][]) =>\n cells.filter(row => row.some(c => (typeof c === \"string\" ? c : c.text).trim() !== \"\")).length\n const countNonEmptyCols = (cells: { text: string }[][] | string[][], cols: number) => {\n let n = 0\n for (let c = 0; c < cols; c++) {\n if (cells.some(row => row[c] != null && (typeof row[c] === \"string\" ? row[c] as string : (row[c] as { text: string }).text).trim() !== \"\")) n++\n }\n return n\n }\n\n if (countNonEmptyRows(rebuilt) <= countNonEmptyRows(originalCells)) return null\n if (countNonEmptyCols(rebuilt, numCols) < countNonEmptyCols(originalCells, numCols)) return null\n\n return rebuilt\n}\n\n/**\n * 셀 내 텍스트 아이템을 읽기 순서로 정렬 후 합치기 — 줄바꿈 병합 전용.\n * (cellTextToString 내부에서 사용)\n */\nfunction mergeCellTextLines(textLines: string[]): string {\n // 셀 내 줄바꿈 병합 — 잘린 단어/숫자 조각 복구\n if (textLines.length <= 1) return textLines[0] || \"\"\n const merged: string[] = [textLines[0]]\n for (let i = 1; i < textLines.length; i++) {\n const prev = merged[merged.length - 1]\n const curr = textLines[i]\n if (/[가-힣]$/.test(prev) && /^[가-힣]+$/.test(curr) && curr.length <= 8 && !curr.includes(\" \")) {\n merged[merged.length - 1] = prev + curr\n }\n else if (curr.trim().length <= 3 && /^[)\\]%}]/.test(curr.trim())) {\n merged[merged.length - 1] = prev + curr.trim()\n }\n else if (/[,(]$/.test(prev.trim()) && curr.trim().length <= 15) {\n merged[merged.length - 1] = prev + curr.trim()\n }\n else if (/[\\d,]$/.test(prev) && /^[\\d,]+[)\\]]?$/.test(curr.trim()) && curr.trim().length <= 10) {\n merged[merged.length - 1] = prev + curr.trim()\n }\n else {\n merged.push(curr)\n }\n }\n return merged.join(\"\\n\")\n}\n","/**\n * 클러스터 기반 테이블 감지 — 선이 없는 PDF에서 텍스트 정렬 패턴으로 테이블 구조 추론.\n *\n * Original work: Copyright 2025-2026 Hancom, Inc.\n * Licensed under the Apache License, Version 2.0\n * https://github.com/opendataloader-project/opendataloader-pdf\n *\n * ODL의 ClusterTableConsumer를 kordoc 컨텍스트에 맞게 단순화한 구현.\n * Modifications: TypeScript 재구현, 최소 2열 감지, 한국어 PDF 특화 최적화.\n *\n * 핵심 아이디어:\n * 1. 균등배분(개별 글자) 아이템을 사전 병합하여 노이즈 제거\n * 2. 헤더 행(짧은 라벨 + 넓은 갭) 감지 → 열 앵커로 사용\n * 3. 헤더 없으면 기존 X좌표 클러스터링으로 열 감지\n * 4. 다중행 셀(같은 논리 행이 여러 Y에 걸침) 병합\n */\n\nimport type { IRTable, IRCell, BoundingBox } from \"../types.js\"\n\n/** parser.ts의 NormItem과 동일한 인터페이스 */\nexport interface ClusterItem {\n text: string\n x: number\n y: number\n w: number\n h: number\n fontSize: number\n fontName: string\n /** pdfjs 공백 아이템이 직전에 있었음 — 단어 경계 힌트 */\n hasSpaceBefore?: boolean\n}\n\n// ─── 상수 ──────────────────────────────────────────────\n/** baseline 그룹핑 허용 오차 (pt) */\nconst Y_TOL = 3\n/** 열 클러스터링 허용 오차 (pt) */\nconst COL_CLUSTER_TOL = 15\n/** 테이블로 인정하기 위한 최소 행 수 */\nconst MIN_ROWS = 3\n/** 테이블로 인정하기 위한 최소 열 수 */\nconst MIN_COLS = 2\n/** 같은 행 내 아이템 간 최소 갭 (테이블 컬럼 구분) — fontSize 배수 */\nconst MIN_GAP_FACTOR = 2.0\n/** 같은 행 내 아이템 간 최소 갭 절대값 (pt) */\nconst MIN_GAP_ABSOLUTE = 20\n/** 열에 값이 있는 행의 비율 최소 기준 */\nconst MIN_COL_FILL_RATIO = 0.4\n\ninterface RowGroup {\n y: number // 대표 Y좌표 (평균 baseline)\n items: ClusterItem[]\n}\n\ninterface ColCluster {\n x: number // 열 X좌표 (왼쪽 경계)\n count: number // 이 열에 속한 아이템 수\n}\n\n/** 헤더 감지 결과: 열 앵커 + 헤더 행 인덱스 */\ninterface HeaderResult {\n columns: ColCluster[]\n headerIdx: number\n}\n\nexport interface ClusterTableResult {\n table: IRTable\n bbox: BoundingBox\n usedItems: Set<ClusterItem>\n}\n\n/**\n * 클러스터 기반 테이블 감지. 선이 없는 PDF의 fallback 경로에서 호출.\n */\nexport function detectClusterTables(items: ClusterItem[], pageNum: number): ClusterTableResult[] {\n if (items.length < MIN_ROWS * MIN_COLS) return []\n\n // 0. 균등배분 아이템 사전 병합 (개별 글자 → 단어)\n const { merged, originMap } = mergeEvenSpacedClusters(items)\n\n // 1. Y좌표로 행 그룹핑 + 첨자 행 복원\n // 본문 줄에서 살짝 올라간 각주 마커(*)·덧말은 baseline 그룹핑(Y_TOL)에서 별도 행으로\n // 떨어진다. 이런 조각 행이 표 헤더/열 앵커로 오인되면 본문 문단이 통째로 표에 흡수되므로\n // 수직으로 겹치는 행은 같은 시각적 줄로 병합한다.\n const rows = mergeOverlappingRows(groupByBaseline(merged))\n if (rows.length < MIN_ROWS) return []\n\n const results: ClusterTableResult[] = []\n\n // 2. 헤더 행 기반 열 감지 시도\n const headerResult = detectHeaderRow(rows)\n\n if (headerResult) {\n // 헤더 기반: 헤더 이후 행에서 boundary 기반 열 구조 매칭\n const { columns, headerIdx } = headerResult\n const headerRow = rows[headerIdx]\n const headerItems = [...headerRow.items].sort((a, b) => a.x - b.x)\n const headerAndBelow = rows.slice(headerIdx)\n // 다중행 셀 병합\n const mergedRows = mergeMultiLineRows(headerAndBelow, columns)\n // boundary 기반 region 탐색 (proximity 대신 headerItems 범위 사용)\n const tableRegions = findTableRegionsByHeader(mergedRows, columns, headerItems)\n for (const region of tableRegions) {\n const table = buildClusterTable(region.rows, columns, pageNum)\n if (table) {\n expandUsedItems(table.usedItems, originMap)\n results.push(table)\n }\n }\n }\n\n if (results.length === 0) {\n // 3. 기존 방식: suspicious gaps + column clustering\n const suspiciousRows = rows.filter(row => hasSuspiciousGaps(row))\n if (suspiciousRows.length >= MIN_ROWS) {\n const columns = extractColumnClusters(suspiciousRows)\n if (columns.length >= MIN_COLS) {\n const tableRegions = findTableRegions(rows, columns)\n for (const region of tableRegions) {\n const mergedRows = mergeMultiLineRows(region.rows, columns)\n const table = buildClusterTable(mergedRows, columns, pageNum)\n if (table) {\n expandUsedItems(table.usedItems, originMap)\n results.push(table)\n }\n }\n }\n }\n }\n\n return results\n}\n\n// ─── 균등배분 사전 병합 ──────────────────────────────────\n\n/** 균등배분(1글자+균일간격) 아이템을 단어 단위로 병합. 원본→병합 매핑도 반환. */\nfunction mergeEvenSpacedClusters(\n items: ClusterItem[],\n): { merged: ClusterItem[]; originMap: Map<ClusterItem, ClusterItem[]> } {\n const originMap = new Map<ClusterItem, ClusterItem[]>()\n const rows = groupByBaseline(items)\n const merged: ClusterItem[] = []\n\n for (const row of rows) {\n const sorted = [...row.items].sort((a, b) => a.x - b.x)\n let i = 0\n while (i < sorted.length) {\n if (/^[가-힣\\d]$/.test(sorted[i].text)) {\n let runEnd = i + 1\n while (runEnd < sorted.length && /^[가-힣\\d]$/.test(sorted[runEnd].text)) {\n // 명시적 공백 글리프 = 단어 경계 → run 분리 (Type3 글자 분리 배치 오판 방지)\n if (sorted[runEnd].hasSpaceBefore) break\n const gap = sorted[runEnd].x - (sorted[runEnd - 1].x + sorted[runEnd - 1].w)\n const fs = sorted[runEnd].fontSize\n if (gap < fs * 0.1 || gap > fs * 3) break\n runEnd++\n }\n if (runEnd - i >= 3) {\n const gaps: number[] = []\n for (let g = i + 1; g < runEnd; g++) {\n gaps.push(sorted[g].x - (sorted[g - 1].x + sorted[g - 1].w))\n }\n let minG = Infinity, maxG = -Infinity\n for (const g of gaps) { if (g < minG) minG = g; if (g > maxG) maxG = g }\n if (minG > 0 && maxG / minG <= 3) {\n const run = sorted.slice(i, runEnd)\n const text = run.map(r => r.text).join(\"\")\n const first = run[0], last = run[runEnd - i - 1]\n const item: ClusterItem = {\n text, x: first.x, y: first.y,\n w: (last.x + last.w) - first.x, h: first.h,\n fontSize: first.fontSize, fontName: first.fontName,\n }\n originMap.set(item, run)\n merged.push(item)\n i = runEnd\n continue\n }\n }\n }\n merged.push(sorted[i])\n i++\n }\n }\n return { merged, originMap }\n}\n\n/** 병합된 ClusterItem의 usedItems를 원본 아이템으로 확장 */\nfunction expandUsedItems(usedItems: Set<ClusterItem>, originMap: Map<ClusterItem, ClusterItem[]>): void {\n const toAdd: ClusterItem[] = []\n for (const item of usedItems) {\n const origins = originMap.get(item)\n if (origins) for (const o of origins) toAdd.push(o)\n }\n for (const a of toAdd) usedItems.add(a)\n}\n\n// ─── 헤더 행 기반 열 감지 ───────────────────────────────\n\n/**\n * 상위 행에서 테이블 헤더 후보를 탐색.\n * 조건: 2~6개 짧은 아이템, 최소 1개 큰 갭(>2.5x fontSize), 넓은 X 범위,\n * 한글 포함, 후속 행에서 최소 MIN_ROWS개가 헤더 열에 매칭\n */\nfunction detectHeaderRow(rows: RowGroup[]): HeaderResult | null {\n const allItems = rows.flatMap(r => r.items)\n if (allItems.length === 0) return null\n let allMinX = Infinity, allMaxX = -Infinity\n for (const i of allItems) { if (i.x < allMinX) allMinX = i.x; const r = i.x + i.w; if (r > allMaxX) allMaxX = r }\n const pageSpan = allMaxX - allMinX\n if (pageSpan <= 0) return null\n\n // 전체 행에서 헤더 후보 탐색 — 가드가 충분히 엄격하므로 범위 제한 불필요\n for (let ri = 0; ri < rows.length; ri++) {\n const row = rows[ri]\n // 2~6 아이템\n if (row.items.length < MIN_COLS || row.items.length > 6) continue\n // 모든 아이템 짧아야 함 (각 8자 이내)\n if (row.items.some(i => i.text.length > 8)) continue\n // 한글 포함 아이템이 최소 1개\n if (!row.items.some(i => /[가-힣]/.test(i.text))) continue\n // 마커(□○●·※▶-) 시작 아이템 제외\n if (row.items.some(i => /^[□■○●·※▶▷◆◇\\-]/.test(i.text))) continue\n\n const sorted = [...row.items].sort((a, b) => a.x - b.x)\n const xSpan = (sorted[sorted.length - 1].x + sorted[sorted.length - 1].w) - sorted[0].x\n\n // X 범위가 전체의 40%+ 차지\n if (xSpan / pageSpan < 0.4) continue\n\n // 최소 1개 갭이 avgFontSize의 2.5배 이상\n const avgFs = sorted.reduce((s, i) => s + i.fontSize, 0) / sorted.length\n let hasLargeGap = false\n for (let i = 1; i < sorted.length; i++) {\n const gap = sorted[i].x - (sorted[i - 1].x + sorted[i - 1].w)\n if (gap >= avgFs * 2.5) { hasLargeGap = true; break }\n }\n if (!hasLargeGap) continue\n\n // 열 앵커 생성\n const columns: ColCluster[] = sorted.map(item => ({ x: item.x, count: 0 }))\n\n // 후속 행 검증: 헤더 이후 MIN_ROWS+ 행이 2+ 열에 매칭\n let matchCount = 0\n for (let j = ri + 1; j < rows.length && matchCount < MIN_ROWS + 2; j++) {\n const matched = countMatchedColumnsRange(rows[j], columns, sorted)\n if (matched >= MIN_COLS) matchCount++\n }\n if (matchCount < MIN_ROWS) continue\n\n return { columns, headerIdx: ri }\n }\n return null\n}\n\n/**\n * 수직으로 겹치는 인접 행 병합 — 첨자(각주 마커/덧말)가 별도 행으로 분리된 것 복원.\n * 첨자 조각 행: 아이템이 적고(≤3) 짧으며(≤8자), 겹치는 행보다 글자 박스가 확실히\n * 작은 행(높이 ≤0.8배). 이런 행만 본문 줄에 흡수한다.\n * (rowspan 라벨처럼 키가 큰 셀이 이웃 행을 덮는 경우는 병합하지 않음 — 정상 표 구조)\n */\nfunction mergeOverlappingRows(rows: RowGroup[]): RowGroup[] {\n if (rows.length <= 1) return rows\n const result: RowGroup[] = [rows[0]]\n for (let i = 1; i < rows.length; i++) {\n const prev = result[result.length - 1]\n const curr = rows[i]\n const a = rowBand(prev)\n const b = rowBand(curr)\n const overlap = Math.min(a.top, b.top) - Math.max(a.bottom, b.bottom)\n const prevIsFrag = isFragmentRow(prev) && a.height <= b.height * 0.8 && overlap >= a.height * 0.5\n const currIsFrag = isFragmentRow(curr) && b.height <= a.height * 0.8 && overlap >= b.height * 0.5\n if (prevIsFrag || currIsFrag) {\n // 본문 줄(흡수하는 쪽)의 y를 대표값으로 유지\n const baseY = prevIsFrag ? curr.y : prev.y\n result[result.length - 1] = { y: baseY, items: [...prev.items, ...curr.items] }\n } else {\n result.push(curr)\n }\n }\n return result\n}\n\n/** 첨자 후보 행: 아이템 ≤3개, 모두 짧은 텍스트(≤8자) */\nfunction isFragmentRow(row: RowGroup): boolean {\n return row.items.length <= 3 && row.items.every(i => i.text.length <= 8)\n}\n\n/** 행 아이템들의 수직 범위 (bottom=min y, top=max y+h) */\nfunction rowBand(row: RowGroup): { bottom: number; top: number; height: number } {\n let bottom = Infinity, top = -Infinity\n for (const i of row.items) {\n const h = i.h > 0 ? i.h : i.fontSize\n if (i.y < bottom) bottom = i.y\n if (i.y + h > top) top = i.y + h\n }\n return { bottom, top, height: top - bottom }\n}\n\n// ─── 다중행 셀 병합 ────────────────────────────────────\n\n/** 연속 행에서 MIN_COLS 미만 열만 사용하고 Y갭이 작으면 이전 행에 병합 */\nfunction mergeMultiLineRows(rows: RowGroup[], columns: ColCluster[]): RowGroup[] {\n if (rows.length <= 1) return rows\n const result: RowGroup[] = [rows[0]]\n const allFontSizes = rows.flatMap(r => r.items).map(i => i.fontSize)\n const avgFontSize = allFontSizes.length > 0\n ? allFontSizes.reduce((s, v) => s + v, 0) / allFontSizes.length : 12\n\n for (let i = 1; i < rows.length; i++) {\n const prev = result[result.length - 1]\n const curr = rows[i]\n const yGap = Math.abs(prev.y - curr.y)\n const matchedCols = countMatchedColumns(curr, columns)\n\n // 병합 조건: Y갭이 작고 + 아이템 수가 적음 (연속 행 = 1~2개 아이템)\n // 아이템이 많은 행(3+)은 독립적인 데이터 행 → 병합하지 않음\n if (yGap < avgFontSize * 1.8 && curr.items.length <= 2 && (matchedCols < MIN_COLS || curr.items.length === 1)) {\n result[result.length - 1] = {\n y: prev.y,\n items: [...prev.items, ...curr.items],\n }\n } else {\n result.push(curr)\n }\n }\n return result\n}\n\n// ─── 기본 유틸 ──────────────────────────────────────────\n\n/** 아이템을 baseline(Y좌표)으로 그룹핑 */\nfunction groupByBaseline(items: ClusterItem[]): RowGroup[] {\n if (items.length === 0) return []\n\n const sorted = [...items].sort((a, b) => b.y - a.y || a.x - b.x)\n const rows: RowGroup[] = []\n let curItems: ClusterItem[] = [sorted[0]]\n let curY = sorted[0].y\n\n for (let i = 1; i < sorted.length; i++) {\n if (Math.abs(sorted[i].y - curY) <= Y_TOL) {\n curItems.push(sorted[i])\n } else {\n rows.push({ y: curY, items: curItems })\n curItems = [sorted[i]]\n curY = sorted[i].y\n }\n }\n if (curItems.length > 0) rows.push({ y: curY, items: curItems })\n\n return rows\n}\n\n/** 행 내 아이템 간 \"의심스러운\" 갭 존재 여부 (테이블 열 구분 후보) */\nfunction hasSuspiciousGaps(row: RowGroup): boolean {\n if (row.items.length < 2) return false\n\n const sorted = [...row.items].sort((a, b) => a.x - b.x)\n // 가드: 2아이템 행에서 두 번째 아이템이 긴 텍스트면 들여쓰기 단락\n if (sorted.length === 2 && sorted[1].text.length > 20) return false\n\n const avgFontSize = sorted.reduce((s, i) => s + i.fontSize, 0) / sorted.length\n const minGap = Math.max(avgFontSize * MIN_GAP_FACTOR, MIN_GAP_ABSOLUTE)\n\n for (let i = 1; i < sorted.length; i++) {\n const gap = sorted[i].x - (sorted[i - 1].x + sorted[i - 1].w)\n if (gap >= minGap) return true\n }\n return false\n}\n\n/** 의심스러운 행들의 X좌표에서 열 클러스터 추출 */\nfunction extractColumnClusters(rows: RowGroup[]): ColCluster[] {\n const allX: number[] = []\n for (const row of rows) {\n for (const item of row.items) allX.push(item.x)\n }\n if (allX.length === 0) return []\n\n allX.sort((a, b) => a - b)\n\n const clusters: ColCluster[] = []\n let clusterStart = 0\n\n for (let i = 1; i <= allX.length; i++) {\n if (i === allX.length || allX[i] - allX[i - 1] > COL_CLUSTER_TOL) {\n const slice = allX.slice(clusterStart, i)\n const avg = Math.round(slice.reduce((s, v) => s + v, 0) / slice.length)\n clusters.push({ x: avg, count: slice.length })\n clusterStart = i\n }\n }\n\n const minCount = Math.max(2, Math.floor(rows.length * MIN_COL_FILL_RATIO))\n return clusters\n .filter(c => c.count >= minCount)\n .sort((a, b) => a.x - b.x)\n}\n\n/** 헤더 기반 테이블 영역 찾기 — boundary 기반 열 매칭, 관대한 종료 조건 */\nfunction findTableRegionsByHeader(\n allRows: RowGroup[], columns: ColCluster[], headerItems: ClusterItem[],\n): { rows: RowGroup[] }[] {\n const regions: { rows: RowGroup[] }[] = []\n let currentRegion: RowGroup[] = []\n let missStreak = 0 // 연속 비매칭 행 수\n\n for (const row of allRows) {\n const matchedCols = countMatchedColumnsRange(row, columns, headerItems)\n if (matchedCols >= MIN_COLS) {\n currentRegion.push(row)\n missStreak = 0\n } else if (currentRegion.length > 0 && (row.items.length <= 2 || missStreak === 0)) {\n // 단일 비매칭은 허용 (multi-line 셀 or 단일 아이템)\n currentRegion.push(row)\n missStreak++\n } else {\n // 연속 2+ 비매칭 → 테이블 종료\n // 끝에 붙은 비매칭 행 제거\n while (currentRegion.length > 0) {\n const last = currentRegion[currentRegion.length - 1]\n if (countMatchedColumnsRange(last, columns, headerItems) >= MIN_COLS) break\n currentRegion.pop()\n }\n if (currentRegion.length >= MIN_ROWS) {\n regions.push({ rows: [...currentRegion] })\n }\n currentRegion = []\n missStreak = 0\n }\n }\n\n // 끝에 붙은 비매칭 행 정리\n while (currentRegion.length > 0) {\n const last = currentRegion[currentRegion.length - 1]\n if (countMatchedColumnsRange(last, columns, headerItems) >= MIN_COLS) break\n currentRegion.pop()\n }\n if (currentRegion.length >= MIN_ROWS) {\n regions.push({ rows: currentRegion })\n }\n\n return regions\n}\n\n/** 연속된 테이블 행 영역 찾기 */\nfunction findTableRegions(allRows: RowGroup[], columns: ColCluster[]): { rows: RowGroup[] }[] {\n const regions: { rows: RowGroup[] }[] = []\n let currentRegion: RowGroup[] = []\n\n for (const row of allRows) {\n const matchedCols = countMatchedColumns(row, columns)\n if (matchedCols >= MIN_COLS) {\n currentRegion.push(row)\n } else if (row.items.length === 1) {\n if (currentRegion.length > 0) {\n currentRegion.push(row)\n }\n } else {\n if (currentRegion.length >= MIN_ROWS) {\n regions.push({ rows: [...currentRegion] })\n }\n currentRegion = []\n }\n }\n\n if (currentRegion.length >= MIN_ROWS) {\n regions.push({ rows: currentRegion })\n }\n\n return regions\n}\n\n/** 행의 아이템이 몇 개의 열에 매칭되는지 (X좌표 근접) */\nfunction countMatchedColumns(row: RowGroup, columns: ColCluster[]): number {\n const matched = new Set<number>()\n for (const item of row.items) {\n for (let ci = 0; ci < columns.length; ci++) {\n if (Math.abs(item.x - columns[ci].x) <= COL_CLUSTER_TOL * 2) {\n matched.add(ci)\n break\n }\n }\n }\n return matched.size\n}\n\n/**\n * 범위 기반 열 매칭 — 헤더 아이템의 x~x+w 범위 내에 있는지 확인.\n * 헤더 열 간 중간점을 경계로 사용하여 넓은 범위 매칭.\n */\nfunction countMatchedColumnsRange(\n row: RowGroup, columns: ColCluster[], headerItems: ClusterItem[],\n): number {\n // 열 경계 계산: 헤더 아이템 사이 중간점\n const boundaries: { left: number; right: number }[] = []\n for (let ci = 0; ci < headerItems.length; ci++) {\n const left = ci === 0 ? 0 : (headerItems[ci - 1].x + headerItems[ci - 1].w + headerItems[ci].x) / 2\n const right = ci === headerItems.length - 1\n ? Infinity\n : (headerItems[ci].x + headerItems[ci].w + headerItems[ci + 1].x) / 2\n boundaries.push({ left, right })\n }\n\n const matched = new Set<number>()\n for (const item of row.items) {\n for (let ci = 0; ci < boundaries.length; ci++) {\n if (item.x >= boundaries[ci].left && item.x < boundaries[ci].right) {\n matched.add(ci)\n break\n }\n }\n }\n return matched.size\n}\n\n/**\n * 행별 갭 분석으로 아이템을 열에 배정.\n *\n * 전역 경계선 대신 각 행을 개별 분석:\n * 1) 행 내 N-1개의 가장 큰 갭으로 N개 그룹 분할\n * 2) 각 그룹의 중심 X를 헤더 열 중심과 비교하여 최적 열 배정\n * 3) 갭이 불충분한 행(아이템 적음)은 헤더 중간점 기반 fallback\n *\n * 이 방식은 열 폭이 행마다 달라도 정확하게 분리.\n */\nfunction assignRowItems(\n items: ClusterItem[], columns: ColCluster[], numCols: number,\n): { col: number; items: ClusterItem[] }[] {\n if (items.length === 0) return []\n const sorted = [...items].sort((a, b) => a.x - b.x)\n\n // 헤더 열 중심 좌표\n const colCenters = columns.map(c => c.x)\n\n // 행 내 갭 계산\n const gaps: { idx: number; size: number }[] = []\n for (let i = 1; i < sorted.length; i++) {\n gaps.push({ idx: i, size: sorted[i].x - (sorted[i - 1].x + sorted[i - 1].w) })\n }\n\n // N-1개의 가장 큰 갭 선택 — 적응형 임계값\n // 아이템이 적은 행(헤더, 라벨 행): 낮은 절대 임계값 (모든 갭이 유의미할 가능성 높음)\n // 아이템이 많은 행(본문 데이터): 상대적 임계값 (워드 갭 vs 컬럼 갭 구분)\n const gapSizes = gaps.map(g => g.size).sort((a, b) => a - b)\n const medianGap = gapSizes.length > 0 ? gapSizes[Math.floor(gapSizes.length / 2)] : 0\n const gapThreshold = sorted.length <= numCols + 1\n ? 12 // 희소 행: 낮은 절대 임계값\n : Math.max(medianGap * 2.5, 12) // 밀집 행: 상대적 임계값\n const significantGaps = gaps\n .filter(g => g.size >= gapThreshold)\n .sort((a, b) => b.size - a.size)\n .slice(0, numCols - 1)\n .sort((a, b) => a.idx - b.idx) // 위치 순 복원\n\n // 가용 갭으로 그룹 분할 (갭이 부족해도 있는 만큼 분할)\n const groups: ClusterItem[][] = []\n let start = 0\n for (const gap of significantGaps) {\n groups.push(sorted.slice(start, gap.idx))\n start = gap.idx\n }\n groups.push(sorted.slice(start))\n\n // 각 그룹 → 가장 가까운 헤더 열에 배정\n const result: { col: number; items: ClusterItem[] }[] = []\n const usedCols = new Set<number>()\n // 그룹별 중심 X 계산\n const groupCenters = groups.map(g => {\n let minX = Infinity, maxX = -Infinity\n for (const i of g) { if (i.x < minX) minX = i.x; const r = i.x + i.w; if (r > maxX) maxX = r }\n return (minX + maxX) / 2\n })\n\n // 탐욕적 배정: 각 그룹을 가장 가까운 미사용 열에 배정\n const assignments: { gi: number; ci: number; dist: number }[] = []\n for (let gi = 0; gi < groups.length; gi++) {\n for (let ci = 0; ci < numCols; ci++) {\n assignments.push({ gi, ci, dist: Math.abs(groupCenters[gi] - colCenters[ci]) })\n }\n }\n assignments.sort((a, b) => a.dist - b.dist)\n\n const assignedGroups = new Set<number>()\n for (const { gi, ci } of assignments) {\n if (assignedGroups.has(gi) || usedCols.has(ci)) continue\n result.push({ col: ci, items: groups[gi] })\n assignedGroups.add(gi)\n usedCols.add(ci)\n }\n // 남은 그룹 (numGroups > numCols인 경우 — 가장 가까운 열에 추가)\n for (let gi = 0; gi < groups.length; gi++) {\n if (assignedGroups.has(gi)) continue\n let bestCol = 0, bestDist = Infinity\n for (let ci = 0; ci < numCols; ci++) {\n const d = Math.abs(groupCenters[gi] - colCenters[ci])\n if (d < bestDist) { bestDist = d; bestCol = ci }\n }\n result.push({ col: bestCol, items: groups[gi] })\n }\n\n return result\n}\n\n/** 클러스터 테이블을 IRTable로 구성 */\nfunction buildClusterTable(\n rows: RowGroup[],\n columns: ColCluster[],\n pageNum: number,\n): ClusterTableResult | null {\n const numCols = columns.length\n const numRows = rows.length\n\n if (numRows < MIN_ROWS || numCols < MIN_COLS) return null\n\n const cells: IRCell[][] = Array.from(\n { length: numRows },\n () => Array.from({ length: numCols }, () => ({ text: \"\", colSpan: 1, rowSpan: 1 })),\n )\n\n const usedItems = new Set<ClusterItem>()\n\n for (let r = 0; r < numRows; r++) {\n const row = rows[r]\n // 단일 아이템 행 → 전체 행 병합 (colSpan)\n if (row.items.length === 1 && numCols > 1) {\n cells[r][0] = { text: row.items[0].text, colSpan: numCols, rowSpan: 1 }\n usedItems.add(row.items[0])\n continue\n }\n\n // 행별 갭 분석 기반 열 배정\n const assignments = assignRowItems(row.items, columns, numCols)\n for (const { col, items } of assignments) {\n const text = items.map(i => i.text).join(\" \")\n const existing = cells[r][col].text\n cells[r][col].text = existing ? existing + \" \" + text : text\n for (const item of items) usedItems.add(item)\n }\n }\n\n // 검증: 빈 행이 너무 많으면 테이블 아님\n let emptyRows = 0\n for (const row of cells) {\n if (row.every(c => c.text === \"\")) emptyRows++\n }\n if (emptyRows > numRows * 0.5) return null\n\n // 검증: 모든 열에 최소 1개 값이 있어야 함\n for (let c = 0; c < numCols; c++) {\n const hasValue = cells.some(row => row[c].text !== \"\")\n if (!hasValue) return null\n }\n\n // 후처리 1: 단일 열 연속 행 → 이전 행에 역방향 병합 (multi-line 셀)\n // 조건: 1개 열만 내용 + col0 비어있음 + 리스트 마커(○-·)로 시작하지 않음\n for (let r = numRows - 1; r >= 1; r--) {\n const nonEmptyCols = cells[r].filter(c => c.text.trim()).length\n if (nonEmptyCols !== 1) continue\n if (cells[r][0].text.trim() !== \"\") continue\n const contentText = cells[r].find(c => c.text.trim())?.text.trim() || \"\"\n // 리스트 마커로 시작하면 새 항목 → 병합하지 않음\n if (/^[○●▶\\-·]/.test(contentText)) continue\n for (let pr = r - 1; pr >= 0; pr--) {\n if (cells[pr].some(c => c.text.trim())) {\n for (let c = 0; c < numCols; c++) {\n const prev = cells[pr][c].text.trim()\n const curr = cells[r][c].text.trim()\n if (curr) cells[pr][c].text = prev ? prev + \" \" + curr : curr\n }\n for (let c = 0; c < numCols; c++) cells[r][c].text = \"\"\n break\n }\n }\n }\n\n // 후처리 2: 번호만 있는 행(col0+마지막열만 값) → 다음 내용 행과 순방향 병합\n // 예: 목차 \"|3|||3|\" + \"|청사 내 에너지...|행정지원과||\" → \"|3|청사...|행정지원과|3|\"\n for (let r = 0; r < cells.length - 1; r++) {\n const row = cells[r]\n const hasCol0 = row[0].text.trim() !== \"\"\n const hasColLast = numCols > 1 && row[numCols - 1].text.trim() !== \"\"\n const midEmpty = row.slice(1, numCols - 1).every(c => c.text.trim() === \"\")\n if (hasCol0 && hasColLast && midEmpty) {\n // 다음 행에 col0이 비어있으면 병합\n const next = cells[r + 1]\n if (next[0].text.trim() === \"\" && next.some(c => c.text.trim())) {\n for (let c = 1; c < numCols; c++) {\n const curr = next[c].text.trim()\n if (curr) row[c].text = row[c].text.trim() ? row[c].text.trim() + \" \" + curr : curr\n }\n for (let c = 0; c < numCols; c++) next[c].text = \"\"\n }\n }\n }\n\n // 빈 행 제거\n const filteredCells = cells.filter(row => row.some(c => c.text.trim()))\n const finalRowCount = filteredCells.length\n\n // 검증\n if (finalRowCount < MIN_ROWS) return null\n\n const irTable: IRTable = {\n rows: finalRowCount,\n cols: numCols,\n cells: filteredCells,\n hasHeader: finalRowCount > 1,\n }\n\n // BBox 계산\n const allItems = rows.flatMap(r => r.items)\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity\n for (const i of allItems) {\n if (i.x < minX) minX = i.x\n if (i.y < minY) minY = i.y\n if (i.x + i.w > maxX) maxX = i.x + i.w\n const h = i.h > 0 ? i.h : i.fontSize\n if (i.y + h > maxY) maxY = i.y + h\n }\n\n return {\n table: irTable,\n bbox: { page: pageNum, x: minX, y: minY, width: maxX - minX, height: maxY - minY },\n usedItems,\n }\n}\n","/**\n * PDF 페이지별 텍스트 품질 신호 수집.\n *\n * pdfjs가 텍스트층을 추출했더라도, 폰트의 ToUnicode/CMap이 불완전하면\n * 한글이 PUA(Private Use Area) 글리프 코드로 그대로 떨어진다. 또 일부 PDF는\n * NUL/제어문자가 본문에 섞여 있다. 본 모듈은 그런 신호를 페이지 단위로 수집해\n * \"OCR 검토 필요\" 판정에 사용한다.\n */\n\nexport interface PageQuality {\n /** 1-based 페이지 번호 */\n page: number\n /** 공백 제외 문자 수 */\n textChars: number\n /** 한글 음절(0xAC00-0xD7A3) 비율 (0~1) */\n hangulRatio: number\n /** C0/C1 제어문자 비율 (tab/newline/CR 제외) */\n controlCharRatio: number\n /** U+FFFD replacement character 비율 */\n replacementCharRatio: number\n /** PUA 비율 — 글꼴 매핑 실패의 핵심 신호 */\n puaRatio: number\n /** OCR 검토 권장 여부 */\n needsOcr: boolean\n /** needsOcr=true일 때 사유 (단일 신호로 충분, 가장 강한 신호 선택) */\n ocrReason?: \"low_text\" | \"high_pua\" | \"high_control\" | \"high_replacement\"\n}\n\n/** 페이지 텍스트에서 품질 메트릭을 계산한다. */\nexport function computePageQuality(page: number, text: string): PageQuality {\n let total = 0\n let hangul = 0\n let control = 0\n let replacement = 0\n let pua = 0\n\n for (let i = 0; i < text.length; i++) {\n const code = text.charCodeAt(i)\n // 공백류는 분모에서 제외\n if (code === 0x20 || code === 0x09 || code === 0x0a || code === 0x0d) continue\n total++\n\n // C0 제어문자 (0x00-0x1F 중 tab/lf/cr 제외) + DEL(0x7F) + C1(0x80-0x9F)\n if ((code < 0x20) || code === 0x7f || (code >= 0x80 && code <= 0x9f)) {\n control++\n continue\n }\n if (code === 0xfffd) {\n replacement++\n continue\n }\n // 한글 음절\n if (code >= 0xac00 && code <= 0xd7a3) {\n hangul++\n continue\n }\n // PUA: BMP(E000-F8FF) + SPUA-A(F0000-FFFFD) + SPUA-B(100000-10FFFD)\n // 서로게이트 페어는 high만 검사해도 충분 (Plane 15/16 high surrogate 범위 DB80-DBFF)\n if ((code >= 0xe000 && code <= 0xf8ff) || (code >= 0xdb80 && code <= 0xdbff)) {\n pua++\n continue\n }\n }\n\n const denom = total || 1\n const puaRatio = pua / denom\n const controlCharRatio = control / denom\n const replacementCharRatio = replacement / denom\n\n let needsOcr = false\n let ocrReason: PageQuality[\"ocrReason\"] | undefined\n // 우선순위: low_text > high_pua > high_control > high_replacement\n if (total < LOW_TEXT_THRESHOLD) { needsOcr = true; ocrReason = \"low_text\" }\n else if (puaRatio >= HIGH_PUA_THRESHOLD) { needsOcr = true; ocrReason = \"high_pua\" }\n else if (controlCharRatio >= HIGH_CONTROL_THRESHOLD) { needsOcr = true; ocrReason = \"high_control\" }\n else if (replacementCharRatio >= HIGH_REPLACEMENT_THRESHOLD) { needsOcr = true; ocrReason = \"high_replacement\" }\n\n return {\n page,\n textChars: total,\n hangulRatio: hangul / denom,\n controlCharRatio,\n replacementCharRatio,\n puaRatio,\n needsOcr,\n ocrReason,\n }\n}\n\n/** 페이지별 품질에서 문서 단위 요약을 계산한다. */\nexport interface DocumentQualitySummary {\n totalPages: number\n totalTextChars: number\n avgHangulRatio: number\n avgControlCharRatio: number\n avgReplacementCharRatio: number\n avgPuaRatio: number\n /** textChars 매우 적은 페이지 수 (이미지/빈 페이지 후보) */\n lowTextPageCount: number\n /** PUA 비율 임계 초과 페이지 수 (글꼴 매핑 깨짐 후보) */\n highPuaPageCount: number\n /** 문서 전체에 OCR 검토가 권장되는지 — needsOcr 페이지 비율 또는 평균 신호 기반 */\n needsOcr: boolean\n /** needsOcr=true인 페이지 번호 목록 */\n ocrCandidatePages: number[]\n}\n\nconst LOW_TEXT_THRESHOLD = 20\nconst HIGH_PUA_THRESHOLD = 0.2\nconst HIGH_CONTROL_THRESHOLD = 0.05\nconst HIGH_REPLACEMENT_THRESHOLD = 0.05\n/** 페이지 중 needsOcr 비율이 이 값 이상이면 문서 전체에 OCR 권장 */\nconst DOC_NEEDS_OCR_PAGE_RATIO = 0.3\n\n/**\n * 비표시 제어문자를 제거한다. C0(NUL 등, tab/lf/cr 제외) + DEL + C1.\n * PUA는 사용자가 글꼴 매핑 실패를 시각적으로 확인할 수 있도록 보존.\n */\nexport function stripControlChars(text: string): string {\n // eslint-disable-next-line no-control-regex\n return text.replace(/[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F\\x80-\\x9F]/g, \"\")\n}\n\nexport function summarizeDocumentQuality(pages: PageQuality[]): DocumentQualitySummary {\n if (pages.length === 0) {\n return {\n totalPages: 0,\n totalTextChars: 0,\n avgHangulRatio: 0,\n avgControlCharRatio: 0,\n avgReplacementCharRatio: 0,\n avgPuaRatio: 0,\n lowTextPageCount: 0,\n highPuaPageCount: 0,\n needsOcr: false,\n ocrCandidatePages: [],\n }\n }\n\n let textChars = 0\n let hangul = 0\n let control = 0\n let replacement = 0\n let pua = 0\n let lowText = 0\n let highPua = 0\n const ocrCandidatePages: number[] = []\n\n for (const p of pages) {\n textChars += p.textChars\n hangul += p.hangulRatio\n control += p.controlCharRatio\n replacement += p.replacementCharRatio\n pua += p.puaRatio\n if (p.textChars < LOW_TEXT_THRESHOLD) lowText++\n if (p.puaRatio >= HIGH_PUA_THRESHOLD) highPua++\n if (p.needsOcr) ocrCandidatePages.push(p.page)\n }\n\n const n = pages.length\n return {\n totalPages: n,\n totalTextChars: textChars,\n avgHangulRatio: hangul / n,\n avgControlCharRatio: control / n,\n avgReplacementCharRatio: replacement / n,\n avgPuaRatio: pua / n,\n lowTextPageCount: lowText,\n highPuaPageCount: highPua,\n needsOcr: ocrCandidatePages.length / n >= DOC_NEEDS_OCR_PAGE_RATIO,\n ocrCandidatePages,\n }\n}\n","/**\n * 서버리스/Node.js 환경에서 pdfjs-dist가 요구하는 브라우저 API polyfill.\n * pdfjs-dist import보다 먼저 실행되어야 함 (ES 모듈 호이스팅 대응으로 별도 파일 분리).\n *\n * 1. DOMMatrix / Path2D polyfill — pdfjs-dist가 참조하지만 Node.js에 없음\n * 2. pdfjsWorker 사전 주입 — fake worker의 동적 import를 우회\n */\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst g = globalThis as any\n\nif (typeof g.DOMMatrix === \"undefined\") {\n g.DOMMatrix = class DOMMatrix {\n m: number[] = [1, 0, 0, 1, 0, 0]\n constructor(init?: number[]) { if (init) this.m = init }\n }\n}\n\nif (typeof g.Path2D === \"undefined\") {\n g.Path2D = class Path2D {}\n}\n\n// worker 모듈 static import → fake worker가 동적 import를 건너뜀\n// @ts-expect-error pdfjs-dist worker has no type declarations\nimport * as pdfjsWorker from \"pdfjs-dist/legacy/build/pdf.worker.mjs\"\ng.pdfjsWorker = pdfjsWorker\n","/**\n * PDF 텍스트 추출 (pdfjs-dist static import 기반)\n *\n * polyfill을 먼저 import해야 DOMMatrix/Path2D/pdfjsWorker가 주입됨.\n * ES 모듈 호이스팅 때문에 별도 파일로 분리되어 있음.\n */\n\nimport type { ParseResult, InternalParseResult, IRBlock, IRTable, DocumentMetadata, ParseOptions, BoundingBox, ParseWarning, OutlineItem } from \"../types.js\"\nimport { HEADING_RATIO_H1, HEADING_RATIO_H2, HEADING_RATIO_H3 } from \"../types.js\"\nimport { KordocError, safeMin, safeMax } from \"../utils.js\"\nimport { parsePageRange } from \"../page-range.js\"\nimport { blocksToMarkdown } from \"../table/builder.js\"\nimport { extractLines, preprocessLines, filterPageBorderLines, buildTableGrids, extractCells, mapTextToCells, cellTextToString, detectEvenSpacedItems, spaceGapThreshold, extractImageRegions, normalizeUndersegmentedTable, type TextItem, type TableGrid, type ExtractedCell, type LineSegment } from \"./line-detector.js\"\nimport { detectClusterTables, type ClusterItem } from \"./cluster-detector.js\"\nimport { computePageQuality, summarizeDocumentQuality, stripControlChars, type PageQuality } from \"./quality.js\"\n// polyfill 먼저 (ES 모듈 호이스팅되므로 별도 파일 필수)\nimport \"./polyfill.js\"\nimport { getDocument, OPS, GlobalWorkerOptions } from \"pdfjs-dist/legacy/build/pdf.mjs\"\n\n// worker 비활성화 (polyfill에서 pdfjsWorker를 이미 주입했으므로)\nGlobalWorkerOptions.workerSrc = \"\"\n\n// ─── 안전 한계값 (구조적 파싱과 무관) ────────────────\nconst MAX_PAGES = 5000\nconst MAX_TOTAL_TEXT = 100 * 1024 * 1024 // 100MB\n/** PDF 로딩 타임아웃 (30초) — 악성/대용량 PDF 무한 대기 방지 */\nconst PDF_LOAD_TIMEOUT_MS = 30_000\n\n/** getDocument + 타임아웃 래퍼 */\nasync function loadPdfWithTimeout(buffer: ArrayBuffer) {\n const loadingTask = getDocument({\n data: new Uint8Array(buffer),\n useSystemFonts: true,\n disableFontFace: true,\n isEvalSupported: false,\n })\n let timer: ReturnType<typeof setTimeout> | undefined\n try {\n return await Promise.race([\n loadingTask.promise,\n new Promise<never>((_, reject) => {\n timer = setTimeout(() => { loadingTask.destroy(); reject(new KordocError(\"PDF 로딩 타임아웃 (30초 초과)\")) }, PDF_LOAD_TIMEOUT_MS)\n }),\n ])\n } finally {\n if (timer !== undefined) clearTimeout(timer)\n }\n}\n\ninterface PdfTextItem {\n str: string\n transform: number[]\n width: number\n height: number\n fontName?: string\n}\n\ninterface NormItem {\n text: string\n x: number\n y: number\n w: number\n h: number\n /** 폰트 높이(≈폰트 크기) — 헤딩 감지용 */\n fontSize: number\n fontName: string\n /** hidden text 여부 (투명/0pt) */\n isHidden: boolean\n /** pdfjs 공백 아이템이 이 아이템 직전에 있었음 — 단어 경계 힌트 */\n hasSpaceBefore?: boolean\n /** 취소선이 그어진 텍스트 (신구조문대비표 삭제 표시 등) */\n strike?: boolean\n}\n\nexport async function parsePdfDocument(buffer: ArrayBuffer, options?: ParseOptions): Promise<InternalParseResult> {\n // pdfjs-dist 는 전달받은 buffer 의 underlying storage 를 detach 할 수 있다.\n // 수식 OCR 은 같은 버퍼를 재사용해야 하므로 옵션 on 일 때만 clone 을 보관.\n const formulaBuffer: ArrayBuffer | null = options?.formulaOcr ? buffer.slice(0) : null\n const doc = await loadPdfWithTimeout(buffer)\n\n try {\n const pageCount = doc.numPages\n if (pageCount === 0) throw new KordocError(\"PDF에 페이지가 없습니다.\")\n\n // 메타데이터 추출 (best-effort)\n const metadata: DocumentMetadata = { pageCount }\n await extractPdfMetadata(doc, metadata)\n\n const blocks: IRBlock[] = []\n const warnings: ParseWarning[] = []\n const pageQuality: PageQuality[] = []\n let totalChars = 0\n let totalTextBytes = 0\n const effectivePageCount = Math.min(pageCount, MAX_PAGES)\n\n // 페이지 범위 필터링\n const pageFilter = options?.pages ? parsePageRange(options.pages, effectivePageCount) : null\n const totalTarget = pageFilter ? pageFilter.size : effectivePageCount\n\n // 전체 문서의 폰트 크기 빈도 수집 (헤딩 감지용) — 빈도 Map으로 메모리 절약\n const fontSizeFreq = new Map<number, number>()\n const pageHeights = new Map<number, number>()\n // 큰 이미지가 있는 페이지 (needsOcr 경고 노이즈 필터 + SKIPPED_IMAGE)\n const pagesWithLargeImage = new Set<number>()\n // 텍스트 없는 큰 이미지 영역: page → count\n const skippedImagePages = new Map<number, number>()\n\n let parsedPages = 0\n for (let i = 1; i <= effectivePageCount; i++) {\n if (pageFilter && !pageFilter.has(i)) continue\n try {\n const page = await doc.getPage(i)\n const tc = await page.getTextContent()\n const viewport = page.getViewport({ scale: 1 })\n pageHeights.set(i, viewport.height)\n const rawItems = tc.items as PdfTextItem[]\n const items = normalizeItems(rawItems)\n\n // hidden text 필터링 + 경고 수집\n const { visible, hiddenCount } = filterHiddenText(items, viewport.width, viewport.height)\n if (hiddenCount > 0) {\n warnings.push({ page: i, message: `${hiddenCount}개 숨겨진 텍스트 요소 필터링됨`, code: \"HIDDEN_TEXT_FILTERED\" })\n }\n\n // 폰트 크기 빈도 수집\n for (const item of visible) {\n if (item.fontSize > 0) fontSizeFreq.set(item.fontSize, (fontSizeFreq.get(item.fontSize) || 0) + 1)\n }\n\n // 선 기반 테이블 감지를 위한 operatorList\n const opList = await page.getOperatorList()\n\n // 이미지 영역 감지 — 텍스트 없는 큰 이미지는 무음 정보손실이므로 가시화 (ODL 아이디어)\n const pageArea = viewport.width * viewport.height\n if (pageArea > 0) {\n const imageRegions = extractImageRegions(opList.fnArray, opList.argsArray)\n let uncovered = 0\n for (const r of imageRegions) {\n const area = (r.x2 - r.x1) * (r.y2 - r.y1)\n if (area < pageArea * 0.05) continue // 작은 장식 이미지 무시\n pagesWithLargeImage.add(i)\n const hasText = visible.some(it => {\n const cx = it.x + it.w / 2\n const cy = it.y + (it.h || it.fontSize) / 2\n return cx >= r.x1 && cx <= r.x2 && cy >= r.y1 && cy <= r.y2\n })\n if (!hasText) uncovered++\n }\n if (uncovered > 0) skippedImagePages.set(i, uncovered)\n }\n\n const pageBlocks = extractPageBlocksWithLines(visible, i, opList, viewport.width, viewport.height)\n for (const b of pageBlocks) blocks.push(b)\n\n // 이미지 기반 PDF 감지 + 크기 제한용 문자 수 집계 + 페이지 품질 신호\n let pageText = \"\"\n for (const b of pageBlocks) {\n const t = b.text || \"\"\n totalChars += t.replace(/\\s/g, \"\").length\n totalTextBytes += t.length * 2\n pageText += pageText ? \"\\n\" + t : t\n }\n pageQuality.push(computePageQuality(i, pageText))\n if (totalTextBytes > MAX_TOTAL_TEXT) throw new KordocError(\"텍스트 추출 크기 초과\")\n parsedPages++\n options?.onProgress?.(parsedPages, totalTarget)\n } catch (pageErr) {\n // 크기 초과는 전체 중단\n if (pageErr instanceof KordocError) throw pageErr\n warnings.push({ page: i, message: `페이지 ${i} 파싱 실패: ${pageErr instanceof Error ? pageErr.message : \"알 수 없는 오류\"}`, code: \"PARTIAL_PARSE\" })\n }\n }\n\n const parsedPageCount = parsedPages || (pageFilter ? pageFilter.size : effectivePageCount)\n let isImageBased = false\n if (totalChars / Math.max(parsedPageCount, 1) < 10) {\n if (options?.ocr) {\n try {\n const { ocrPages } = await import(\"../ocr/provider.js\")\n const ocrBlocks = await ocrPages(doc, options.ocr, pageFilter, effectivePageCount)\n if (ocrBlocks.length > 0) {\n const ocrMarkdown = ocrBlocks.map(b => b.text || \"\").filter(Boolean).join(\"\\n\\n\")\n return { markdown: ocrMarkdown, blocks: ocrBlocks, metadata, warnings, isImageBased: true, pageQuality, qualitySummary: summarizeDocumentQuality(pageQuality) }\n }\n } catch {\n // OCR 실패 시 일반 경로로 폴백 (아래에서 NEEDS_OCR 경고)\n }\n }\n // OCR 미설정/실패 — 빈 출력을 무경고로 내보내지 않고 경고 + 플래그로 가시화 (v3.0)\n isImageBased = true\n warnings.push({\n message: `이미지 기반 PDF (${pageCount}페이지, 텍스트 ${totalChars}자) — 텍스트 레이어가 없어 OCR이 필요합니다`,\n code: \"NEEDS_OCR\",\n })\n }\n\n // 페이지 단위 needsOcr 경고 — 텍스트+스캔 혼합 문서에서 스캔 페이지 무음 손실 방지.\n // low_text는 빈 페이지(표지/간지)일 수 있으므로 큰 이미지가 있는 페이지만 경고.\n if (!isImageBased) {\n const OCR_REASON_MESSAGES: Record<string, string> = {\n low_text: \"텍스트가 거의 없는 페이지 (스캔/이미지 추정)\",\n high_pua: \"글꼴 매핑 실패 (PUA 비율 높음) — 추출 텍스트 신뢰 불가\",\n high_control: \"제어문자 비율 높음 — 추출 텍스트 신뢰 불가\",\n high_replacement: \"대체문자(U+FFFD) 비율 높음 — 추출 텍스트 신뢰 불가\",\n }\n for (const pq of pageQuality) {\n if (!pq.needsOcr || !pq.ocrReason) continue\n if (pq.ocrReason === \"low_text\" && !pagesWithLargeImage.has(pq.page)) continue\n warnings.push({ page: pq.page, message: `${OCR_REASON_MESSAGES[pq.ocrReason]} — OCR 검토 필요`, code: \"NEEDS_OCR\" })\n }\n }\n\n // 텍스트 없는 큰 이미지 영역 경고 — 그림/차트/도장 무음 누락 가시화\n // (문서 전체가 이미지 기반이면 위의 NEEDS_OCR 단일 경고로 충분)\n if (!isImageBased) {\n for (const [page, count] of [...skippedImagePages.entries()].sort((a, b) => a[0] - b[0])) {\n warnings.push({ page, message: `${count}개 이미지 영역에 추출 가능한 텍스트 없음 (그림/차트/도장 내용 누락 가능)`, code: \"SKIPPED_IMAGE\" })\n }\n }\n\n // 머리글/바닥글 필터링 (기본 ON — 명시적 false일 때만 비활성화)\n if (options?.removeHeaderFooter !== false && parsedPageCount >= 3) {\n const removed = removeHeaderFooterBlocks(blocks, pageHeights, warnings)\n // 필터링된 블록 제거 (뒤에서부터 삭제)\n for (let ri = removed.length - 1; ri >= 0; ri--) {\n blocks.splice(removed[ri], 1)\n }\n }\n\n // 페이지 걸친 표 병합 — 머리글/바닥글 제거 후 인접해진 표를 하나로\n // (ODL TableBorderProcessor.checkNeighborTables 포팅)\n mergeCrossPageTables(blocks)\n\n // 수식 OCR (선택) — 기본 텍스트 추출과 별개로 페이지 이미지 렌더 후 수식만 검출/인식.\n // 실패 시 경고만 기록하고 일반 텍스트 추출 결과는 그대로 반환한다.\n if (options?.formulaOcr && formulaBuffer) {\n try {\n await applyFormulaOcr(formulaBuffer, blocks, pageFilter, effectivePageCount, warnings, options.onProgress)\n } catch (e) {\n warnings.push({\n message: `수식 OCR 실패: ${e instanceof Error ? e.message : String(e)}`,\n code: \"PARTIAL_PARSE\",\n })\n }\n }\n\n // 헤딩 감지: 폰트 크기 기반\n const medianFontSize = computeMedianFontSizeFromFreq(fontSizeFreq)\n if (medianFontSize > 0) {\n detectHeadings(blocks, medianFontSize)\n }\n\n // □/■ 마커 기반 서브헤딩 감지 (ODL 패턴)\n detectMarkerHeadings(blocks)\n\n // 표 캡션 감지 — 표 직전/직후 '표 N./그림 N' 패턴 텍스트를 IRTable.caption으로\n detectTableCaptions(blocks)\n\n // 한국어 리스트 감지 — 공문서 계층 라벨(1.→가.→1)→가)→①) 시퀀스 검증\n detectKoreanListBlocks(blocks)\n\n // outline 구축\n const outline: OutlineItem[] = blocks\n .filter(b => b.type === \"heading\" && b.level && b.text)\n .map(b => ({ level: b.level!, text: b.text!, pageNumber: b.pageNumber }))\n\n // 메트릭 수집 끝났으니 블록 텍스트의 C0/C1 제어문자(NUL 등) 정리\n sanitizeBlockControlChars(blocks)\n\n // blocksToMarkdown로 통일 — 헤딩 마크다운 반영 (HWP5/HWPX와 일관성)\n let markdown = cleanPdfText(blocksToMarkdown(blocks))\n\n return {\n markdown,\n blocks,\n metadata,\n outline: outline.length > 0 ? outline : undefined,\n warnings: warnings.length > 0 ? warnings : undefined,\n isImageBased: isImageBased || undefined,\n pageQuality,\n qualitySummary: summarizeDocumentQuality(pageQuality),\n }\n } finally {\n await doc.destroy().catch(() => {})\n }\n}\n\n// ─── PDF 메타데이터 추출 ────────────────────────────\n\nasync function extractPdfMetadata(doc: { getMetadata(): Promise<unknown> }, metadata: DocumentMetadata): Promise<void> {\n try {\n const result = await doc.getMetadata() as { info?: Record<string, unknown> } | null\n if (!result?.info) return\n const info = result.info\n\n if (typeof info.Title === \"string\" && info.Title.trim()) metadata.title = info.Title.trim()\n if (typeof info.Author === \"string\" && info.Author.trim()) metadata.author = info.Author.trim()\n if (typeof info.Creator === \"string\" && info.Creator.trim()) metadata.creator = info.Creator.trim()\n if (typeof info.Subject === \"string\" && info.Subject.trim()) metadata.description = info.Subject.trim()\n if (typeof info.Keywords === \"string\" && info.Keywords.trim()) {\n metadata.keywords = info.Keywords.split(/[,;]/).map((k: string) => k.trim()).filter(Boolean)\n }\n if (typeof info.CreationDate === \"string\") metadata.createdAt = parsePdfDate(info.CreationDate)\n if (typeof info.ModDate === \"string\") metadata.modifiedAt = parsePdfDate(info.ModDate)\n } catch {\n // best-effort\n }\n}\n\n/** PDF 날짜 형식 (D:YYYYMMDDHHmmSS) → ISO 8601 변환 */\nfunction parsePdfDate(dateStr: string): string | undefined {\n const m = dateStr.match(/D:(\\d{4})(\\d{2})?(\\d{2})?(\\d{2})?(\\d{2})?(\\d{2})?/)\n if (!m) return undefined\n const [, year, month = \"01\", day = \"01\", hour = \"00\", min = \"00\", sec = \"00\"] = m\n return `${year}-${month}-${day}T${hour}:${min}:${sec}`\n}\n\n/** 메타데이터만 추출 (전체 파싱 없이) — MCP parse_metadata용 */\nexport async function extractPdfMetadataOnly(buffer: ArrayBuffer): Promise<DocumentMetadata> {\n const doc = await loadPdfWithTimeout(buffer)\n\n try {\n const metadata: DocumentMetadata = { pageCount: doc.numPages }\n await extractPdfMetadata(doc, metadata)\n return metadata\n } finally {\n await doc.destroy().catch(() => {})\n }\n}\n\n// ═══════════════════════════════════════════════════════\n// Hidden text 필터링 (prompt injection 방어)\n// ═══════════════════════════════════════════════════════\n\nfunction filterHiddenText(items: NormItem[], pageWidth: number, pageHeight: number): { visible: NormItem[]; hiddenCount: number } {\n let hiddenCount = 0\n const visible: NormItem[] = []\n\n for (const item of items) {\n // 0pt 폰트 / 너비 0 → 숨겨진 텍스트\n if (item.isHidden) { hiddenCount++; continue }\n // 페이지 범위 밖 (여백 10% 허용)\n const margin = Math.max(pageWidth, pageHeight) * 0.1\n if (item.x < -margin || item.x > pageWidth + margin || item.y < -margin || item.y > pageHeight + margin) {\n hiddenCount++; continue\n }\n visible.push(item)\n }\n\n return { visible, hiddenCount }\n}\n\n// ═══════════════════════════════════════════════════════\n// 헤딩 감지 (폰트 크기 기반)\n// ═══════════════════════════════════════════════════════\n\nfunction computeMedianFontSizeFromFreq(freq: Map<number, number>): number {\n if (freq.size === 0) return 0\n let total = 0\n for (const count of freq.values()) total += count\n const sorted = [...freq.entries()].sort((a, b) => a[0] - b[0])\n const mid = Math.floor(total / 2)\n let cumulative = 0\n for (const [size, count] of sorted) {\n cumulative += count\n if (cumulative > mid) return size\n }\n return sorted[sorted.length - 1][0]\n}\n\n/**\n * 블록의 폰트 크기를 median과 비교하여 헤딩으로 승격.\n * - 150%+ → heading level 1\n * - 130%+ → heading level 2\n * - 115%+ → heading level 3\n * 조건: 짧은 텍스트 (200자 미만), 숫자만으로 구성되지 않음\n */\nfunction detectHeadings(blocks: IRBlock[], medianFontSize: number): void {\n for (const block of blocks) {\n if (block.type !== \"paragraph\" || !block.text || !block.style?.fontSize) continue\n const text = block.text.trim()\n if (text.length === 0 || text.length > 200) continue\n // 숫자만이면 헤딩 아님\n if (/^\\d+$/.test(text)) continue\n\n const ratio = block.style.fontSize / medianFontSize\n let level = 0\n if (ratio >= HEADING_RATIO_H1) level = 1\n else if (ratio >= HEADING_RATIO_H2) level = 2\n else if (ratio >= HEADING_RATIO_H3) level = 3\n\n if (level > 0) {\n block.type = \"heading\"\n block.level = level\n // PDF 균등배분 스페이스 제거 (\"기 본 현 황\" → \"기본현황\")\n // 한글 글자 사이에 단독 공백이 반복되면 균등배분으로 판단\n block.text = collapseEvenSpacing(text)\n }\n }\n}\n\n/**\n * 문자열 기반 균등배분 제거.\n * normalizeItems에서 분해 + 좌표 기반 감지가 주 경로이고, 여기는 안전망.\n * pdfjs가 이미 합친 \"홍 보 담 당 관\" 같은 TextItem 문자열에 적용.\n */\nfunction collapseEvenSpacing(text: string): string {\n // 1. 전체가 균등배분: 토큰의 70%가 1글자\n const tokens = text.split(\" \")\n const singleCharCount = tokens.filter(t => t.length === 1).length\n if (tokens.length >= 3 && singleCharCount / tokens.length >= 0.7) {\n return tokens.join(\"\")\n }\n\n // 2. 부분 균등배분: 한글 1자가 3개+ 연속 (2자 단어는 건드리지 않음)\n // \"홍 보 담 당 관\" → \"홍보담당관\", \"지 역 경 제 과\" → \"지역경제과\"\n // \"중동 사태 대응\" (2자 단어)는 매칭 안 됨 → 공백 유지\n return text.replace(\n /(?<![가-힣])[가-힣](?: [가-힣\\d]){2,}(?![가-힣])/g,\n match => match.replace(/ /g, \"\"),\n )\n}\n\n/**\n * 의사 테이블 감지: 실제 데이터 테이블이 아닌 텍스트가 우연히 테이블로 감지된 경우.\n */\nfunction shouldDemoteTable(table: IRTable): boolean {\n const allCells = table.cells.flatMap(row => row.map(c => c.text.trim())).filter(Boolean)\n const allText = allCells.join(\" \")\n\n // 텍스트 박스 패턴: 3행 이하 + 3열 이하 + <...> 또는 ㅇ 마커 포함\n // 공문서 \"중점 추진사항\" 등 요약 박스\n if (table.rows <= 3 && table.cols <= 3) {\n // 빈 셀이 과반 → 텍스트 박스 (테두리 안에 텍스트만 있는 형태)\n const totalCells = table.rows * table.cols\n const emptyCells = totalCells - allCells.length\n if (emptyCells >= totalCells * 0.3) return true\n\n // 마커 패턴 (ㅇ, □, ○, <> 등) → 텍스트성\n if (/[□■◆○●▶ㅇ]/.test(allText)) return true\n if (/<[^>]+>/.test(allText)) return true\n }\n\n if (allText.length > 200) return false\n // □, ○, ■ 마커 포함 + 3행 이하 → 텍스트성\n if (/[□■◆○●▶]/.test(allText) && table.rows <= 3) return true\n // 빈 셀이 과반 → 의사 테이블\n const totalCells = table.rows * table.cols\n const emptyCells = totalCells - allCells.length\n if (table.rows <= 2 && emptyCells > totalCells * 0.5) return true\n // 1행 + 숫자 데이터 없음 → 의사 테이블\n if (table.rows === 1 && !/\\d{2,}/.test(allText)) return true\n return false\n}\n\n/** demote된 테이블을 구조화된 텍스트로 변환 */\nfunction demoteTableToText(table: IRTable): string {\n const lines: string[] = []\n for (let r = 0; r < table.rows; r++) {\n const cells = table.cells[r].map(c => c.text.trim()).filter(Boolean)\n if (cells.length === 0) continue\n if (table.cols === 2 && cells.length === 2) {\n lines.push(`${cells[0]} : ${cells[1]}`)\n } else {\n // 각 셀 텍스트를 공백으로 합침 (br 태그는 줄바꿈으로 유지)\n lines.push(cells.join(\" \"))\n }\n }\n return lines.join(\"\\n\")\n}\n\n/** □/■ 마커 및 짧은 섹션명을 서브헤딩으로 변환 */\nfunction detectMarkerHeadings(blocks: IRBlock[]): void {\n for (let i = 0; i < blocks.length; i++) {\n const block = blocks[i]\n if (block.type !== \"paragraph\" || !block.text) continue\n const text = block.text.trim()\n // □/■ + 한글로 시작하는 짧은 텍스트 (50자 미만)\n if (text.length < 50 && /^[□■◆◇▶]\\s*[가-힣]/.test(text)) {\n block.type = \"heading\"\n block.level = 4\n continue\n }\n // 순수 한글 2-6자 + 앞뒤가 표/헤딩/빈블록 → 섹션 제목으로 추정\n // (예: \"사업설명\", \"사업효과\", \"추진경위\")\n if (/^[가-힣]{2,6}$/.test(text) && block.style?.fontSize) {\n const prev = blocks[i - 1]\n const next = blocks[i + 1]\n const prevIsStructural = !prev || prev.type === \"table\" || prev.type === \"heading\" || prev.type === \"separator\"\n const nextIsStructural = !next || next.type === \"table\" || next.type === \"heading\" || (next.type === \"paragraph\" && next.text && /^[□■◆○●]/.test(next.text.trim()))\n if (prevIsStructural || nextIsStructural) {\n block.type = \"heading\"\n block.level = 3\n }\n }\n }\n}\n\n// ═══════════════════════════════════════════════════════\n// XY-Cut++ 읽기 순서 알고리즘 (arXiv:2504.10258)\n//\n// OpenDataLoader PDF의 XYCutPlusPlusSorter를 TypeScript로 포팅.\n// Original work: Copyright 2025-2026 Hancom Inc. (Apache-2.0)\n// https://github.com/opendataloader-project/opendataloader-pdf\n//\n// 기존 XY-Cut 대비 개선 3종:\n// ① cross-layout 전폭 요소(제목 등) 마스크 후 Y 위치로 재삽입\n// ② 좁은 요소(쪽번호류) 아웃라이어 필터 후 수직 컷 재시도\n// ③ 양방향(수평/수직) 컷을 모두 계산해 더 큰 갭 선택 + 최소 갭 5pt\n// ═══════════════════════════════════════════════════════\n\n/** 재귀 깊이 제한 — 수천 아이템의 pathological 레이아웃에서 스택 오버플로 방지 */\nconst MAX_XYCUT_DEPTH = 50\n/** 분할 최소 갭 (pt) — 미세 갭(1px급) 분할 방지 (ODL MIN_GAP_THRESHOLD) */\nconst XYCUT_MIN_GAP = 5\n/** cross-layout 판정: 최대폭 대비 비율 (ODL beta) — ODL 기본값 2.0 (사실상 비활성) */\nconst CROSS_LAYOUT_BETA = 2.0\n/** cross-layout 판정: 수평 겹침 비율 최소값 */\nconst CROSS_OVERLAP_RATIO = 0.1\n/** cross-layout 판정: 최소 겹침 요소 수 */\nconst CROSS_MIN_OVERLAPS = 2\n/** cross-layout 마스크 상한 — 전체의 20% 초과 마스크 시 비활성 (단일 컬럼 문서 보호) */\nconst CROSS_MAX_MASK_RATIO = 0.2\n/** 좁은 요소 아웃라이어 필터: 영역 폭 대비 비율 (쪽번호·각주 마커) */\nconst NARROW_ELEMENT_WIDTH_RATIO = 0.1\n\ninterface CutInfo {\n position: number\n gap: number\n}\n\nfunction xyCutOrder(items: NormItem[], gapThreshold: number, depth = 0): NormItem[][] {\n if (items.length === 0) return []\n if (items.length <= 2 || depth >= MAX_XYCUT_DEPTH) return [items]\n\n // Phase 1 (최상위에서만): cross-layout 전폭 요소 마스크\n if (depth === 0 && items.length >= 3) {\n const cross = identifyCrossLayoutItems(items)\n if (cross.size > 0 && cross.size <= items.length * CROSS_MAX_MASK_RATIO) {\n const rest = items.filter(i => !cross.has(i))\n if (rest.length > 0) {\n const groups = xyCutOrder(rest, gapThreshold, 1)\n return mergeCrossLayoutGroups(groups, [...cross])\n }\n }\n }\n\n // Phase 3: 양방향 컷 계산 → 더 큰 갭 선택 (기존: Y 무조건 우선 → 2단 인터리브)\n const minGap = Math.max(XYCUT_MIN_GAP, gapThreshold)\n const hCut = findHorizontalCut(items)\n const vCut = findVerticalCutWithOutlierFilter(items, minGap)\n\n const hValid = hCut.gap >= minGap\n const vValid = vCut.gap >= minGap\n\n // 축 선택: 기본 Y 우선 (한국 공문서는 단일 컬럼 위주 — 코퍼스 검증 결과 Y 우선이 안정적).\n // 단, 수직 갭이 수평 갭보다 명백히 크면(1.5×) 컬럼 분리로 보고 X 우선\n // → 2단 레이아웃에서 문단 간 수평 갭이 단 사이 수직 갭보다 먼저 잡혀 행 단위로\n // 인터리브되는 문제 방지 (XY-Cut++ 양방향 컷의 보수적 적용)\n let useHorizontal: boolean\n if (hValid && vValid) useHorizontal = vCut.gap <= hCut.gap * 1.5\n else if (hValid) useHorizontal = true\n else if (vValid) useHorizontal = false\n else return [items] // 분할 불가 → 리프 노드\n\n if (useHorizontal) {\n const upper = items.filter(i => i.y > hCut.position)\n const lower = items.filter(i => i.y <= hCut.position)\n if (upper.length > 0 && lower.length > 0 && upper.length < items.length) {\n return [...xyCutOrder(upper, gapThreshold, depth + 1), ...xyCutOrder(lower, gapThreshold, depth + 1)]\n }\n } else {\n const left = items.filter(i => i.x + i.w / 2 < vCut.position)\n const right = items.filter(i => i.x + i.w / 2 >= vCut.position)\n if (left.length > 0 && right.length > 0 && left.length < items.length) {\n return [...xyCutOrder(left, gapThreshold, depth + 1), ...xyCutOrder(right, gapThreshold, depth + 1)]\n }\n }\n\n return [items]\n}\n\n/**\n * cross-layout 요소 식별: 폭 ≥ beta×최대폭 + 다른 요소 2개 이상과 수평 겹침.\n * 전폭 제목/헤더가 컬럼 분할을 가로막는 것을 방지.\n */\nfunction identifyCrossLayoutItems(items: NormItem[]): Set<NormItem> {\n const cross = new Set<NormItem>()\n if (items.length < 3) return cross\n\n let maxWidth = 0\n for (const i of items) { if (i.w > maxWidth) maxWidth = i.w }\n const threshold = CROSS_LAYOUT_BETA * maxWidth\n\n for (const item of items) {\n if (item.w < threshold) continue\n let overlaps = 0\n for (const other of items) {\n if (other === item) continue\n const left = Math.max(item.x, other.x)\n const right = Math.min(item.x + item.w, other.x + other.w)\n const overlapW = right - left\n if (overlapW <= 0) continue\n const smaller = Math.min(item.w, other.w)\n if (smaller > 0 && overlapW / smaller >= CROSS_OVERLAP_RATIO) {\n overlaps++\n if (overlaps >= CROSS_MIN_OVERLAPS) break\n }\n }\n if (overlaps >= CROSS_MIN_OVERLAPS) cross.add(item)\n }\n return cross\n}\n\n/** cross-layout 요소를 Y 위치 기준으로 그룹 시퀀스에 재삽입 (각자 단독 그룹) */\nfunction mergeCrossLayoutGroups(groups: NormItem[][], cross: NormItem[]): NormItem[][] {\n if (cross.length === 0) return groups\n const sortedCross = [...cross].sort((a, b) => (b.y + b.h) - (a.y + a.h) || a.x - b.x)\n const groupTop = (g: NormItem[]) => {\n let top = -Infinity\n for (const i of g) { const t = i.y + i.h; if (t > top) top = t }\n return top\n }\n\n const result: NormItem[][] = []\n let gi = 0, ci = 0\n while (gi < groups.length || ci < sortedCross.length) {\n if (ci >= sortedCross.length) { result.push(groups[gi++]); continue }\n if (gi >= groups.length) { result.push([sortedCross[ci++]]); continue }\n const crossTop = sortedCross[ci].y + sortedCross[ci].h\n if (crossTop >= groupTop(groups[gi])) result.push([sortedCross[ci++]])\n else result.push(groups[gi++])\n }\n return result\n}\n\n/**\n * 수평 컷(Y축 분할) — Y 프로젝션에서 가장 넓은 갭.\n * 갭/분할점 계산은 기존 findYSplit과 동일 (y-h를 하단으로 보는 bbox 모델 유지 —\n * 코퍼스 검증 결과 모델 변경 시 행 분할점이 이동해 회귀 발생).\n */\nfunction findHorizontalCut(items: NormItem[]): CutInfo {\n if (items.length < 2) return { position: 0, gap: 0 }\n const sorted = [...items].sort((a, b) => b.y - a.y)\n let largestGap = 0\n let position = 0\n\n for (let i = 1; i < sorted.length; i++) {\n const prevBottom = sorted[i - 1].y - sorted[i - 1].h\n const currTop = sorted[i].y\n const gap = prevBottom - currTop\n if (gap > largestGap) {\n largestGap = gap\n position = (prevBottom + currTop) / 2\n }\n }\n return { position, gap: largestGap }\n}\n\n/**\n * 수직 컷(X축 분할) — 갭이 안 나오면 좁은 요소(쪽번호류) 제외 후 재시도.\n * 쪽번호가 2단 컬럼 사이 갭을 가로막는 경우 복구 (ODL ②).\n */\nfunction findVerticalCutWithOutlierFilter(items: NormItem[], minGap: number): CutInfo {\n const edgeCut = findVerticalCut(items)\n if (edgeCut.gap >= minGap) return edgeCut\n\n if (items.length >= 3) {\n let minX = Infinity, maxX = -Infinity\n for (const i of items) {\n if (i.x < minX) minX = i.x\n const r = i.x + i.w\n if (r > maxX) maxX = r\n }\n const narrowThreshold = (maxX - minX) * NARROW_ELEMENT_WIDTH_RATIO\n const filtered = items.filter(i => i.w >= narrowThreshold)\n // 아웃라이어는 소수여야 함 (쪽번호 1~2개) — 단어 단위 아이템이 대량 필터되면\n // 본문에서 가짜 컬럼 갭이 만들어지므로 70% 이상 유지될 때만 재시도\n if (filtered.length >= 2 && filtered.length < items.length && filtered.length >= items.length * 0.7) {\n const filteredCut = findVerticalCut(filtered)\n if (filteredCut.gap > edgeCut.gap && filteredCut.gap >= minGap) {\n return filteredCut\n }\n }\n }\n return edgeCut\n}\n\n/** 수직 컷 — X 프로젝션에서 가장 넓은 갭 */\nfunction findVerticalCut(items: NormItem[]): CutInfo {\n if (items.length < 2) return { position: 0, gap: 0 }\n const sorted = [...items].sort((a, b) => a.x - b.x || (a.x + a.w) - (b.x + b.w))\n let largestGap = 0\n let position = 0\n let prevRight: number | null = null\n\n for (const it of sorted) {\n const left = it.x\n const right = it.x + it.w\n if (prevRight !== null && left > prevRight) {\n const gap = left - prevRight\n if (gap > largestGap) {\n largestGap = gap\n position = (prevRight + left) / 2\n }\n }\n prevRight = prevRight === null ? right : Math.max(prevRight, right)\n }\n return { position, gap: largestGap }\n}\n\n// ═══════════════════════════════════════════════════════\n// 페이지 콘텐츠 추출 → IRBlock[] (v2: 바운딩 박스 + 페이지 번호)\n// ═══════════════════════════════════════════════════════\n\n/**\n * 선 기반 테이블 감지를 우선 시도, 실패 시 기존 휴리스틱 fallback.\n */\nfunction extractPageBlocksWithLines(\n items: NormItem[],\n pageNum: number,\n opList: { fnArray: Uint32Array | number[]; argsArray: unknown[][] },\n pageWidth: number,\n pageHeight: number,\n): IRBlock[] {\n if (items.length === 0) return []\n\n // 1단계: PDF 그래픽 명령에서 선 추출\n let { horizontals, verticals } = extractLines(opList.fnArray, opList.argsArray)\n ;({ horizontals, verticals } = filterPageBorderLines(horizontals, verticals, pageWidth, pageHeight))\n\n // 1.5단계: 선 전처리 (ODL LinesPreprocessingConsumer 포팅)\n // 굵은 선 필터 + 근접 평행 선 병합\n ;({ horizontals, verticals } = preprocessLines(horizontals, verticals))\n\n // 1.7단계: 취소선 감지 — 텍스트 중심을 가로지르는 얇은 수평선 (ODL StrikethroughProcessor)\n markStrikethroughItems(items, horizontals)\n wrapStrikethroughRuns(items)\n\n // 2단계: 선으로 테이블 그리드 구성\n const grids = buildTableGrids(horizontals, verticals)\n\n if (grids.length > 0) {\n return extractBlocksWithGrids(items, pageNum, grids, horizontals, verticals)\n }\n\n // Fallback: 기존 휴리스틱 (선이 없는 PDF)\n return extractPageBlocksFallback(items, pageNum)\n}\n\n// ─── 취소선 감지 (ODL StrikethroughProcessor 포팅) ─────\n// Original work: Copyright 2025-2026 Hancom Inc. (Apache-2.0)\n// https://github.com/opendataloader-project/opendataloader-pdf\n\n/** 취소선 최대 두께 (pt) — 굵은 선은 배경 채움/테두리 */\nconst STRIKE_MAX_THICKNESS = 2.0\n/** 취소선 두께 / 텍스트 높이 최대 비율 */\nconst STRIKE_MAX_THICKNESS_RATIO = 0.25\n/** 선 Y와 텍스트 중심 Y의 허용 오차 (텍스트 높이 비율) */\nconst STRIKE_CENTER_TOLERANCE = 0.25\n/** 선이 텍스트를 덮어야 하는 최소 수평 비율 */\nconst STRIKE_MIN_OVERLAP_RATIO = 0.8\n/** 선 폭 / 매칭 텍스트 총폭 최대 비율 — 표 구분선/배경선 오탐 방지 */\nconst STRIKE_MAX_LINE_TO_TEXT_RATIO = 1.5\n\n/**\n * 텍스트 중심을 가로지르는 얇은 수평선을 찾아 해당 아이템에 strike 마킹.\n * 법령 개정문(신구조문대비표)의 삭제 표시 텍스트 보존용.\n */\nfunction markStrikethroughItems(items: NormItem[], horizontals: LineSegment[]): void {\n if (items.length === 0 || horizontals.length === 0) return\n\n for (const line of horizontals) {\n if (line.lineWidth > STRIKE_MAX_THICKNESS) continue\n const matches: NormItem[] = []\n for (const item of items) {\n const h = item.h > 0 ? item.h : item.fontSize\n if (h <= 0 || item.w <= 0) continue\n if (line.lineWidth > h * STRIKE_MAX_THICKNESS_RATIO) continue\n // 글자 중심 근사: baseline(y) + 높이의 40% (한글 x-height 중앙)\n const centerY = item.y + h * 0.4\n if (Math.abs(line.y1 - centerY) > h * STRIKE_CENTER_TOLERANCE) continue\n const overlap = Math.min(line.x2, item.x + item.w) - Math.max(line.x1, item.x)\n if (overlap / item.w < STRIKE_MIN_OVERLAP_RATIO) continue\n matches.push(item)\n }\n if (matches.length === 0) continue\n // 선 폭이 매칭 텍스트 총폭의 1.5배 이내여야 취소선 (표 괘선 오탐 방지)\n let totalW = 0\n for (const m of matches) totalW += m.w\n if (totalW <= 0 || (line.x2 - line.x1) / totalW > STRIKE_MAX_LINE_TO_TEXT_RATIO) continue\n for (const m of matches) m.strike = true\n }\n}\n\n/**\n * strike 마킹된 연속 아이템 run을 ~~...~~ 마크다운으로 감싼다.\n * (같은 시각적 줄에서 인접한 마킹 아이템들을 하나의 run으로 묶음)\n */\nfunction wrapStrikethroughRuns(items: NormItem[]): void {\n const struck = items.filter(i => i.strike)\n if (struck.length === 0) return\n\n // 줄 단위 그룹핑 (y ±3) 후 x 순 정렬\n const lines = new Map<number, NormItem[]>()\n for (const item of struck) {\n const key = Math.round(item.y / 3)\n const arr = lines.get(key) || []\n arr.push(item)\n lines.set(key, arr)\n }\n for (const arr of lines.values()) {\n arr.sort((a, b) => a.x - b.x)\n arr[0].text = \"~~\" + arr[0].text\n arr[arr.length - 1].text = arr[arr.length - 1].text + \"~~\"\n }\n}\n\n/**\n * 선 기반 그리드가 감지된 경우: 테이블 영역의 텍스트는 셀에 매핑,\n * 나머지는 일반 텍스트 블록으로 처리.\n */\nfunction extractBlocksWithGrids(\n items: NormItem[],\n pageNum: number,\n grids: TableGrid[],\n horizontals: import(\"./line-detector.js\").LineSegment[],\n verticals: import(\"./line-detector.js\").LineSegment[],\n): IRBlock[] {\n const blocks: IRBlock[] = []\n const usedItems = new Set<NormItem>()\n\n // 그리드를 Y좌표 내림차순 정렬 (위→아래)\n const sortedGrids = [...grids].sort((a, b) => b.bbox.y2 - a.bbox.y2)\n\n for (const grid of sortedGrids) {\n // 1행 다열 그리드는 테이블 헤더일 가능성 높음 → 스킵하여 클러스터 감지에 위임\n const numGridRows = grid.rowYs.length - 1\n const numGridCols = grid.colXs.length - 1\n if (numGridRows === 1 && numGridCols >= 2) continue\n // 1열 다행 그리드 (세로선 없는 표) → 스킵하여 클러스터 감지로 열 추론 위임\n // Why: 행 구분선만 있는 표는 builder.ts 의 1-col branch 에서 세로 일렬로 플래튼되어\n // 테이블 구조가 무너짐. 클러스터 기반 X좌표 정렬로 열을 복원할 기회 제공.\n if (numGridCols === 1 && numGridRows >= 2) continue\n\n // 그리드 영역 내 텍스트 아이템 수집\n const tableItems: NormItem[] = []\n const pad = 3\n const gridW = grid.bbox.x2 - grid.bbox.x1\n for (const item of items) {\n if (usedItems.has(item)) continue\n // Y 범위 체크\n if (item.y < grid.bbox.y1 - pad || item.y > grid.bbox.y2 + pad) continue\n // X 범위 체크 — 아이템의 시작과 끝이 모두 그리드 안에 있어야 함\n if (item.x < grid.bbox.x1 - pad || item.x + item.w > grid.bbox.x2 + pad) continue\n // 좁은 그리드(120px 미만)에서 큰 아이템이 경계에 걸치면 제외\n // 제목 텍스트가 인접 그리드에 잡히는 것을 방지\n if (gridW < 120 && item.x + item.w > grid.bbox.x2 - 2) continue\n tableItems.push(item)\n usedItems.add(item)\n }\n\n // 셀 추출\n const cells = extractCells(grid, horizontals, verticals)\n if (cells.length === 0) continue\n\n // 텍스트→셀 매핑 (hasSpaceBefore 전파 — 셀 텍스트 단어 공백 복원)\n const textItems: TextItem[] = tableItems.map(i => ({\n text: i.text, x: i.x, y: i.y, w: i.w, h: i.h,\n fontSize: i.fontSize, fontName: i.fontName, hasSpaceBefore: i.hasSpaceBefore,\n }))\n const cellTextMap = mapTextToCells(textItems, cells)\n\n // IRTable 구성\n const numRows = grid.rowYs.length - 1\n const numCols = grid.colXs.length - 1\n const irGrid: import(\"../types.js\").IRCell[][] = Array.from(\n { length: numRows },\n () => Array.from({ length: numCols }, () => ({ text: \"\", colSpan: 1, rowSpan: 1 })),\n )\n\n for (const cell of cells) {\n const cellItems = cellTextMap.get(cell) || []\n let text = cellTextToString(cellItems)\n // 셀 안의 페이지 번호 표시 제거 (\"- 2 -\" 등)\n text = text.replace(/^[\\s]*[-–—]\\s*\\d+\\s*[-–—][\\s]*$/gm, \"\").trim()\n // 셀 텍스트 균등배분 공백 제거 (\"경 제 총 괄 반\" → \"경제총괄반\")\n text = text.split(\"\\n\").map(line => collapseEvenSpacing(line)).join(\"\\n\")\n irGrid[cell.row][cell.col] = {\n text,\n colSpan: cell.colSpan,\n rowSpan: cell.rowSpan,\n }\n }\n\n // 과소분할 표 재구성 (ODL TableStructureNormalizer):\n // 행≤2 + 열≥3 + 셀 안에 텍스트 줄이 뭉친 표는 줄 centerY 기반 row band로 행 복원\n let finalGrid = irGrid\n let finalRows = numRows\n if (numRows <= 2 && numCols >= 3) {\n const rebuilt = normalizeUndersegmentedTable(irGrid, grid.colXs, textItems)\n if (rebuilt) {\n finalGrid = rebuilt.map(row => row.map(rawText => {\n const cleaned = rawText.replace(/^[\\s]*[-–—]\\s*\\d+\\s*[-–—][\\s]*$/gm, \"\").trim()\n return {\n text: cleaned.split(\"\\n\").map(line => collapseEvenSpacing(line)).join(\"\\n\"),\n colSpan: 1,\n rowSpan: 1,\n }\n }))\n finalRows = finalGrid.length\n }\n }\n\n const irTable: IRTable = {\n rows: finalRows,\n cols: numCols,\n cells: finalGrid,\n hasHeader: finalRows > 1,\n }\n\n // 빈 테이블(모든 셀이 빈 문자열) 스킵\n const hasContent = finalGrid.some(row => row.some(cell => cell.text.trim() !== \"\"))\n if (!hasContent) continue\n\n const tableBbox: BoundingBox = {\n page: pageNum,\n x: grid.bbox.x1, y: grid.bbox.y1,\n width: grid.bbox.x2 - grid.bbox.x1, height: grid.bbox.y2 - grid.bbox.y1,\n }\n\n // 의사 테이블 필터: 텍스트성 내용 → paragraph로 복원 (구조 보존)\n if (shouldDemoteTable(irTable)) {\n const demoted = demoteTableToText(irTable)\n if (demoted) {\n // 텍스트 박스(1x1 또는 1행 그리드) demote 시 앞뒤 줄바꿈으로 본문과 분리\n const text = numGridRows === 1 ? \"\\n\" + demoted + \"\\n\" : demoted\n blocks.push({ type: \"paragraph\", text, pageNumber: pageNum, bbox: tableBbox, style: dominantStyle(tableItems) })\n }\n continue\n }\n\n blocks.push({ type: \"table\", table: irTable, pageNumber: pageNum, bbox: tableBbox })\n }\n\n // 테이블에 속하지 않은 나머지 텍스트 → 일반 블록\n let remaining = items.filter(i => !usedItems.has(i))\n if (remaining.length > 0) {\n remaining.sort((a, b) => b.y - a.y || a.x - b.x)\n\n // 클러스터 기반 테이블 감지 (XY-Cut 전에 실행 — 테이블이 쪼개지지 않도록)\n const clusterItems: ClusterItem[] = remaining.map(i => ({\n text: i.text, x: i.x, y: i.y, w: i.w, h: i.h,\n fontSize: i.fontSize, fontName: i.fontName, hasSpaceBefore: i.hasSpaceBefore,\n }))\n const clusterResults = detectClusterTables(clusterItems, pageNum)\n if (clusterResults.length > 0) {\n const ciToIdx = new Map<ClusterItem, number>()\n for (let ci = 0; ci < clusterItems.length; ci++) ciToIdx.set(clusterItems[ci], ci)\n const usedClusterIndices = new Set<number>()\n for (const cr of clusterResults) {\n for (const ci of cr.usedItems) {\n const idx = ciToIdx.get(ci)\n if (idx !== undefined) usedClusterIndices.add(idx)\n }\n blocks.push({ type: \"table\", table: cr.table, pageNumber: pageNum, bbox: cr.bbox })\n }\n remaining = remaining.filter((_, idx) => !usedClusterIndices.has(idx))\n }\n\n // XY-Cut으로 왼쪽 본문과 오른쪽 부서명 등을 분리 후 개별 처리\n if (remaining.length > 0) {\n const allY = remaining.map(i => i.y)\n const pageH = safeMax(allY) - safeMin(allY)\n const groups = xyCutOrder(remaining, Math.max(15, pageH * 0.03))\n const textBlocks: IRBlock[] = []\n for (const group of groups) {\n if (group.length === 0) continue\n const groupBlocks = extractPageBlocksFallback(group, pageNum)\n for (const b of groupBlocks) textBlocks.push(b)\n }\n const finalTextBlocks = detectListBlocks(textBlocks)\n for (const b of finalTextBlocks) blocks.push(b)\n }\n\n // Y좌표 기반 정렬\n blocks.sort((a, b) => {\n const ay = a.bbox ? (a.bbox.y + a.bbox.height) : 0\n const by = b.bbox ? (b.bbox.y + b.bbox.height) : 0\n return by - ay // PDF는 y가 위가 큼 → 내림차순\n })\n return mergeAdjacentTableBlocks(blocks)\n }\n\n return mergeAdjacentTableBlocks(blocks)\n}\n\n/**\n * 페이지 걸친 표 병합 — ODL TableBorderProcessor.checkNeighborTables 포팅.\n * Original work: Copyright 2025-2026 Hancom Inc. (Apache-2.0)\n *\n * 페이지 N의 마지막 표와 페이지 N+1의 첫 표가:\n * - 블록 배열에서 인접 (사이에 본문 블록 없음 — 머리글/바닥글 제거 후 기준)\n * - 열 수 동일\n * - 좌우 경계 근접 (폭 대비 0.2 비율 이내, ODL NEIGHBOUR_TABLE_EPSILON)\n * 이면 한 표로 병합. 반복 헤더 행(첫 행 텍스트 동일)은 제거.\n */\nconst NEIGHBOR_TABLE_EPSILON = 0.2\n\nexport function mergeCrossPageTables(blocks: IRBlock[]): void {\n for (let i = blocks.length - 2; i >= 0; i--) {\n const prev = blocks[i]\n const curr = blocks[i + 1]\n if (prev.type !== \"table\" || curr.type !== \"table\" || !prev.table || !curr.table) continue\n if (!prev.pageNumber || !curr.pageNumber || curr.pageNumber !== prev.pageNumber + 1) continue\n if (prev.table.cols !== curr.table.cols) continue\n if (!prev.bbox || !curr.bbox) continue\n\n // 좌우 경계 근접 검증 (폭 대비 비율)\n const width = Math.max(prev.bbox.width, curr.bbox.width, 1)\n const leftDiff = Math.abs(prev.bbox.x - curr.bbox.x)\n const rightDiff = Math.abs((prev.bbox.x + prev.bbox.width) - (curr.bbox.x + curr.bbox.width))\n if (leftDiff > width * NEIGHBOR_TABLE_EPSILON || rightDiff > width * NEIGHBOR_TABLE_EPSILON) continue\n\n // 반복 헤더 행 제거: 다음 표 첫 행이 이전 표 첫 행과 동일하면 중복 헤더\n let currCells = curr.table.cells\n if (currCells.length > 1 && prev.table.cells.length > 0 &&\n rowTextsEqual(prev.table.cells[0], currCells[0])) {\n currCells = currCells.slice(1)\n }\n if (currCells.length === 0) {\n blocks.splice(i + 1, 1)\n continue\n }\n\n const merged: IRTable = {\n rows: prev.table.rows + currCells.length,\n cols: prev.table.cols,\n cells: [...prev.table.cells, ...currCells],\n hasHeader: prev.table.hasHeader,\n caption: prev.table.caption,\n }\n blocks[i] = { ...prev, table: merged }\n blocks.splice(i + 1, 1)\n }\n}\n\n/** 두 행의 셀 텍스트가 모두 동일한지 (공백 정규화 후 비교) */\nfunction rowTextsEqual(a: import(\"../types.js\").IRCell[], b: import(\"../types.js\").IRCell[]): boolean {\n if (a.length !== b.length) return false\n const norm = (t: string) => t.replace(/\\s+/g, \"\")\n for (let i = 0; i < a.length; i++) {\n if (norm(a[i].text) !== norm(b[i].text)) return false\n }\n // 빈 행끼리의 비교는 의미 없음\n return a.some(c => c.text.trim() !== \"\")\n}\n\n/** 같은 열 수의 연속 테이블 블록을 하나로 합침 */\nfunction mergeAdjacentTableBlocks(blocks: IRBlock[]): IRBlock[] {\n if (blocks.length <= 1) return blocks\n const result: IRBlock[] = [blocks[0]]\n for (let i = 1; i < blocks.length; i++) {\n const prev = result[result.length - 1]\n const curr = blocks[i]\n if (prev.type === \"table\" && curr.type === \"table\" && prev.table && curr.table &&\n prev.table.cols === curr.table.cols) {\n // 합치기: prev의 cells에 curr의 cells 추가\n const merged: IRTable = {\n rows: prev.table.rows + curr.table.rows,\n cols: prev.table.cols,\n cells: [...prev.table.cells, ...curr.table.cells],\n hasHeader: prev.table.hasHeader,\n }\n result[result.length - 1] = { ...prev, table: merged }\n } else {\n result.push(curr)\n }\n }\n return result\n}\n\n/**\n * 기존 휴리스틱 기반 페이지 블록 추출 (선이 없는 PDF 대비 fallback).\n */\nfunction extractPageBlocksFallback(items: NormItem[], pageNum: number): IRBlock[] {\n if (items.length === 0) return []\n\n const blocks: IRBlock[] = []\n\n // 1단계: 클러스터 기반 테이블 감지 우선 (헤더 감지 시 정확도 높음)\n const clusterItems: ClusterItem[] = items.map(i => ({\n text: i.text, x: i.x, y: i.y, w: i.w, h: i.h,\n fontSize: i.fontSize, fontName: i.fontName, hasSpaceBefore: i.hasSpaceBefore,\n }))\n const clusterResults = detectClusterTables(clusterItems, pageNum)\n\n if (clusterResults.length > 0) {\n const ciToIdx = new Map<ClusterItem, number>()\n for (let ci = 0; ci < clusterItems.length; ci++) ciToIdx.set(clusterItems[ci], ci)\n const usedIndices = new Set<number>()\n for (const cr of clusterResults) {\n for (const ci of cr.usedItems) {\n const idx = ciToIdx.get(ci)\n if (idx !== undefined) usedIndices.add(idx)\n }\n blocks.push({ type: \"table\", table: cr.table, pageNumber: pageNum, bbox: cr.bbox })\n }\n\n // 테이블에 속하지 않은 나머지 텍스트 → 일반 블록\n const remaining = items.filter((_, idx) => !usedIndices.has(idx))\n if (remaining.length > 0) {\n const yLines = mergeSuperscriptLines(groupByY(remaining))\n for (const line of yLines) {\n const text = mergeLineSimple(line)\n if (!text.trim()) continue\n const bbox = computeBBox(line, pageNum)\n blocks.push({ type: \"paragraph\", text, pageNumber: pageNum, bbox, style: dominantStyle(line) })\n }\n }\n\n blocks.sort((a, b) => {\n const ay = a.bbox ? (a.bbox.y + a.bbox.height) : 0\n const by = b.bbox ? (b.bbox.y + b.bbox.height) : 0\n return by - ay\n })\n } else {\n // 2단계: 레거시 컬럼 감지 (3+ 열)\n const allYLines = mergeSuperscriptLines(groupByY(items))\n const columns = detectColumns(allYLines)\n\n if (columns && columns.length >= 3) {\n const tableText = extractWithColumns(allYLines, columns)\n const bbox = computeBBox(items, pageNum)\n blocks.push({ type: \"paragraph\", text: tableText, pageNumber: pageNum, bbox, style: dominantStyle(items) })\n } else {\n // 3단계: XY-Cut으로 읽기 순서 결정\n const allY = items.map(i => i.y)\n const pageHeight = safeMax(allY) - safeMin(allY)\n const gapThreshold = Math.max(15, pageHeight * 0.03)\n\n const orderedGroups = xyCutOrder(items, gapThreshold)\n\n for (const group of orderedGroups) {\n if (group.length === 0) continue\n const yLines = mergeSuperscriptLines(groupByY(group))\n\n const groupColumns = detectColumns(yLines)\n if (groupColumns && groupColumns.length >= 3) {\n const tableText = extractWithColumns(yLines, groupColumns)\n const bbox = computeBBox(group, pageNum)\n blocks.push({ type: \"paragraph\", text: tableText, pageNumber: pageNum, bbox, style: dominantStyle(group) })\n } else {\n for (const line of yLines) {\n const text = mergeLineSimple(line)\n if (!text.trim()) continue\n const bbox = computeBBox(line, pageNum)\n blocks.push({ type: \"paragraph\", text, pageNumber: pageNum, bbox, style: dominantStyle(line) })\n }\n }\n }\n }\n }\n\n // 한국어 특수 테이블 감지 (구분/항목/종류 패턴)\n return detectSpecialKoreanTables(blocks)\n}\n\n/** 아이템 그룹에서 바운딩 박스 계산 */\nfunction computeBBox(items: NormItem[], pageNum: number): BoundingBox {\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity\n for (const i of items) {\n if (i.x < minX) minX = i.x\n if (i.y < minY) minY = i.y\n if (i.x + i.w > maxX) maxX = i.x + i.w\n // h가 0인 경우 fontSize를 높이 대용으로 사용 (pdfjs가 height를 제공하지 않는 경우)\n const effectiveH = i.h > 0 ? i.h : i.fontSize\n if (i.y + effectiveH > maxY) maxY = i.y + effectiveH\n }\n return { page: pageNum, x: minX, y: minY, width: maxX - minX, height: maxY - minY }\n}\n\n/** 아이템 그룹의 대표 스타일 (최빈 폰트 크기) */\nfunction dominantStyle(items: NormItem[]): { fontSize: number; fontName: string } | undefined {\n if (items.length === 0) return undefined\n // 최빈 폰트 크기 찾기\n const freq = new Map<number, number>()\n let maxCount = 0, dominantSize = 0\n for (const i of items) {\n if (i.fontSize <= 0) continue\n const count = (freq.get(i.fontSize) || 0) + 1\n freq.set(i.fontSize, count)\n if (count > maxCount) { maxCount = count; dominantSize = i.fontSize }\n }\n if (dominantSize === 0) return undefined\n // 대표 폰트명 (빈 문자열은 undefined로)\n const fontName = items.find(i => i.fontSize === dominantSize)?.fontName || undefined\n return { fontSize: dominantSize, fontName }\n}\n\nfunction normalizeItems(rawItems: PdfTextItem[]): NormItem[] {\n const items: NormItem[] = []\n // pdfjs 공백 아이템 위치 수집 — 단어 경계 힌트로 활용\n const spacePositions: { x: number; y: number }[] = []\n\n for (const i of rawItems) {\n if (typeof i.str !== \"string\") continue\n const x = Math.round(i.transform[4])\n const y = Math.round(i.transform[5])\n\n if (!i.str.trim()) {\n // 공백 전용 아이템: 위치만 기록 (단어 구분 힌트)\n spacePositions.push({ x, y })\n continue\n }\n\n const scaleY = Math.abs(i.transform[3])\n const scaleX = Math.abs(i.transform[0])\n const fontSize = Math.round(Math.max(scaleY, scaleX))\n const w = Math.round(i.width)\n const h = Math.round(i.height)\n const isHidden = fontSize === 0 || (i.width === 0 && i.str.trim().length > 0)\n\n // letterSpacing이 적용된 숫자/기호 문자열 정규화\n // \"45 0 -7 3 40 )\" → \"450-7340)\" (전화번호, 금액 등)\n let text = i.str.trim()\n if (/^[\\d\\s\\-().·,☎]+$/.test(text) && /\\d/.test(text) && / /.test(text)) {\n text = text.replace(/ /g, \"\")\n }\n\n // 균등배분 TextItem 분해: \"홍 보 지 원 반\" → 개별 글자 아이템으로\n const split = splitEvenSpacedItem(text, x, w, fontSize)\n if (split) {\n for (const s of split) {\n items.push({ text: s.text, x: s.x, y, w: s.w, h, fontSize, fontName: i.fontName || \"\", isHidden })\n }\n } else {\n items.push({ text, x, y, w, h, fontSize, fontName: i.fontName || \"\", isHidden })\n }\n }\n\n const sorted = items.sort((a, b) => b.y - a.y || a.x - b.x)\n\n // 1. 가짜 볼드 중복 제거: 같은 텍스트가 거의 동일한 좌표(±3px)에 2~3회 겹쳐진 경우\n // PDF에서 볼드 효과를 위해 텍스트를 여러 번 렌더링하는 기법\n const deduped: NormItem[] = []\n for (let i = 0; i < sorted.length; i++) {\n let isDup = false\n // Y 정렬(desc)이므로 역순 스캔 — Y 차이가 tolerance를 넘으면 중단\n for (let j = deduped.length - 1; j >= 0; j--) {\n const prev = deduped[j]\n if (prev.y - sorted[i].y > 3) break // 이전 아이템이 너무 높음 → 중단\n if (Math.abs(prev.y - sorted[i].y) <= 3 &&\n prev.text === sorted[i].text && Math.abs(prev.x - sorted[i].x) <= 3) {\n isDup = true\n break\n }\n }\n if (!isDup) deduped.push(sorted[i])\n }\n\n // 2. 공백 아이템 위치를 NormItem.hasSpaceBefore로 전파\n // 같은 Y라인(±3px)에서 공백 바로 오른쪽의 \"가장 가까운\" 아이템에만 표시.\n // (기존: 20px 윈도 내 모든 아이템 마킹 → \"기관 [공백] 내부에서\"의 '부'까지\n // 오마킹되어 \"내 부에서\" 과다 공백 발생 — 인접 아이템 1개로 제한)\n if (spacePositions.length > 0) {\n for (const sp of spacePositions) {\n let nearest: NormItem | null = null\n for (const item of deduped) {\n if (Math.abs(sp.y - item.y) > 3) continue\n const dist = item.x - sp.x\n if (dist >= -1 && dist <= 20 && (!nearest || item.x < nearest.x)) {\n nearest = item\n }\n }\n if (nearest) nearest.hasSpaceBefore = true\n }\n }\n\n return deduped\n}\n\n/**\n * 균등배분 TextItem 감지 및 분해.\n * \"홍 보 지 원 반\" (1자+공백 패턴) → [{text:\"홍\",x,w}, {text:\"보\",x,w}, ...]\n * 분해하면 이후 detectEvenSpacedItems가 좌표 기반으로 정확히 감지할 수 있음.\n */\nfunction splitEvenSpacedItem(\n text: string, itemX: number, itemW: number, fontSize: number,\n): { text: string; x: number; w: number }[] | null {\n // 한글/숫자 1자 + 공백이 3회+ 반복되는 패턴\n // \"홍 보 지 원 반\", \"세 무 1 과\", \"주 요 내 용\"\n if (!/^[가-힣\\d](?: [가-힣\\d]){2,}$/.test(text)) return null\n\n const chars = text.split(\" \")\n if (chars.length < 3) return null\n\n // 글자당 폭 계산 — 전체 width를 글자 수로 나눔\n const charW = itemW / chars.length\n // 글자 폭이 너무 크면 균등배분이 아님 (한 글자가 fontSize의 2배 넘으면 이상)\n if (charW > fontSize * 2) return null\n\n return chars.map((ch, idx) => ({\n text: ch,\n x: Math.round(itemX + idx * charW),\n w: Math.round(charW * 0.8), // 실제 글자 폭은 간격보다 좁음\n }))\n}\n\nfunction groupByY(items: NormItem[]): NormItem[][] {\n if (items.length === 0) return []\n const lines: NormItem[][] = []\n let curY = items[0].y\n let curLine: NormItem[] = [items[0]]\n\n for (let i = 1; i < items.length; i++) {\n // Y좌표 허용 오차 3px — PDF 렌더링 미세 오차 보정, 별표 행 경계 감지에 최적화된 값\n if (Math.abs(items[i].y - curY) > 3) {\n lines.push(curLine)\n curLine = []\n curY = items[i].y\n }\n curLine.push(items[i])\n }\n if (curLine.length > 0) lines.push(curLine)\n return lines\n}\n\n/**\n * 첨자 줄 병합 — 본문 줄보다 살짝 위에 뜬 작은 글자 조각(각주 마커 `*`, 원문자 ①,\n * 덧말)이 groupByY에서 별도 줄로 분리된 것을 본문 줄에 흡수한다.\n * 조각 줄(아이템 ≤3개·각 ≤8자·글자 박스가 인접 줄보다 확실히 작음)이 인접 줄과\n * 수직으로 겹치면 같은 시각적 줄이다. mergeLineSimple이 x순 정렬하므로\n * 병합 후 원래 인라인 위치(\"①근로자...\")가 복원된다.\n */\nfunction mergeSuperscriptLines(lines: NormItem[][]): NormItem[][] {\n if (lines.length <= 1) return lines\n const band = (line: NormItem[]) => {\n let bottom = Infinity, top = -Infinity\n for (const i of line) {\n const h = i.h > 0 ? i.h : i.fontSize\n if (i.y < bottom) bottom = i.y\n if (i.y + h > top) top = i.y + h\n }\n return { bottom, top, height: top - bottom }\n }\n const isFrag = (line: NormItem[]) =>\n line.length <= 3 && line.every(i => i.text.trim().length <= 8)\n\n const result: NormItem[][] = [lines[0]]\n for (let i = 1; i < lines.length; i++) {\n const prev = result[result.length - 1]\n const curr = lines[i]\n const a = band(prev)\n const b = band(curr)\n const overlap = Math.min(a.top, b.top) - Math.max(a.bottom, b.bottom)\n const prevIsFrag = isFrag(prev) && a.height <= b.height * 0.8 && overlap >= a.height * 0.5\n const currIsFrag = isFrag(curr) && b.height <= a.height * 0.8 && overlap >= b.height * 0.5\n if (prevIsFrag || currIsFrag) {\n result[result.length - 1] = [...prev, ...curr]\n } else {\n result.push(curr)\n }\n }\n return result\n}\n\n// ═══════════════════════════════════════════════════════\n// 열 경계 감지 — 빈도 기반 x-히스토그램 클러스터링\n// ═══════════════════════════════════════════════════════\n\n/** prose 라인 판별: 아이템 간 gap이 모두 작으면 문장 (단어 나열) */\nfunction isProseSpread(items: NormItem[]): boolean {\n if (items.length < 4) return false\n const sorted = [...items].sort((a, b) => a.x - b.x)\n const gaps: number[] = []\n for (let i = 1; i < sorted.length; i++) {\n gaps.push(sorted[i].x - (sorted[i - 1].x + sorted[i - 1].w))\n }\n // gap의 최대값이 작고 평균 단어 길이가 짧으면 prose\n const maxGap = safeMax(gaps)\n const avgLen = items.reduce((s, i) => s + i.text.length, 0) / items.length\n // 짧은 단어들이 좁은 간격으로 나열 = prose (예: \"위 표 제3호나목에서 남은 유효기간...\")\n return maxGap < 40 && avgLen < 5\n}\n\nfunction detectColumns(yLines: NormItem[][]): number[] | null {\n const allItems = yLines.flat()\n if (allItems.length === 0) return null\n const pageWidth = safeMax(allItems.map(i => i.x + i.w)) - safeMin(allItems.map(i => i.x))\n if (pageWidth < 100) return null\n\n // \"비고\" 이전 아이템만 사용 (비고 이후는 prose)\n let bigoLineIdx = -1\n for (let i = 0; i < yLines.length; i++) {\n if (yLines[i].length <= 2 && yLines[i].some(item => item.text === \"비고\")) {\n bigoLineIdx = i\n break\n }\n }\n const tableYLines = bigoLineIdx >= 0 ? yLines.slice(0, bigoLineIdx) : yLines\n\n // Step 1: 모든 아이템의 x를 수집 (prose 라인 제외)\n // CLUSTER_TOL 22px — 한국 공문서 PDF 열 간격에 최적화, 별표 표 열 감지 핵심값\n const CLUSTER_TOL = 22\n const xClusters: { center: number; count: number; minX: number }[] = []\n\n for (const line of tableYLines) {\n if (isProseSpread(line)) continue\n for (const item of line) {\n let found = false\n for (const c of xClusters) {\n if (Math.abs(item.x - c.center) <= CLUSTER_TOL) {\n c.center = Math.round((c.center * c.count + item.x) / (c.count + 1))\n c.minX = Math.min(c.minX, item.x)\n c.count++\n found = true\n break\n }\n }\n if (!found) {\n xClusters.push({ center: item.x, count: 1, minX: item.x })\n }\n }\n }\n\n // Step 2: 빈도 피크 — 최소 3회 이상 등장 (단발성 텍스트 노이즈 제거)\n const peaks = xClusters\n .filter(c => c.count >= 3)\n .sort((a, b) => a.minX - b.minX)\n\n // 최소 3개 열이 있어야 테이블로 판별 — 2열은 일반 2단 레이아웃과 구분 불가\n if (peaks.length < 3) return null\n\n // Step 3: 가까운 피크 병합 — MERGE_TOL 40px (같은 논리 열의 미세 위치 차이 흡수)\n const MERGE_TOL = 40\n const merged: { center: number; count: number; minX: number }[] = [peaks[0]]\n for (let i = 1; i < peaks.length; i++) {\n const prev = merged[merged.length - 1]\n if (peaks[i].minX - prev.minX < MERGE_TOL) {\n // 빈도 높은 쪽 유지, 최소 x는 작은 값\n if (peaks[i].count > prev.count) {\n prev.center = peaks[i].center\n }\n prev.count += peaks[i].count\n prev.minX = Math.min(prev.minX, peaks[i].minX)\n } else {\n merged.push({ ...peaks[i] })\n }\n }\n\n // 열 경계 = 각 클러스터의 minX (왼쪽 정렬 기준), 병합 후 재검증\n const rawColumns = merged.filter(c => c.count >= 3).map(c => c.minX)\n if (rawColumns.length < 3) return null\n\n // 최소 열 폭 검증: 30px 미만인 열은 인접 열과 병합 (한 글자 열 방지)\n const MIN_DETECT_COL_WIDTH = 30\n const columns: number[] = [rawColumns[0]]\n for (let i = 1; i < rawColumns.length; i++) {\n if (rawColumns[i] - columns[columns.length - 1] < MIN_DETECT_COL_WIDTH) continue\n columns.push(rawColumns[i])\n }\n return columns.length >= 3 ? columns : null\n}\n\nfunction findColumn(x: number, columns: number[]): number {\n for (let i = columns.length - 1; i >= 0; i--) {\n // 10px 왼쪽 허용 오차 — 셀 내 텍스트 미세 좌측 이탈 보정\n if (x >= columns[i] - 10) return i\n }\n return 0\n}\n\n// ═══════════════════════════════════════════════════════\n// 열 기반 추출 — 테이블/텍스트 영역 분리\n// ═══════════════════════════════════════════════════════\n\nfunction extractWithColumns(yLines: NormItem[][], columns: number[]): string {\n const result: string[] = []\n const colMin = columns[0]\n const colMax = columns[columns.length - 1]\n\n // \"비고\" 라인 감지 — 이후는 텍스트로 처리\n let bigoIdx = -1\n for (let i = 0; i < yLines.length; i++) {\n if (yLines[i].length <= 2 && yLines[i].some(item => item.text === \"비고\")) {\n bigoIdx = i\n break\n }\n }\n\n // 테이블 시작: 첫 번째 다열(3+ 열 사용) 라인\n let tableStart = -1\n for (let i = 0; i < (bigoIdx >= 0 ? bigoIdx : yLines.length); i++) {\n const usedCols = new Set(yLines[i].map(item => findColumn(item.x, columns)))\n if (usedCols.size >= 3) {\n tableStart = i\n break\n }\n }\n\n const tableEnd = bigoIdx >= 0 ? bigoIdx : yLines.length\n\n // 테이블 시작 이전 = 텍스트\n for (let i = 0; i < (tableStart >= 0 ? tableStart : tableEnd); i++) {\n result.push(mergeLineSimple(yLines[i]))\n }\n\n // 테이블 영역: 모든 라인을 그리드에 포함 (단일 아이템 라인도)\n if (tableStart >= 0) {\n const tableLines = yLines.slice(tableStart, tableEnd)\n // 테이블 x범위 밖의 라인만 텍스트로 분리\n // 좌측 20px, 우측 200px 허용 — 비고/주석 열이 오른쪽에 넓게 위치하는 공문서 특성 반영\n const gridLines: NormItem[][] = []\n for (const line of tableLines) {\n const inRange = line.some(item =>\n item.x >= colMin - 20 && item.x <= colMax + 200\n )\n if (inRange && !isProseSpread(line)) {\n gridLines.push(line)\n } else {\n // 그리드 밖 라인은 현재까지 축적된 그리드 출력 후 텍스트로\n if (gridLines.length > 0) {\n result.push(buildGridTable(gridLines.splice(0), columns))\n }\n result.push(mergeLineSimple(line))\n }\n }\n if (gridLines.length > 0) {\n result.push(buildGridTable(gridLines, columns))\n }\n }\n\n // 비고 영역\n if (bigoIdx >= 0) {\n result.push(\"\")\n for (let i = bigoIdx; i < yLines.length; i++) {\n result.push(mergeLineSimple(yLines[i]))\n }\n }\n\n return result.join(\"\\n\")\n}\n\n// ═══════════════════════════════════════════════════════\n// 그리드 테이블 빌더 — y-라인을 열에 배치 후 행 병합\n// ═══════════════════════════════════════════════════════\n\nfunction buildGridTable(lines: NormItem[][], columns: number[]): string {\n const numCols = columns.length\n\n // Step 1: 각 y-라인을 열에 배치\n const yRows: string[][] = lines.map(items => {\n const row = Array(numCols).fill(\"\")\n for (const item of items) {\n const col = findColumn(item.x, columns)\n row[col] = row[col] ? row[col] + \" \" + item.text : item.text\n }\n return row\n })\n\n // Step 2: 행 병합 — 새 논리적 행 판별\n // 데이터 열 기준점 (가격 등이 들어가는 오른쪽 열들)\n const dataColStart = Math.max(2, Math.floor(numCols / 2))\n const merged: string[][] = []\n\n for (const row of yRows) {\n if (row.every(c => c === \"\")) continue\n\n if (merged.length === 0) {\n merged.push([...row])\n continue\n }\n\n const prev = merged[merged.length - 1]\n const filledCols = row.map((c, i) => c ? i : -1).filter(i => i >= 0)\n const filledCount = filledCols.length\n\n let isNewRow = false\n\n // Rule 1: col 0에 텍스트 (3글자 이상) → 새 행 (단, \"권\"처럼 짧은 건 continuation)\n if (row[0] && row[0].length >= 3) {\n isNewRow = true\n }\n\n // Rule 2: col 1에 텍스트 → 항상 새 행 (새 항목 시작)\n if (!isNewRow && numCols > 1 && row[1]) {\n isNewRow = true\n }\n\n // Rule 3: 데이터 열(3+)에 새 값이 있고 이전 행 데이터 열에도 이미 값 있음 → 새 가격 행\n if (!isNewRow) {\n const hasData = row.slice(dataColStart).some(c => c !== \"\")\n const prevHasData = prev.slice(dataColStart).some(c => c !== \"\")\n if (hasData && prevHasData) {\n isNewRow = true\n }\n }\n\n // Exception: filledCount=1이고 col 0에 짧은 텍스트(≤2자) → word continuation (예: \"권\", \"여권\")\n if (isNewRow && filledCount === 1 && row[0] && row[0].length <= 2) {\n isNewRow = false\n }\n\n if (isNewRow) {\n merged.push([...row])\n } else {\n for (let c = 0; c < numCols; c++) {\n if (row[c]) {\n prev[c] = prev[c] ? prev[c] + \" \" + row[c] : row[c]\n }\n }\n }\n }\n\n if (merged.length < 2) {\n return merged.map(r => r.filter(c => c).join(\" \")).join(\"\\n\")\n }\n\n // Step 3: 헤더 행 병합 — 첫 N행이 모두 데이터열(dataColStart+)에 값이 없으면 헤더\n let headerEnd = 0\n for (let r = 0; r < merged.length; r++) {\n const hasDataValues = merged[r].slice(dataColStart).some(c => c && /\\d/.test(c))\n if (hasDataValues) break\n headerEnd = r + 1\n }\n\n if (headerEnd > 1) {\n // 헤더 행들을 하나로 합침\n const headerRow = Array(numCols).fill(\"\")\n for (let r = 0; r < headerEnd; r++) {\n for (let c = 0; c < numCols; c++) {\n if (merged[r][c]) {\n headerRow[c] = headerRow[c] ? headerRow[c] + \" \" + merged[r][c] : merged[r][c]\n }\n }\n }\n merged.splice(0, headerEnd, headerRow)\n }\n\n // Step 3.5: 셀 텍스트 균등배분 공백 제거 (\"경 제 총 괄 반\" → \"경제총괄반\")\n for (const row of merged) {\n for (let c = 0; c < row.length; c++) {\n if (row[c]) row[c] = collapseEvenSpacing(row[c])\n }\n }\n\n // Step 3.6: 테이블 품질 검증 — 선 없는 fallback 경로에서는 보수적으로\n const totalCells = merged.length * numCols\n const filledCells = merged.reduce((s, row) => s + row.filter(c => c).length, 0)\n // 빈 셀 과반, 행이 2 미만, 또는 3행 이하+7열 이상 → 텍스트로 복원\n if (filledCells < totalCells * 0.35 || merged.length < 2 ||\n (merged.length <= 3 && numCols >= 7)) {\n return merged.map(r => r.filter(c => c).join(\"\\t\")).join(\"\\n\")\n }\n\n // Step 4: 마크다운 테이블\n const md: string[] = []\n md.push(\"| \" + merged[0].join(\" | \") + \" |\")\n md.push(\"| \" + merged[0].map(() => \"---\").join(\" | \") + \" |\")\n for (let r = 1; r < merged.length; r++) {\n md.push(\"| \" + merged[r].join(\" | \") + \" |\")\n }\n return md.join(\"\\n\")\n}\n\n// ═══════════════════════════════════════════════════════\n// 유틸\n// ═══════════════════════════════════════════════════════\n\nfunction mergeLineSimple(items: NormItem[]): string {\n if (items.length <= 1) return items[0]?.text || \"\"\n const sorted = [...items].sort((a, b) => a.x - b.x)\n\n // 좌표 기반 균등배분 감지 (ODL TextLineProcessor 방식)\n const isEvenSpaced = detectEvenSpacedItems(sorted)\n\n let result = sorted[0].text\n for (let i = 1; i < sorted.length; i++) {\n const gap = sorted[i].x - (sorted[i - 1].x + sorted[i - 1].w)\n const avgFs = (sorted[i].fontSize + sorted[i - 1].fontSize) / 2\n\n // 탭 갭은 항상 탭으로 — 균등배분보다 우선\n // 기준: fontSize의 2배 이상 또는 30px+ (균등배분 간격은 보통 fontSize*1.5 이하)\n const tabThreshold = Math.max(avgFs * 2, 30)\n if (gap > tabThreshold) {\n result += \"\\t\"\n result += sorted[i].text\n continue\n }\n\n // 균등배분 구간이면 공백 없이 합침\n if (isEvenSpaced[i]) {\n result += sorted[i].text\n continue\n }\n\n // pdfjs 공백 아이템이 있었으면 단어 경계 — 갭 크기 무관하게 공백 삽입\n if (sorted[i].hasSpaceBefore && gap >= avgFs * 0.05) {\n result += \" \"\n result += sorted[i].text\n continue\n }\n // 마커(□○▶ 등) 뒤에 한글이 오면 항상 공백 보장 — \"□장소\" → \"□ 장소\"\n if (/[□■○●▶◆◇ㅇ]$/.test(sorted[i - 1].text) && /^[가-힣]/.test(sorted[i].text) && gap > 1) {\n result += \" \"\n result += sorted[i].text\n continue\n }\n // 폰트 크기 비례 공백 임계값 — 고정 px 기준은 Type3/대형 폰트에서 공백 소실·과다 유발\n if (gap > spaceGapThreshold(avgFs)) result += \" \"\n result += sorted[i].text\n }\n return result\n}\n\n\n\n\n/** 블록 트리의 텍스트에서 비표시 제어문자를 in-place로 제거한다. */\nfunction sanitizeBlockControlChars(blocks: IRBlock[]): void {\n for (const b of blocks) {\n if (b.text) b.text = stripControlChars(b.text)\n if (b.table) {\n for (const row of b.table.cells) {\n for (const cell of row) {\n if (cell.text) cell.text = stripControlChars(cell.text)\n }\n }\n }\n if (b.children) sanitizeBlockControlChars(b.children)\n }\n}\n\nexport function cleanPdfText(text: string): string {\n return mergeKoreanLines(\n stripControlChars(text)\n // 문서 시작 단독 페이지 번호\n .replace(/^\\d{1,4}\\n/, \"\")\n // \"- 2 -\" 스타일 페이지 번호 (독립 라인 및 목록 항목 형태 포함)\n .replace(/^[\\s]*[-–—]\\s*[-–—]?\\d+[-–—]?[\\s]*[-–—]?[\\s]*$/gm, \"\")\n // \"1 / 5\" 스타일 페이지 번호\n .replace(/^\\s*\\d+\\s*\\/\\s*\\d+\\s*$/gm, \"\")\n // 단독 페이지 번호 (줄 끝에 혼자 있는 숫자)\n .replace(/\\n\\d{1,4}\\n/g, \"\\n\")\n // 문서 마지막 단독 페이지 번호\n .replace(/\\n\\d{1,4}$/, \"\")\n // 단독 숫자 헤딩 제거 (\"# 6\\n재무과\" → \"\\n재무과\")\n .replace(/^#{1,6}\\s*\\d{1,4}\\s*$/gm, \"\")\n )\n // 균등배분 문자열 후처리 (pdfjs가 합친 TextItem + buildGridTable 셀 텍스트)\n // LaTeX 수식 라인 ($...$ / $$...$$) 은 공백이 토큰 구분자라 collapse 시 `\\cdot d` → `\\cdotd` 로 망가짐 — skip\n .replace(/^(?!\\| ---).*$/gm, line => {\n if (/^\\s*\\${1,2}.+\\${1,2}\\s*$/.test(line)) return line\n return collapseEvenSpacing(line)\n })\n // 마커 뒤 2글자 균등배분 합침 (\"□ 일 시\" → \"□ 일시\", \"□ 장 소\" → \"□ 장소\")\n .replace(/([□■◆○●▶ㅇ])\\s+([가-힣])\\s+([가-힣])/g, \"$1 $2$3\")\n // 취소선 복원: builder escapeGfm이 ~를 \\~로 이스케이프 — 쌍(~~)만 되살림\n .replace(/\\\\~\\\\~/g, \"~~\")\n // 인접 취소선 run이 붙어 생긴 빈 마크(~~~~) 정리\n .replace(/~~~~/g, \"\")\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trim()\n}\n\nfunction startsWithMarker(line: string): boolean {\n const t = line.trimStart()\n return /^[가-힣ㄱ-ㅎ][.)]/.test(t) || /^\\d+[.)]/.test(t) || /^\\([가-힣ㄱ-ㅎ\\d]+\\)/.test(t) ||\n /^[○●※▶▷◆◇■□★☆\\-·]\\s/.test(t) || /^제\\d+[조항호장절]/.test(t)\n}\n\nfunction isStandaloneHeader(line: string): boolean {\n return /^제\\d+[조항호장절](\\([^)]*\\))?(\\s+\\S+){0,7}$/.test(line.trim())\n}\n\n// ═══════════════════════════════════════════════════════\n// 표 캡션 감지 (ODL CaptionProcessor의 패턴 기반 서브셋)\n// ═══════════════════════════════════════════════════════\n\n/**\n * 캡션 라벨 패턴 — '표 1.', '<표 2>', '[표 3-1]', '그림 4', 'Table 1', 'Figure 2' 등.\n * 숫자(또는 원문자)가 반드시 있어야 함 — '표지', '그림자' 같은 일반 단어 오탐 방지.\n */\nconst TABLE_CAPTION_RE = /^[<\\[(【〈]?\\s*(표|그림|도표|Table|Figure|Fig\\.?)\\s*[\\d①-⑮][\\d.\\-]*\\s*[\\])】〉>]?[.:]?\\s*/i\n\n/** 캡션 후보 최대 길이 */\nconst CAPTION_MAX_LENGTH = 100\n/** 캡션-표 수직 거리 한계 (pt) */\nconst CAPTION_MAX_GAP = 30\n\n/**\n * 표 블록 직전/직후의 짧은 캡션 패턴 텍스트를 IRTable.caption으로 연결하고\n * 해당 paragraph 블록은 제거한다 (중복 출력 방지 — builder가 표 위에 캡션 출력).\n */\nexport function detectTableCaptions(blocks: IRBlock[]): void {\n const isCaptionCandidate = (b: IRBlock | undefined, table: IRBlock): b is IRBlock => {\n if (!b || b.type !== \"paragraph\" || !b.text) return false\n if (b.pageNumber !== table.pageNumber) return false\n const text = b.text.trim()\n if (!text || text.length > CAPTION_MAX_LENGTH || text.includes(\"\\n\")) return false\n if (!TABLE_CAPTION_RE.test(text)) return false\n // 수직 근접 + 수평 겹침 검증 (bbox 있을 때만)\n if (b.bbox && table.bbox) {\n const capTop = b.bbox.y + b.bbox.height\n const capBottom = b.bbox.y\n const tblTop = table.bbox.y + table.bbox.height\n const tblBottom = table.bbox.y\n const gap = capBottom >= tblTop ? capBottom - tblTop : tblBottom - capTop\n if (gap > CAPTION_MAX_GAP) return false\n const overlap = Math.min(b.bbox.x + b.bbox.width, table.bbox.x + table.bbox.width) -\n Math.max(b.bbox.x, table.bbox.x)\n if (overlap < Math.min(b.bbox.width, table.bbox.width) * 0.3) return false\n }\n return true\n }\n\n for (let i = 0; i < blocks.length; i++) {\n const block = blocks[i]\n if (block.type !== \"table\" || !block.table || block.table.caption) continue\n\n // 직전 블록 우선 (한국 공문서는 표 위 캡션이 일반적), 다음 블록 차선\n if (isCaptionCandidate(blocks[i - 1], block)) {\n block.table.caption = blocks[i - 1].text!.trim()\n blocks.splice(i - 1, 1)\n i--\n } else if (isCaptionCandidate(blocks[i + 1], block)) {\n block.table.caption = blocks[i + 1].text!.trim()\n blocks.splice(i + 1, 1)\n }\n }\n}\n\n// ═══════════════════════════════════════════════════════\n// 한국어 리스트 감지 — 공문서 계층 라벨 시퀀스 검증\n// (ODL ListProcessor의 한국어 서브셋 — 가나다 시퀀스, '붙임' 패턴)\n// ═══════════════════════════════════════════════════════\n\n/** 한국 공문서 항목 기호 시퀀스 (가나다순) */\nconst KOREAN_LIST_SEQ = \"가나다라마바사아자차카타파하\"\n\ninterface ListLabel {\n family: \"arabicDot\" | \"korDot\" | \"arabicParen\" | \"korParen\" | \"circled\"\n ord: number\n}\n\n/** 블록 텍스트에서 리스트 라벨 파싱 — 시퀀스 검증 가능한 라벨만 */\nfunction parseListLabel(text: string): ListLabel | null {\n let m = text.match(/^(\\d{1,2})\\.(?!\\d)\\s+/)\n if (m) return { family: \"arabicDot\", ord: parseInt(m[1], 10) }\n m = text.match(/^([가-하])\\.\\s+/)\n if (m) {\n const idx = KOREAN_LIST_SEQ.indexOf(m[1])\n if (idx >= 0) return { family: \"korDot\", ord: idx + 1 }\n }\n m = text.match(/^(\\d{1,2})\\)\\s*/)\n if (m) return { family: \"arabicParen\", ord: parseInt(m[1], 10) }\n m = text.match(/^([가-하])\\)\\s*/)\n if (m) {\n const idx = KOREAN_LIST_SEQ.indexOf(m[1])\n if (idx >= 0) return { family: \"korParen\", ord: idx + 1 }\n }\n m = text.match(/^([①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮])\\s*/)\n if (m) return { family: \"circled\", ord: m[1].charCodeAt(0) - 0x2460 + 1 }\n return null\n}\n\n/** '붙임' 패턴 (ODL ATTACHMENTS_PATTERN) — 공문서 첨부 표기 */\nconst ATTACHMENT_RE = /^붙\\s*임\\s*(\\d+[.:]?)?\\s/\n\n/**\n * 라벨 시퀀스 검증 기반 한국어 리스트 감지.\n *\n * 1) paragraph 블록의 선두 라벨(1./가./1)/가)/①)을 파싱\n * 2) 같은 family의 라벨이 +1씩 증가하는 체인(2개+)만 리스트로 확정\n * — \"2026. 6. 9.\" 같은 날짜/단발 번호 오탐 방지\n * 3) 상위 family 항목 사이에 낀 하위 family 항목은 children으로 중첩 (들여쓰기)\n * 4) '붙임 1 ...' 패턴은 시퀀스 없이도 리스트 항목으로 인정\n */\nexport function detectKoreanListBlocks(blocks: IRBlock[]): void {\n // ── 1단계: 라벨 수집 ──\n interface Labeled {\n idx: number\n label: ListLabel\n }\n const labeled: Labeled[] = []\n for (let i = 0; i < blocks.length; i++) {\n const b = blocks[i]\n if ((b.type !== \"paragraph\" && b.type !== \"list\") || !b.text) continue\n const label = parseListLabel(b.text.trim())\n if (label) labeled.push({ idx: i, label })\n }\n\n // ── 2단계: family별 시퀀스 체인 검증 ──\n // 체인: 같은 family + ord가 +1씩 증가 + 블록 간격 ≤ 20 (사이에 하위 항목/본문 허용)\n const validated = new Set<number>()\n const byFamily = new Map<string, Labeled[]>()\n for (const l of labeled) {\n const arr = byFamily.get(l.label.family) || []\n arr.push(l)\n byFamily.set(l.label.family, arr)\n }\n for (const arr of byFamily.values()) {\n let chain: Labeled[] = []\n for (const item of arr) {\n const prev = chain[chain.length - 1]\n if (prev && item.label.ord === prev.label.ord + 1 && item.idx - prev.idx <= 20) {\n chain.push(item)\n } else {\n if (chain.length >= 2) for (const c of chain) validated.add(c.idx)\n chain = [item]\n }\n }\n if (chain.length >= 2) for (const c of chain) validated.add(c.idx)\n }\n\n // ── 3단계: 변환 + 중첩 ──\n // familyStack: 현재 리스트 run에서 등장한 family 순서 (얕은 → 깊은)\n let familyStack: string[] = []\n let lastTopLevelList: IRBlock | null = null\n const toRemove = new Set<number>()\n\n for (let i = 0; i < blocks.length; i++) {\n const b = blocks[i]\n\n // 표/헤딩/구분선은 리스트 run 종료\n if (b.type === \"table\" || b.type === \"heading\" || b.type === \"separator\") {\n familyStack = []\n lastTopLevelList = null\n continue\n }\n if ((b.type !== \"paragraph\" && b.type !== \"list\") || !b.text) continue\n\n const text = b.text.trim()\n\n // '붙임' 패턴 — 시퀀스 불요\n if (b.type === \"paragraph\" && ATTACHMENT_RE.test(text)) {\n blocks[i] = { ...b, type: \"list\", listType: \"unordered\" }\n continue\n }\n\n if (!validated.has(i)) continue\n const label = parseListLabel(text)!\n\n // family 깊이 결정 — 처음 보는 family는 스택에 push\n let depth = familyStack.indexOf(label.family)\n if (depth < 0) {\n familyStack.push(label.family)\n depth = familyStack.length - 1\n } else {\n // 상위 family로 복귀하면 더 깊은 family 제거\n familyStack = familyStack.slice(0, depth + 1)\n }\n\n const listType: \"ordered\" | \"unordered\" = label.family === \"arabicDot\" ? \"ordered\" : \"unordered\"\n const listBlock: IRBlock = { ...b, type: \"list\", listType }\n\n if (depth === 0) {\n blocks[i] = listBlock\n lastTopLevelList = listBlock\n } else if (lastTopLevelList) {\n // 하위 항목 → 직전 상위 항목의 children으로 (마크다운 들여쓰기)\n if (!lastTopLevelList.children) lastTopLevelList.children = []\n lastTopLevelList.children.push(listBlock)\n toRemove.add(i)\n } else {\n // 상위 항목 없이 시작된 하위 family — 평면 리스트로\n blocks[i] = listBlock\n lastTopLevelList = listBlock\n }\n }\n\n // 제거는 뒤에서부터\n if (toRemove.size > 0) {\n const sorted = [...toRemove].sort((a, b) => b - a)\n for (const idx of sorted) blocks.splice(idx, 1)\n }\n}\n\n// ═══════════════════════════════════════════════════════\n// 리스트 감지 — paragraph 블록 중 번호 패턴을 list 블록으로 변환\n// ═══════════════════════════════════════════════════════\n\n/**\n * 연속된 paragraph 블록에서 번호 리스트 패턴을 감지하여 list 블록으로 변환.\n * \"비고\" 헤더 뒤에 오는 \"1.\", \"2.\" 패턴이 대표적.\n */\nfunction detectListBlocks(blocks: IRBlock[]): IRBlock[] {\n const result: IRBlock[] = []\n\n for (let i = 0; i < blocks.length; i++) {\n const block = blocks[i]\n\n if (block.type === \"paragraph\" && block.text) {\n const text = block.text.trim()\n // 번호 리스트: \"1.\", \"2.\" 등\n if (/^\\d+\\.\\s/.test(text)) {\n result.push({ ...block, type: \"list\", listType: \"ordered\", text: block.text })\n continue\n }\n // 비번호 리스트: ○, -, ·, ※, ▶ 등\n if (/^[○●·※▶▷◆◇\\-]\\s/.test(text)) {\n result.push({ ...block, type: \"list\", listType: \"unordered\", text: block.text })\n continue\n }\n }\n\n result.push(block)\n }\n\n return result\n}\n\n// ═══════════════════════════════════════════════════════\n// 한국어 특수 테이블 감지 — \"구분/항목/종류\" 패턴 기반 key-value 테이블\n// ═══════════════════════════════════════════════════════\n\n/**\n * ODL SpecialTableProcessor 포팅: 연속된 \"구분:\", \"항목:\", \"종류:\" 등\n * 한국어 key-value 패턴을 2열 테이블로 변환.\n *\n * 동작:\n * 1) paragraph 블록의 텍스트에서 한국어 key-value 패턴 감지\n * 2) \":\"가 있으면 key | value 2열, 없으면 colSpan=2 (전체 행)\n * 3) 연속된 패턴을 하나의 테이블로 그룹화\n */\nconst KOREAN_TABLE_HEADER_RE = /^\\(?(구분|항목|종류|분류|유형|대상|내용|기간|금액|비율|방법|절차|요건|조건|근거|목적|범위|기준)\\)?[:\\s]/\n\n/** KV 오탐 패턴: 시간(14:30), URL(://), 숫자:숫자(3:2) */\nconst KV_FALSE_POSITIVE_RE = /\\d{1,2}:\\d{2}|:\\/\\/|\\d+:\\d+/\n\nfunction detectSpecialKoreanTables(blocks: IRBlock[]): IRBlock[] {\n const result: IRBlock[] = []\n let kvLines: { key: string; value: string; block: IRBlock }[] = []\n\n const flushKvTable = () => {\n if (kvLines.length < 2) {\n // 2행 미만이면 테이블로 만들 가치 없음 → 원래 블록 복원\n for (const kv of kvLines) result.push(kv.block)\n kvLines = []\n return\n }\n\n // 2열 테이블 생성\n const cells: import(\"../types.js\").IRCell[][] = kvLines.map(kv => {\n if (kv.value) {\n return [\n { text: kv.key, colSpan: 1, rowSpan: 1 },\n { text: kv.value, colSpan: 1, rowSpan: 1 },\n ]\n }\n // \":\" 없는 줄 → 전체 행 (colSpan=2)\n return [\n { text: kv.key, colSpan: 2, rowSpan: 1 },\n { text: \"\", colSpan: 1, rowSpan: 1 },\n ]\n })\n\n const irTable: IRTable = {\n rows: cells.length,\n cols: 2,\n cells,\n hasHeader: true,\n }\n\n // 첫 블록의 위치 정보 사용\n const firstBlock = kvLines[0].block\n result.push({\n type: \"table\",\n table: irTable,\n pageNumber: firstBlock.pageNumber,\n bbox: firstBlock.bbox,\n })\n kvLines = []\n }\n\n for (const block of blocks) {\n if (block.type !== \"paragraph\" || !block.text) {\n flushKvTable()\n result.push(block)\n continue\n }\n\n const text = block.text.trim()\n\n // \"구분: xxx\" 또는 \"항목: xxx\" 패턴 매칭\n if (KOREAN_TABLE_HEADER_RE.test(text)) {\n const colonIdx = text.indexOf(\":\")\n if (colonIdx >= 0) {\n kvLines.push({\n key: text.slice(0, colonIdx).trim(),\n value: text.slice(colonIdx + 1).trim(),\n block,\n })\n } else {\n // \":\" 없이 공백으로 구분된 경우: \"구분 xxx\"\n const spaceIdx = text.search(/\\s/)\n if (spaceIdx > 0) {\n kvLines.push({\n key: text.slice(0, spaceIdx).trim(),\n value: text.slice(spaceIdx + 1).trim(),\n block,\n })\n } else {\n kvLines.push({ key: text, value: \"\", block })\n }\n }\n continue\n }\n\n // key-value 패턴이 아닌 블록이 나오면 축적된 것을 flush\n // 단, 이미 수집 중이고 현재 블록이 \"label: value\" 형태면 계속 수집\n if (kvLines.length > 0 && text.includes(\":\")) {\n // 오탐 제외: 시간(14:30), URL(http://), 숫자:숫자(3:2), 괄호 포함\n if (!KV_FALSE_POSITIVE_RE.test(text) && !text.includes(\"(\") && !text.includes(\")\")) {\n const colonIdx = text.indexOf(\":\")\n const key = text.slice(0, colonIdx).trim()\n // key가 순수 한글 2~8자 (공백/괄호 없음)면 유효한 key-value 라인\n if (/^[가-힣]+$/.test(key) && key.length >= 2 && key.length <= 8) {\n kvLines.push({\n key,\n value: text.slice(colonIdx + 1).trim(),\n block,\n })\n continue\n }\n }\n }\n\n flushKvTable()\n result.push(block)\n }\n\n flushKvTable()\n return result\n}\n\n// ─── 머리글/바닥글 감지 ────────────────────────────\n\n/**\n * 머리글/바닥글 감지 — 텍스트 반복 패턴 (숫자 normalization).\n *\n * v3.0.x: y 위치 클러스터 규칙(같은 y 버킷이 3+페이지 반복이면 텍스트가 달라도 제거)을\n * 삭제했다. 본문도 페이지마다 같은 y에서 시작/끝나므로 (균일한 상하 여백), 위치 반복만으로는\n * 머리글/바닥글과 본문 첫/마지막 줄을 구분할 수 없다 — 인사말씀·보고서류에서 본문 문단\n * 첫 줄과 섹션 제목(\"붙임 1\" 등)이 통째로 제거되는 사고가 corpus에서 다수 확인됨.\n * 페이지 번호(\"- 1 -\")처럼 가변 숫자가 있는 고정 문구는 # normalization으로 충분히 잡힌다.\n */\nexport function removeHeaderFooterBlocks(\n blocks: IRBlock[],\n pageHeights: Map<number, number>,\n warnings: ParseWarning[],\n): number[] {\n const ZONE_RATIO = 0.12 // 상하 12% (10% 초과 여백 대응)\n const MIN_REPEAT = 3 // 최소 3페이지 반복\n\n type ZoneEntry = { blockIdx: number; page: number; text: string }\n const topEntries: ZoneEntry[] = []\n const bottomEntries: ZoneEntry[] = []\n\n for (let bi = 0; bi < blocks.length; bi++) {\n const b = blocks[bi]\n if (!b.bbox || !b.pageNumber || !b.text?.trim()) continue\n const ph = pageHeights.get(b.bbox.page) || pageHeights.get(b.pageNumber)\n if (!ph) continue\n\n const blockTop = ph - (b.bbox.y + b.bbox.height)\n const blockBottom = ph - b.bbox.y\n const entry: ZoneEntry = { blockIdx: bi, page: b.pageNumber, text: b.text.trim() }\n\n if (blockBottom <= ph * ZONE_RATIO) bottomEntries.push(entry)\n else if (blockTop >= ph * (1 - ZONE_RATIO)) topEntries.push(entry)\n }\n\n const removeSet = new Set<number>()\n\n for (const entries of [topEntries, bottomEntries]) {\n if (entries.length === 0) continue\n\n // (1) 텍스트 반복 패턴\n const patternCount = new Map<string, number>()\n const patternPages = new Map<string, Set<number>>()\n for (const e of entries) {\n const norm = e.text.replace(/\\d+/g, \"#\")\n patternCount.set(norm, (patternCount.get(norm) || 0) + 1)\n const pages = patternPages.get(norm) || new Set<number>()\n pages.add(e.page)\n patternPages.set(norm, pages)\n }\n const repeatedPatterns = new Set<string>()\n for (const [p, count] of patternCount) {\n // 서로 다른 페이지에서 MIN_REPEAT번 이상 등장\n if (count >= MIN_REPEAT && (patternPages.get(p)?.size ?? 0) >= MIN_REPEAT) {\n repeatedPatterns.add(p)\n }\n }\n\n // 제거 대상: 텍스트 반복 패턴 매칭\n for (const e of entries) {\n const norm = e.text.replace(/\\d+/g, \"#\")\n if (repeatedPatterns.has(norm)) {\n removeSet.add(e.blockIdx)\n }\n }\n }\n\n if (removeSet.size > 0) {\n warnings.push({ message: `${removeSet.size}개 머리글/바닥글 요소 제거됨`, code: \"HIDDEN_TEXT_FILTERED\" })\n }\n\n return [...removeSet].sort((a, b) => a - b)\n}\n\nfunction mergeKoreanLines(text: string): string {\n if (!text) return \"\"\n const lines = text.split(\"\\n\")\n if (lines.length <= 1) return text\n const result: string[] = [lines[0]]\n\n for (let i = 1; i < lines.length; i++) {\n const prev = result[result.length - 1]\n const curr = lines[i]\n const currTrimmed = curr.trim()\n // 마크다운 헤딩/테이블/구분선은 병합하지 않음\n if (/^#{1,6}\\s/.test(prev) || /^#{1,6}\\s/.test(curr) || /^\\|/.test(currTrimmed) || /^---/.test(currTrimmed)) {\n result.push(curr)\n continue\n }\n // 쉼표로 끝나는 줄 + 다음 줄 = 연속 문장\n if (/,$/.test(prev.trim()) && currTrimmed.length > 0) {\n result[result.length - 1] = prev + \"\\n\" + curr\n continue\n }\n // (※ 로 시작하는 줄 = 이전 줄의 부연설명\n if (/^\\(※/.test(currTrimmed)) {\n result[result.length - 1] = prev + \" \" + currTrimmed\n continue\n }\n // 한글 줄바꿈 병합 — 마커(○, □ 등)로 시작하는 이전 줄은 합치지 않음\n if (/[가-힣·,\\-]$/.test(prev) && /^[가-힣(]/.test(curr) &&\n !startsWithMarker(curr) && !isStandaloneHeader(prev) &&\n !startsWithMarker(prev)) {\n result[result.length - 1] = prev + \" \" + curr\n } else {\n result.push(curr)\n }\n }\n return result.join(\"\\n\")\n}\n\n// ═══════════════════════════════════════════════════════\n// 수식 OCR 통합 (optional)\n// ═══════════════════════════════════════════════════════\n\n/**\n * 수식 OCR 을 적용하여 blocks 에 formula paragraph 를 삽입한다.\n *\n * 좌표 매핑:\n * - pdfium 픽셀 bbox (top-left origin) → PDF 포인트 (bottom-left origin) 변환\n * - 수식 bbox 의 y center 와 같은 페이지 내 pdfjs block 의 y center 비교로 삽입 위치 결정\n * - pdfjs 가 이미 뽑은 수식 흔적(block) 과 겹치면 해당 block 제거 (중복 방지)\n *\n * 실패/trivial 수식(latex === \"\") 은 삽입하지 않는다.\n */\nasync function applyFormulaOcr(\n buffer: ArrayBuffer,\n blocks: IRBlock[],\n pageFilter: Set<number> | null,\n effectivePageCount: number,\n warnings: ParseWarning[],\n _onProgress?: (current: number, total: number) => void,\n): Promise<void> {\n const formulaMod = await import(\"./formula/index.js\")\n const { FormulaPipeline, ensureFormulaModels } = formulaMod\n\n // 모델 준비 — 없으면 자동 다운로드. 진행률은 stderr 로 출력.\n await ensureFormulaModels((p) => {\n if (p.phase === \"download\" && p.total) {\n const pct = Math.floor((p.downloaded / p.total) * 100)\n process.stderr.write(`\\r[kordoc-formula] ${p.spec.name} ${pct}% (${formatMb(p.downloaded)}/${formatMb(p.total)})`)\n if (p.downloaded >= p.total) process.stderr.write(\"\\n\")\n } else if (p.phase === \"verify\") {\n process.stderr.write(`[kordoc-formula] ${p.spec.name} SHA-256 검증 중...\\n`)\n } else if (p.phase === \"done\") {\n process.stderr.write(`[kordoc-formula] ${p.spec.name} 준비 완료\\n`)\n } else if (p.phase === \"skip\") {\n // 조용히 스킵\n }\n })\n\n const pipeline = await FormulaPipeline.create()\n try {\n const pagesResult = await pipeline.runOnBuffer(buffer, pageFilter)\n\n if (pagesResult.length === 0) return\n\n let insertedCount = 0\n let removedDupCount = 0\n\n for (const page of pagesResult) {\n const pageNumber = page.pageNumber\n const pdfHeight = page.pdfHeight\n const scaleX = page.renderedWidth > 0 ? page.pdfWidth / page.renderedWidth : 0.5\n const scaleY = page.renderedHeight > 0 ? page.pdfHeight / page.renderedHeight : 0.5\n\n // 1) 수식 → (PDF 포인트 bbox, latex) 정규화 + trivial 제외\n interface FormulaCandidate {\n block: IRBlock\n pdfBbox: { x1: number; x2: number; yTop: number; yBottom: number }\n centerY: number // PDF bottom-up\n }\n const candidates: FormulaCandidate[] = []\n for (const r of page.regions) {\n if (!r.latex || !r.latex.trim()) continue\n const wrapped = r.kind === \"display\" ? `$$${r.latex}$$` : `$${r.latex}$`\n\n const x1 = r.bbox.x1 * scaleX\n const x2 = r.bbox.x2 * scaleX\n // pdfium 픽셀 y → PDF bottom-up\n const yTop = pdfHeight - r.bbox.y1 * scaleY\n const yBottom = pdfHeight - r.bbox.y2 * scaleY\n const centerY = (yTop + yBottom) / 2\n const width = x2 - x1\n const height = yTop - yBottom\n\n candidates.push({\n block: {\n type: \"paragraph\",\n text: wrapped,\n pageNumber,\n bbox: { page: pageNumber, x: x1, y: yBottom, width, height },\n },\n pdfBbox: { x1, x2, yTop, yBottom },\n centerY,\n })\n }\n if (candidates.length === 0) continue\n\n // 2) 같은 페이지의 pdfjs block 중 수식 bbox 와 크게 겹치는 것 제거\n // (pdfjs 가 수식을 텍스트로 파편 추출한 경우 — overlap ratio ≥ 0.6 이면 중복으로 간주)\n const OVERLAP_THRESHOLD = 0.6\n const indicesToRemove = new Set<number>()\n for (let i = 0; i < blocks.length; i++) {\n const b = blocks[i]\n if (b.pageNumber !== pageNumber) continue\n if (b.type === \"table\") continue // 표는 건드리지 않음\n if (!b.bbox || b.bbox.width <= 0 || b.bbox.height <= 0) continue\n const blockArea = b.bbox.width * b.bbox.height\n if (blockArea <= 0) continue\n\n for (const c of candidates) {\n const ox1 = Math.max(b.bbox.x, c.pdfBbox.x1)\n const ox2 = Math.min(b.bbox.x + b.bbox.width, c.pdfBbox.x2)\n const oy1 = Math.max(b.bbox.y, c.pdfBbox.yBottom)\n const oy2 = Math.min(b.bbox.y + b.bbox.height, c.pdfBbox.yTop)\n const interArea = Math.max(0, ox2 - ox1) * Math.max(0, oy2 - oy1)\n if (interArea / blockArea >= OVERLAP_THRESHOLD) {\n indicesToRemove.add(i)\n break\n }\n }\n }\n\n if (indicesToRemove.size > 0) {\n // 내림차순으로 제거해야 인덱스가 밀리지 않음\n const sorted = [...indicesToRemove].sort((a, b) => b - a)\n for (const idx of sorted) blocks.splice(idx, 1)\n removedDupCount += indicesToRemove.size\n }\n\n // 3) 각 수식을 y 좌표 기준 적절한 위치에 삽입\n // 수식들을 위→아래(centerY 큰 것부터) 정렬 후, 각 수식마다 현재 blocks 에서\n // \"center y < 수식 center y\" 인 첫 블록(= 수식보다 아래) 앞에 삽입.\n candidates.sort((a, b) => b.centerY - a.centerY)\n\n for (const c of candidates) {\n let insertIdx = -1\n let pageFirstIdx = -1\n let pageLastIdx = -1\n for (let i = 0; i < blocks.length; i++) {\n const b = blocks[i]\n if (b.pageNumber !== pageNumber) continue\n if (pageFirstIdx === -1) pageFirstIdx = i\n pageLastIdx = i\n if (!b.bbox) continue\n const blockCenter = b.bbox.y + b.bbox.height / 2\n if (blockCenter < c.centerY) {\n insertIdx = i\n break\n }\n }\n\n if (insertIdx !== -1) {\n blocks.splice(insertIdx, 0, c.block)\n } else if (pageLastIdx !== -1) {\n blocks.splice(pageLastIdx + 1, 0, c.block)\n } else {\n // 해당 페이지에 텍스트 블록 없음 — 맨 끝에 추가\n blocks.push(c.block)\n }\n insertedCount++\n }\n }\n\n if (insertedCount > 0 || removedDupCount > 0) {\n process.stderr.write(\n `[kordoc-formula] ${insertedCount}개 수식 삽입, ${removedDupCount}개 중복 block 제거 (${pagesResult.length}개 페이지)\\n`,\n )\n }\n } finally {\n await pipeline.destroy().catch(() => {})\n }\n}\n\nfunction formatMb(bytes: number): string {\n return `${(bytes / 1024 / 1024).toFixed(1)}MB`\n}\n"],"mappings":"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","names":["_","g","g","totalCells","emptyCells","g"]}